patch 9.1.0770: current command line completion is a bit limited

Problem:  current command completion is a bit limited
Solution: Add the shellcmdline completion type and getmdcomplpat()
          function (Ruslan Russkikh).

closes: #15823

Signed-off-by: Ruslan Russkikh <dvrussk@yandex.ru>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index c1ed03b..c7c42ab 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -59,6 +59,7 @@
 	    && xp->xp_context != EXPAND_PACKADD
 	    && xp->xp_context != EXPAND_RUNTIME
 	    && xp->xp_context != EXPAND_SHELLCMD
+	    && xp->xp_context != EXPAND_SHELLCMDLINE
 	    && xp->xp_context != EXPAND_TAGS
 	    && xp->xp_context != EXPAND_TAGS_LISTFILES
 	    && xp->xp_context != EXPAND_USER_LIST);
@@ -1754,7 +1755,7 @@
     xp->xp_context = EXPAND_FILES;
 
     // For a shell command more chars need to be escaped.
-    if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal)
+    if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal || *complp == EXPAND_SHELLCMDLINE)
     {
 #ifndef BACKSLASH_IN_FILENAME
 	xp->xp_shell = TRUE;
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 67104ea..d902045 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2091,6 +2091,8 @@
 			ret_dict_any,	    f_getcharsearch},
     {"getcharstr",	0, 1, 0,	    arg1_bool,
 			ret_string,	    f_getcharstr},
+    {"getcmdcomplpat",	0, 0, 0,	    NULL,
+			ret_string,	    f_getcmdcomplpat},
     {"getcmdcompltype",	0, 0, 0,	    NULL,
 			ret_string,	    f_getcmdcompltype},
     {"getcmdline",	0, 0, 0,	    NULL,
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 980faf8..b7804d1 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -4205,6 +4205,40 @@
 }
 
 /*
+ * Get the current command-line completion pattern.
+ */
+    static char_u *
+get_cmdline_completion_pattern(void)
+{
+    cmdline_info_T *p;
+    int		xp_context;
+
+    if (cmdline_star > 0)
+	return NULL;
+
+    p = get_ccline_ptr();
+    if (p == NULL || p->xpc == NULL)
+	return NULL;
+
+    xp_context = p->xpc->xp_context;
+    if (xp_context == EXPAND_NOTHING)
+    {
+	set_expand_context(p->xpc);
+	xp_context = p->xpc->xp_context;
+	p->xpc->xp_context = EXPAND_NOTHING;
+    }
+    if (xp_context == EXPAND_UNSUCCESSFUL)
+	return NULL;
+
+    char_u *compl_pat = p->xpc->xp_pattern;
+
+    if (compl_pat == NULL)
+	return NULL;
+
+    return vim_strsave(compl_pat);
+}
+
+/*
  * Get the current command-line completion type.
  */
     static char_u *
@@ -4248,6 +4282,16 @@
 }
 
 /*
+ * "getcmdcomplpat()" function
+ */
+    void
+f_getcmdcomplpat(typval_T *argvars UNUSED, typval_T *rettv)
+{
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = get_cmdline_completion_pattern();
+}
+
+/*
  * "getcmdcompltype()" function
  */
     void
diff --git a/src/proto/ex_getln.pro b/src/proto/ex_getln.pro
index 1ab3b1c..cc8723a 100644
--- a/src/proto/ex_getln.pro
+++ b/src/proto/ex_getln.pro
@@ -30,6 +30,7 @@
 void escape_fname(char_u **pp);
 void tilde_replace(char_u *orig_pat, int num_files, char_u **files);
 cmdline_info_T *get_cmdline_info(void);
+void f_getcmdcomplpat(typval_T *argvars, typval_T *rettv);
 void f_getcmdcompltype(typval_T *argvars, typval_T *rettv);
 void f_getcmdline(typval_T *argvars, typval_T *rettv);
 void f_getcmdpos(typval_T *argvars, typval_T *rettv);
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 790efaa..418dafa 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -1078,6 +1078,16 @@
   endif
 endfunc
 
+func Test_cmdline_complete_shellcmdline()
+  CheckExecutable whoami
+  command -nargs=1 -complete=shellcmdline MyCmd
+
+  call feedkeys(":MyCmd whoam\<C-A>\<C-B>\"\<CR>", 'tx')
+  call assert_match('^".*\<whoami\>', @:)
+
+  delcommand MyCmd
+endfunc
+
 func Test_cmdline_complete_bang()
   CheckExecutable whoami
   call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx')
@@ -3631,6 +3641,52 @@
   endfor
 endfunc
 
+" Test for shellcmdline command argument completion
+func Test_cmdline_complete_shellcmdline_argument()
+  command -nargs=+ -complete=shellcmdline MyCmd
+
+  set wildoptions=fuzzy
+
+  call feedkeys(":MyCmd vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim test_cmdline.vim', @:)
+
+  call feedkeys(":MyCmd vim nonexistentfile\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim nonexistentfile', @:)
+
+  let compl1 = getcompletion('', 'file')[0]
+  let compl2 = getcompletion('', 'file')[1]
+  call feedkeys(":MyCmd vim \<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1, @:)
+
+  call feedkeys(":MyCmd vim \<Tab> \<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl1, @:)
+
+  let compl = getcompletion('', 'file')[1]
+  call feedkeys(":MyCmd vim \<Tab> \<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl2, @:)
+
+  set wildoptions&
+  call feedkeys(":MyCmd vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim test_cmdline.vim', @:)
+
+  call feedkeys(":MyCmd vim nonexistentfile\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim nonexistentfile', @:)
+
+  let compl1 = getcompletion('', 'file')[0]
+  let compl2 = getcompletion('', 'file')[1]
+  call feedkeys(":MyCmd vim \<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1, @:)
+
+  call feedkeys(":MyCmd vim \<Tab> \<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl1, @:)
+
+  let compl = getcompletion('', 'file')[1]
+  call feedkeys(":MyCmd vim \<Tab> \<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal('"MyCmd vim ' .. compl1 .. ' ' .. compl2, @:)
+
+  delcommand MyCmd
+endfunc
+
 " Test for :! shell command argument completion
 func Test_cmdline_complete_bang_cmd_argument()
   set wildoptions=fuzzy
@@ -3642,30 +3698,32 @@
 endfunc
 
 func Call_cmd_funcs()
-  return [getcmdpos(), getcmdscreenpos(), getcmdcompltype()]
+  return [getcmdpos(), getcmdscreenpos(), getcmdcompltype(), getcmdcomplpat()]
 endfunc
 
 func Test_screenpos_and_completion()
   call assert_equal(0, getcmdpos())
   call assert_equal(0, getcmdscreenpos())
   call assert_equal('', getcmdcompltype())
+  call assert_equal('', getcmdcomplpat())
 
   cnoremap <expr> <F2> string(Call_cmd_funcs())
   call feedkeys(":let a\<F2>\<C-B>\"\<CR>", "xt")
-  call assert_equal("\"let a[6, 7, 'var']", @:)
+  call assert_equal("\"let a[6, 7, 'var', 'a']", @:)
   call feedkeys(":quit \<F2>\<C-B>\"\<CR>", "xt")
-  call assert_equal("\"quit [6, 7, '']", @:)
+  call assert_equal("\"quit [6, 7, '', '']", @:)
   call feedkeys(":nosuchcommand \<F2>\<C-B>\"\<CR>", "xt")
-  call assert_equal("\"nosuchcommand [15, 16, '']", @:)
+  call assert_equal("\"nosuchcommand [15, 16, '', '']", @:)
 
-  " Check that getcmdcompltype() doesn't interfere with cmdline completion.
+  " Check that getcmdcompltype() and getcmdcomplpat() don't interfere with
+  " cmdline completion.
   let g:results = []
   cnoremap <F2> <Cmd>let g:results += [[getcmdline()] + Call_cmd_funcs()]<CR>
   call feedkeys(":sign un\<Tab>\<F2>\<Tab>\<F2>\<Tab>\<F2>\<C-C>", "xt")
   call assert_equal([
-        \ ['sign undefine', 14, 15, 'sign'],
-        \ ['sign unplace', 13, 14, 'sign'],
-        \ ['sign un', 8, 9, 'sign']], g:results)
+        \ ['sign undefine', 14, 15, 'sign', 'undefine'],
+        \ ['sign unplace', 13, 14, 'sign', 'unplace'],
+        \ ['sign un', 8, 9, 'sign', 'un']], g:results)
 
   unlet g:results
   cunmap <F2>
diff --git a/src/usercmd.c b/src/usercmd.c
index 585ced3..43409ae 100644
--- a/src/usercmd.c
+++ b/src/usercmd.c
@@ -92,6 +92,7 @@
     KEYVALUE_ENTRY(EXPAND_SCRIPTNAMES, "scriptnames"),
 #endif
     KEYVALUE_ENTRY(EXPAND_SHELLCMD, "shellcmd"),
+    KEYVALUE_ENTRY(EXPAND_SHELLCMDLINE, "shellcmdline"),
 #if defined(FEAT_SIGNS)
     KEYVALUE_ENTRY(EXPAND_SIGN, "sign"),
 #endif
@@ -328,7 +329,6 @@
     if (argt & EX_XFILE)
     {
 	// EX_XFILE: file names are handled before this call
-	xp->xp_context = context;
 	return NULL;
     }
 
@@ -850,7 +850,7 @@
     *complp = entry->key;
     if (*complp == EXPAND_BUFFERS)
 	*argt |= EX_BUFNAME;
-    else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES)
+    else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
 	*argt |= EX_XFILE;
 
     if (
diff --git a/src/version.c b/src/version.c
index b87fc78..e949908 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    770,
+/**/
     769,
 /**/
     768,
diff --git a/src/vim.h b/src/vim.h
index ebca6ae..387fe4f 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -844,6 +844,7 @@
 #define EXPAND_TERMINALOPT	57
 #define EXPAND_KEYMAP		58
 #define EXPAND_DIRS_IN_CDPATH	59
+#define EXPAND_SHELLCMDLINE	60
 
 
 // Values for exmode_active (0 is no exmode)