# HG changeset patch # User Bram Moolenaar # Date 1566157503 -7200 # Node ID 87a8760babec4df1887ed63c00d15ac0ed1fb03a # Parent b684d0c86bda295c27b6340f5be585e75e9449b7 patch 8.1.1886: command line expansion code is spread out commit https://github.com/vim/vim/commit/66b51420e0c8d49bcf6786b792c938d6099e3393 Author: Bram Moolenaar Date: Sun Aug 18 21:44:12 2019 +0200 patch 8.1.1886: command line expansion code is spread out Problem: Command line expansion code is spread out. Solution: Move the code to cmdexpand.c. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/4831) diff --git a/Filelist b/Filelist --- a/Filelist +++ b/Filelist @@ -24,6 +24,7 @@ SRC_ALL = \ src/change.c \ src/channel.c \ src/charset.c \ + src/cmdexpand.c \ src/cmdhist.c \ src/crypt.c \ src/crypt_zip.c \ @@ -175,6 +176,7 @@ SRC_ALL = \ src/proto/change.pro \ src/proto/channel.pro \ src/proto/charset.pro \ + src/proto/cmdexpand.pro \ src/proto/cmdhist.pro \ src/proto/crypt.pro \ src/proto/crypt_zip.pro \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -711,6 +711,7 @@ OBJ = \ $(OUTDIR)/buffer.o \ $(OUTDIR)/change.o \ $(OUTDIR)/charset.o \ + $(OUTDIR)/cmdexpand.o \ $(OUTDIR)/cmdhist.o \ $(OUTDIR)/crypt.o \ $(OUTDIR)/crypt_zip.o \ diff --git a/src/Make_morph.mak b/src/Make_morph.mak --- a/src/Make_morph.mak +++ b/src/Make_morph.mak @@ -31,6 +31,7 @@ SRC = arabic.c \ buffer.c \ change.c \ charset.c \ + cmdexpand.c \ cmdhist.c \ crypt.c \ crypt_zip.c \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -720,6 +720,7 @@ OBJ = \ $(OUTDIR)\buffer.obj \ $(OUTDIR)\change.obj \ $(OUTDIR)\charset.obj \ + $(OUTDIR)\cmdexpand.obj \ $(OUTDIR)\cmdhist.obj \ $(OUTDIR)\crypt.obj \ $(OUTDIR)\crypt_zip.obj \ @@ -1450,6 +1451,8 @@ testclean: $(OUTDIR)/charset.obj: $(OUTDIR) charset.c $(INCL) +$(OUTDIR)/cmdexpand.obj: $(OUTDIR) cmdexpand.c $(INCL) + $(OUTDIR)/cmdhist.obj: $(OUTDIR) cmdhist.c $(INCL) $(OUTDIR)/crypt.obj: $(OUTDIR) crypt.c $(INCL) @@ -1754,6 +1757,7 @@ proto.h: \ proto/buffer.pro \ proto/change.pro \ proto/charset.pro \ + proto/cmdexpand.pro \ proto/cmdhist.pro \ proto/crypt.pro \ proto/crypt_zip.pro \ diff --git a/src/Make_vms.mms b/src/Make_vms.mms --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -2,7 +2,7 @@ # Makefile for Vim on OpenVMS # # Maintainer: Zoltan Arpadffy -# Last change: 2019 Jul 14 +# Last change: 2019 Aug 18 # # This has script been tested on VMS 6.2 to 8.2 on DEC Alpha, VAX and IA64 # with MMS and MMK @@ -308,11 +308,11 @@ ALL_LIBS = $(LIBS) $(GUI_LIB_DIR) $(GUI_ $(PERL_LIB) $(PYTHON_LIB) $(TCL_LIB) $(RUBY_LIB) SRC = arabic.c arglist.c autocmd.c beval.c blob.c blowfish.c buffer.c \ - change.c charset.c cmdhist.c crypt.c crypt_zip.c debugger.c dict.c \ - diff.c digraph.c edit.c eval.c evalfunc.c ex_cmds.c ex_cmds2.c \ - ex_docmd.c ex_eval.c ex_getln.c if_cscope.c if_xcmdsrv.c fileio.c \ - findfile.c fold.c getchar.c hardcopy.c hashtab.c highlight.c indent.c \ - insexpand.c json.c list.c main.c map.c mark.c menu.c mbyte.c \ + change.c charset.c cmdexpand.c cmdhist.c crypt.c crypt_zip.c \ + debugger.c dict.c diff.c digraph.c edit.c eval.c evalfunc.c ex_cmds.c \ + ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c if_cscope.c if_xcmdsrv.c \ + fileio.c findfile.c fold.c getchar.c hardcopy.c hashtab.c highlight.c \ + indent.c insexpand.c json.c list.c main.c map.c mark.c menu.c mbyte.c \ memfile.c memline.c message.c misc1.c misc2.c move.c normal.c ops.c \ option.c popupmnu.c popupwin.c profiler.c quickfix.c regexp.c \ search.c session.c sha256.c sign.c spell.c spellfile.c syntax.c tag.c \ @@ -323,18 +323,18 @@ SRC = arabic.c arglist.c autocmd.c beval $(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC) OBJ = arabic.obj arglist.obj autocmd.obj beval.obj blob.obj blowfish.obj \ - buffer.obj change.obj charset.obj cmdhist.obj crypt.obj crypt_zip.obj \ - debugger.obj dict.obj diff.obj digraph.obj edit.obj eval.obj \ - evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj ex_eval.obj \ - ex_getln.obj if_cscope.obj if_xcmdsrv.obj fileio.obj findfile.obj \ - fold.obj getchar.obj hardcopy.obj hashtab.obj highlight.obj \ - indent.obj insexpand.obj json.obj list.obj main.obj map.obj mark.obj \ - menu.obj memfile.obj memline.obj message.obj misc1.obj misc2.obj \ - move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj \ - popupwin.obj profiler.obj quickfix.obj regexp.obj search.obj \ - session.obj sha256.obj sign.obj spell.obj spellfile.obj syntax.obj \ - tag.obj term.obj termlib.obj testing.obj textprop.obj ui.obj undo.obj \ - usercmd.obj userfunc.obj screen.obj version.obj \ + buffer.obj change.obj charset.obj cmdexpand.obj cmdhist.obj \ + crypt.obj crypt_zip.obj debugger.obj dict.obj diff.obj digraph.obj \ + edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj \ + ex_eval.obj ex_getln.obj if_cscope.obj if_xcmdsrv.obj fileio.obj \ + findfile.obj fold.obj getchar.obj hardcopy.obj hashtab.obj \ + highlight.obj indent.obj insexpand.obj json.obj list.obj main.obj \ + map.obj mark.obj menu.obj memfile.obj memline.obj message.obj \ + misc1.obj misc2.obj move.obj mbyte.obj normal.obj ops.obj option.obj \ + popupmnu.obj popupwin.obj profiler.obj quickfix.obj regexp.obj \ + search.obj session.obj sha256.obj sign.obj spell.obj spellfile.obj \ + syntax.obj tag.obj term.obj termlib.obj testing.obj textprop.obj \ + ui.obj undo.obj usercmd.obj userfunc.obj screen.obj version.obj \ viminfo.obj window.obj os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \ $(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \ $(RUBY_OBJ) $(HANGULIN_OBJ) $(MZSCH_OBJ) $(XDIFF_OBJ) @@ -523,6 +523,10 @@ charset.obj : charset.c vim.h [.auto]con ascii.h keymap.h term.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ globals.h +cmdexpand.obj : cmdexpand.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h term.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + globals.h cmdhist.obj : cmdhist.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -1585,6 +1585,7 @@ BASIC_SRC = \ buffer.c \ change.c \ charset.c \ + cmdexpand.c \ cmdhist.c \ crypt.c \ crypt_zip.c \ @@ -1712,6 +1713,7 @@ OBJ_COMMON = \ objects/change.o \ objects/blob.o \ objects/blowfish.o \ + objects/cmdexpand.o \ objects/cmdhist.o \ objects/crypt.o \ objects/crypt_zip.o \ @@ -1852,6 +1854,7 @@ PRO_AUTO = \ buffer.pro \ change.pro \ charset.pro \ + cmdexpand.pro \ cmdhist.pro \ crypt.pro \ crypt_zip.pro \ @@ -3013,6 +3016,9 @@ objects/change.o: change.c objects/charset.o: charset.c $(CCC) -o $@ charset.c +objects/cmdexpand.o: cmdexpand.c + $(CCC) -o $@ cmdexpand.c + objects/cmdhist.o: cmdhist.c $(CCC) -o $@ cmdhist.c @@ -3514,6 +3520,10 @@ objects/charset.o: charset.c vim.h proto auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h +objects/cmdexpand.o: cmdexpand.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h objects/cmdhist.o: cmdhist.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/README.md b/src/README.md --- a/src/README.md +++ b/src/README.md @@ -28,6 +28,7 @@ autocmd.c | autocommands blob.c | blob data type buffer.c | manipulating buffers (loaded files) change.c | handling changes to text +cmdexpand.c | command-line completion cmdhist.c | command-line history debugger.c | vim script debugger diff.c | diff mode (vimdiff) diff --git a/src/cmdexpand.c b/src/cmdexpand.c new file mode 100644 --- /dev/null +++ b/src/cmdexpand.c @@ -0,0 +1,2031 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * cmdexpand.c: functions for command-line completion + */ + +#include "vim.h" + +static int cmd_showtail; // Only show path tail in lists ? + +static void set_expand_context(expand_T *xp); +static int ExpandFromContext(expand_T *xp, char_u *, int *, char_u ***, int); +static int expand_showtail(expand_T *xp); +#ifdef FEAT_CMDL_COMPL +static int expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int flagsarg); +static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char *dirname[]); +static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file); +# if defined(FEAT_EVAL) +static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file); +static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file); +# endif +#endif + +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) + static int +sort_func_compare(const void *s1, const void *s2) +{ + char_u *p1 = *(char_u **)s1; + char_u *p2 = *(char_u **)s2; + + if (*p1 != '<' && *p2 == '<') return -1; + if (*p1 == '<' && *p2 != '<') return 1; + return STRCMP(p1, p2); +} +#endif + + static void +ExpandEscape( + expand_T *xp, + char_u *str, + int numfiles, + char_u **files, + int options) +{ + int i; + char_u *p; + + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) + tilde_replace(str, numfiles, files); + + if (options & WILD_ESCAPE) + { + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) + { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (i = 0; i < numfiles; ++i) + { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash == XP_BS_THREE) + { + p = vim_strsave_escaped(files[i], (char_u *)" "); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; +#if defined(BACKSLASH_IN_FILENAME) + p = vim_strsave_escaped(files[i], (char_u *)" "); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } +#endif + } + } +#ifdef BACKSLASH_IN_FILENAME + p = vim_strsave_fnameescape(files[i], FALSE); +#else + p = vim_strsave_fnameescape(files[i], xp->xp_shell); +#endif + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } + + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') + escape_fname(&files[i]); + } + xp->xp_backslash = XP_BS_NONE; + + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') + escape_fname(&files[0]); + } + else if (xp->xp_context == EXPAND_TAGS) + { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (i = 0; i < numfiles; ++i) + { + p = vim_strsave_escaped(files[i], (char_u *)"\\|\""); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } + } + } + } +} + +/* + * Return FAIL if this is not an appropriate context in which to do + * completion of anything, return OK if it is (even if there are no matches). + * For the caller, this means that the character is just passed through like a + * normal character (instead of being expanded). This allows :s/^I^D etc. + */ + int +nextwild( + expand_T *xp, + int type, + int options, // extra options for ExpandOne() + int escape) // if TRUE, escape the returned matches +{ + cmdline_info_T *ccline = get_cmdline_info(); + int i, j; + char_u *p1; + char_u *p2; + int difflen; + int v; + + if (xp->xp_numfiles == -1) + { + set_expand_context(xp); + cmd_showtail = expand_showtail(xp); + } + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return OK; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) + { + // Caller can use the character as a normal char instead + return FAIL; + } + + msg_puts("..."); // show that we are busy + out_flush(); + + i = (int)(xp->xp_pattern - ccline->cmdbuff); + xp->xp_pattern_len = ccline->cmdpos - i; + + if (type == WILD_NEXT || type == WILD_PREV) + { + // Get next/previous match for a previous expanded pattern. + p2 = ExpandOne(xp, NULL, NULL, 0, type); + } + else + { + // Translate string into pattern and expand it. + if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, + xp->xp_context)) == NULL) + p2 = NULL; + else + { + int use_options = options | + WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; + 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); + vim_free(p1); + // longest match: make sure it is not shorter, happens with :help + if (p2 != NULL && type == WILD_LONGEST) + { + for (j = 0; j < xp->xp_pattern_len; ++j) + if (ccline->cmdbuff[i + j] == '*' + || ccline->cmdbuff[i + j] == '?') + break; + if ((int)STRLEN(p2) < j) + VIM_CLEAR(p2); + } + } + } + + if (p2 != NULL && !got_int) + { + difflen = (int)STRLEN(p2) - xp->xp_pattern_len; + if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) + { + v = realloc_cmdbuff(ccline->cmdlen + difflen + 4); + xp->xp_pattern = ccline->cmdbuff + i; + } + else + v = OK; + if (v == OK) + { + mch_memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], + &ccline->cmdbuff[ccline->cmdpos], + (size_t)(ccline->cmdlen - ccline->cmdpos + 1)); + mch_memmove(&ccline->cmdbuff[i], p2, STRLEN(p2)); + ccline->cmdlen += difflen; + ccline->cmdpos += difflen; + } + } + vim_free(p2); + + redrawcmd(); + cursorcmd(); + + // When expanding a ":map" command and no matches are found, assume that + // the key is supposed to be inserted literally + if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) + return FAIL; + + if (xp->xp_numfiles <= 0 && p2 == NULL) + beep_flush(); + else if (xp->xp_numfiles == 1) + // free expanded pattern + (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); + + return OK; +} + +/* + * Do wildcard expansion on the string 'str'. + * Chars that should not be expanded must be preceded with a backslash. + * Return a pointer to allocated memory containing the new string. + * Return NULL for failure. + * + * "orig" is the originally expanded string, copied to allocated memory. It + * should either be kept in orig_save or freed. When "mode" is WILD_NEXT or + * WILD_PREV "orig" should be NULL. + * + * Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" + * is WILD_EXPAND_FREE or WILD_ALL. + * + * mode = WILD_FREE: just free previously expanded matches + * mode = WILD_EXPAND_FREE: normal expansion, do not keep matches + * mode = WILD_EXPAND_KEEP: normal expansion, keep matches + * mode = WILD_NEXT: use next match in multiple match, wrap to first + * mode = WILD_PREV: use previous match in multiple match, wrap to first + * mode = WILD_ALL: return all matches concatenated + * mode = WILD_LONGEST: return longest matched part + * mode = WILD_ALL_KEEP: get all matches, keep matches + * + * options = WILD_LIST_NOTFOUND: list entries without a match + * options = WILD_HOME_REPLACE: do home_replace() for buffer names + * options = WILD_USE_NL: Use '\n' for WILD_ALL + * options = WILD_NO_BEEP: Don't beep for multiple matches + * options = WILD_ADD_SLASH: add a slash after directory names + * options = WILD_KEEP_ALL: don't remove 'wildignore' entries + * options = WILD_SILENT: don't print warning messages + * options = WILD_ESCAPE: put backslash before special chars + * options = WILD_ICASE: ignore case for files + * + * The variables xp->xp_context and xp->xp_backslash must have been set! + */ + char_u * +ExpandOne( + expand_T *xp, + char_u *str, + char_u *orig, // allocated copy of original of expanded string + int options, + int mode) +{ + char_u *ss = NULL; + static int findex; + static char_u *orig_save = NULL; // kept value of orig + int orig_saved = FALSE; + int i; + long_u len; + int non_suf_match; // number without matching suffix + + // first handle the case of using an old match + if (mode == WILD_NEXT || mode == WILD_PREV) + { + if (xp->xp_numfiles > 0) + { + if (mode == WILD_PREV) + { + if (findex == -1) + findex = xp->xp_numfiles; + --findex; + } + else // mode == WILD_NEXT + ++findex; + + // When wrapping around, return the original string, set findex to + // -1. + if (findex < 0) + { + if (orig_save == NULL) + findex = xp->xp_numfiles - 1; + else + findex = -1; + } + if (findex >= xp->xp_numfiles) + { + if (orig_save == NULL) + findex = 0; + else + findex = -1; + } +#ifdef FEAT_WILDMENU + if (p_wmnu) + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); +#endif + if (findex == -1) + return vim_strsave(orig_save); + return vim_strsave(xp->xp_files[findex]); + } + else + return NULL; + } + + // free old names + if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) + { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + VIM_CLEAR(orig_save); + } + findex = 0; + + if (mode == WILD_FREE) // only release file name + return NULL; + + if (xp->xp_numfiles == -1) + { + vim_free(orig_save); + orig_save = orig; + orig_saved = TRUE; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, + options) == FAIL) + { +#ifdef FNAME_ILLEGAL + // Illegal file name has been silently skipped. But when there + // are wildcards, the real problem is that there was no match, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) + semsg(_(e_nomatch2), str); +#endif + } + else if (xp->xp_numfiles == 0) + { + if (!(options & WILD_SILENT)) + semsg(_(e_nomatch2), str); + } + else + { + // Escape the matches for use on the command line. + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + // Check for matching suffixes in file names. + if (mode != WILD_ALL && mode != WILD_ALL_KEEP + && mode != WILD_LONGEST) + { + if (xp->xp_numfiles) + non_suf_match = xp->xp_numfiles; + else + non_suf_match = 1; + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) + { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (i = 0; i < 2; ++i) + if (match_suffix(xp->xp_files[i])) + ++non_suf_match; + } + if (non_suf_match != 1) + { + // Can we ever get here unless it's while expanding + // interactively? If not, we can get rid of this all + // together. Don't really want to wait for this message + // (and possibly have to hit return to continue!). + if (!(options & WILD_SILENT)) + emsg(_(e_toomany)); + else if (!(options & WILD_NO_BEEP)) + beep_flush(); + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) + ss = vim_strsave(xp->xp_files[0]); + } + } + } + + // Find longest common part + if (mode == WILD_LONGEST && xp->xp_numfiles > 0) + { + int mb_len = 1; + int c0, ci; + + for (len = 0; xp->xp_files[0][len]; len += mb_len) + { + if (has_mbyte) + { + mb_len = (*mb_ptr2len)(&xp->xp_files[0][len]); + c0 =(* mb_ptr2char)(&xp->xp_files[0][len]); + } + else + c0 = xp->xp_files[0][len]; + for (i = 1; i < xp->xp_numfiles; ++i) + { + if (has_mbyte) + ci =(* mb_ptr2char)(&xp->xp_files[i][len]); + else + ci = xp->xp_files[i][len]; + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) + { + if (MB_TOLOWER(c0) != MB_TOLOWER(ci)) + break; + } + else if (c0 != ci) + break; + } + if (i < xp->xp_numfiles) + { + if (!(options & WILD_NO_BEEP)) + vim_beep(BO_WILD); + break; + } + } + + ss = alloc(len + 1); + if (ss) + vim_strncpy(ss, xp->xp_files[0], (size_t)len); + findex = -1; // next p_wc gets first one + } + + // Concatenate all matching names + if (mode == WILD_ALL && xp->xp_numfiles > 0) + { + len = 0; + for (i = 0; i < xp->xp_numfiles; ++i) + len += (long_u)STRLEN(xp->xp_files[i]) + 1; + ss = alloc(len); + if (ss != NULL) + { + *ss = NUL; + for (i = 0; i < xp->xp_numfiles; ++i) + { + STRCAT(ss, xp->xp_files[i]); + if (i != xp->xp_numfiles - 1) + STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) + ExpandCleanup(xp); + + // Free "orig" if it wasn't stored in "orig_save". + if (!orig_saved) + vim_free(orig); + + return ss; +} + +/* + * Prepare an expand structure for use. + */ + void +ExpandInit(expand_T *xp) +{ + xp->xp_pattern = NULL; + xp->xp_pattern_len = 0; + xp->xp_backslash = XP_BS_NONE; +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = FALSE; +#endif + xp->xp_numfiles = -1; + xp->xp_files = NULL; +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + xp->xp_arg = NULL; +#endif + xp->xp_line = NULL; +} + +/* + * Cleanup an expand structure after use. + */ + void +ExpandCleanup(expand_T *xp) +{ + if (xp->xp_numfiles >= 0) + { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + } +} + +/* + * Show all matches for completion on the command line. + * Returns EXPAND_NOTHING when the character that triggered expansion should + * be inserted like a normal character. + */ + int +showmatches(expand_T *xp, int wildmenu UNUSED) +{ + cmdline_info_T *ccline = get_cmdline_info(); +#define L_SHOWFILE(m) (showtail ? sm_gettail(files_found[m]) : files_found[m]) + int num_files; + char_u **files_found; + int i, j, k; + int maxlen; + int lines; + int columns; + char_u *p; + int lastlen; + int attr; + int showtail; + + if (xp->xp_numfiles == -1) + { + set_expand_context(xp); + i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, + &num_files, &files_found); + showtail = expand_showtail(xp); + if (i != EXPAND_OK) + return i; + + } + else + { + num_files = xp->xp_numfiles; + files_found = xp->xp_files; + showtail = cmd_showtail; + } + +#ifdef FEAT_WILDMENU + if (!wildmenu) + { +#endif + msg_didany = FALSE; // lines_left will be set + msg_start(); // prepare for paging + msg_putchar('\n'); + out_flush(); + cmdline_row = msg_row; + msg_didany = FALSE; // lines_left will be set again + msg_start(); // prepare for paging +#ifdef FEAT_WILDMENU + } +#endif + + if (got_int) + got_int = FALSE; // only int. the completion, not the cmd line +#ifdef FEAT_WILDMENU + else if (wildmenu) + win_redr_status_matches(xp, num_files, files_found, -1, showtail); +#endif + else + { + // find the length of the longest file name + maxlen = 0; + for (i = 0; i < num_files; ++i) + { + if (!showtail && (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) + { + home_replace(NULL, files_found[i], NameBuff, MAXPATHL, TRUE); + j = vim_strsize(NameBuff); + } + else + j = vim_strsize(L_SHOWFILE(i)); + if (j > maxlen) + maxlen = j; + } + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + lines = num_files; + else + { + // compute the number of columns and lines for the listing + maxlen += 2; // two spaces between file names + columns = ((int)Columns + 2) / maxlen; + if (columns < 1) + columns = 1; + lines = (num_files + columns - 1) / columns; + } + + attr = HL_ATTR(HLF_D); // find out highlighting for directories + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + { + msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); + msg_clr_eos(); + msg_advance(maxlen - 3); + msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); + } + + // list the files line by line + for (i = 0; i < lines; ++i) + { + lastlen = 999; + for (k = i; k < num_files; k += lines) + { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + { + msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); + p = files_found[k] + STRLEN(files_found[k]) + 1; + msg_advance(maxlen + 1); + msg_puts((char *)p); + msg_advance(maxlen + 3); + msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); + break; + } + for (j = maxlen - lastlen; --j >= 0; ) + msg_putchar(' '); + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) + { + // highlight directories + if (xp->xp_numfiles != -1) + { + char_u *halved_slash; + char_u *exp_path; + + // Expansion was done before and special characters + // were escaped, need to halve backslashes. Also + // $HOME has been replaced with ~/. + exp_path = expand_env_save_opt(files_found[k], TRUE); + halved_slash = backslash_halve_save( + exp_path != NULL ? exp_path : files_found[k]); + j = mch_isdir(halved_slash != NULL ? halved_slash + : files_found[k]); + vim_free(exp_path); + vim_free(halved_slash); + } + else + // Expansion was done here, file names are literal. + j = mch_isdir(files_found[k]); + if (showtail) + p = L_SHOWFILE(k); + else + { + home_replace(NULL, files_found[k], NameBuff, MAXPATHL, + TRUE); + p = NameBuff; + } + } + else + { + j = FALSE; + p = L_SHOWFILE(k); + } + lastlen = msg_outtrans_attr(p, j ? attr : 0); + } + if (msg_col > 0) // when not wrapped around + { + msg_clr_eos(); + msg_putchar('\n'); + } + out_flush(); // show one line at a time + if (got_int) + { + got_int = FALSE; + break; + } + } + + // we redraw the command below the lines that we have just listed + // This is a bit tricky, but it saves a lot of screen updating. + cmdline_row = msg_row; // will put it back later + } + + if (xp->xp_numfiles == -1) + FreeWild(num_files, files_found); + + return EXPAND_OK; +} + +/* + * Private gettail for showmatches() (and win_redr_status_matches()): + * Find tail of file name path, but ignore trailing "/". + */ + char_u * +sm_gettail(char_u *s) +{ + char_u *p; + char_u *t = s; + int had_sep = FALSE; + + for (p = s; *p != NUL; ) + { + if (vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && !rem_backslash(p) +#endif + ) + had_sep = TRUE; + else if (had_sep) + { + t = p; + had_sep = FALSE; + } + MB_PTR_ADV(p); + } + return t; +} + +/* + * Return TRUE if we only need to show the tail of completion matches. + * When not completing file names or there is a wildcard in the path FALSE is + * returned. + */ + static int +expand_showtail(expand_T *xp) +{ + char_u *s; + char_u *end; + + // When not completing file names a "/" may mean something different. + if (xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_DIRECTORIES) + return FALSE; + + end = gettail(xp->xp_pattern); + if (end == xp->xp_pattern) // there is no path separator + return FALSE; + + for (s = xp->xp_pattern; s < end; s++) + { + // Skip escaped wildcards. Only when the backslash is not a path + // separator, on DOS the '*' "path\*\file" must not be skipped. + if (rem_backslash(s)) + ++s; + else if (vim_strchr((char_u *)"*?[", *s) != NULL) + return FALSE; + } + return TRUE; +} + +/* + * Prepare a string for expansion. + * When expanding file names: The string will be used with expand_wildcards(). + * Copy "fname[len]" into allocated memory and add a '*' at the end. + * When expanding other names: The string will be used with regcomp(). Copy + * the name into allocated memory and prepend "^". + */ + char_u * +addstar( + char_u *fname, + int len, + int context) // EXPAND_FILES etc. +{ + char_u *retval; + int i, j; + int new_len; + char_u *tail; + int ends_in_star; + + if (context != EXPAND_FILES + && context != EXPAND_FILES_IN_PATH + && context != EXPAND_SHELLCMD + && context != EXPAND_DIRECTORIES) + { + // Matching will be done internally (on something other than files). + // So we convert the file-matching-type wildcards into our kind for + // use with vim_regcomp(). First work out how long it will be: + + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. + if (context == EXPAND_HELP + || context == EXPAND_COLORS + || context == EXPAND_COMPILER + || context == EXPAND_OWNSYNTAX + || context == EXPAND_FILETYPE + || context == EXPAND_PACKADD + || ((context == EXPAND_TAGS_LISTFILES + || context == EXPAND_TAGS) + && fname[0] == '/')) + retval = vim_strnsave(fname, len); + else + { + new_len = len + 2; // +2 for '^' at start, NUL at end + for (i = 0; i < len; i++) + { + if (fname[i] == '*' || fname[i] == '~') + new_len++; // '*' needs to be replaced by ".*" + // '~' needs to be replaced by "\~" + + // Buffer names are like file names. "." should be literal + if (context == EXPAND_BUFFERS && fname[i] == '.') + new_len++; // "." becomes "\." + + // Custom expansion takes care of special things, match + // backslashes literally (perhaps also for other types?) + if ((context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) && fname[i] == '\\') + new_len++; // '\' becomes "\\" + } + retval = alloc(new_len); + if (retval != NULL) + { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) + { + // Skip backslash. But why? At least keep it for custom + // expansion. + if (context != EXPAND_USER_DEFINED + && context != EXPAND_USER_LIST + && fname[i] == '\\' + && ++i == len) + break; + + switch (fname[i]) + { + case '*': retval[j++] = '.'; + break; + case '~': retval[j++] = '\\'; + break; + case '?': retval[j] = '.'; + continue; + case '.': if (context == EXPAND_BUFFERS) + retval[j++] = '\\'; + break; + case '\\': if (context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) + retval[j++] = '\\'; + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } + else + { + retval = alloc(len + 4); + if (retval != NULL) + { + vim_strncpy(retval, fname, len); + + // Don't add a star to *, ~, ~user, $var or `cmd`. + // * would become **, which walks the whole tree. + // ~ would be at the start of the file name, but not the tail. + // $ could be anywhere in the tail. + // ` could be anywhere in the file name. + // When the name ends in '$' don't add a star, remove the '$'. + tail = gettail(retval); + ends_in_star = (len > 0 && retval[len - 1] == '*'); +#ifndef BACKSLASH_IN_FILENAME + for (i = len - 2; i >= 0; --i) + { + if (retval[i] != '\\') + break; + ends_in_star = !ends_in_star; + } +#endif + if ((*retval != '~' || tail != retval) + && !ends_in_star + && vim_strchr(tail, '$') == NULL + && vim_strchr(retval, '`') == NULL) + retval[len++] = '*'; + else if (len > 0 && retval[len - 1] == '$') + --len; + retval[len] = NUL; + } + } + return retval; +} + +/* + * Must parse the command line so far to work out what context we are in. + * Completion can then be done based on that context. + * This routine sets the variables: + * xp->xp_pattern The start of the pattern to be expanded within + * the command line (ends at the cursor). + * xp->xp_context The type of thing to expand. Will be one of: + * + * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on + * the command line, like an unknown command. Caller + * should beep. + * EXPAND_NOTHING Unrecognised context for completion, use char like + * a normal char, rather than for completion. eg + * :s/^I/ + * EXPAND_COMMANDS Cursor is still touching the command, so complete + * it. + * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. + * EXPAND_FILES After command with EX_XFILE set, or after setting + * with P_EXPAND set. eg :e ^I, :w>>^I + * EXPAND_DIRECTORIES In some cases this is used instead of the latter + * when we know only directories are of interest. eg + * :set dir=^I + * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". + * EXPAND_SETTINGS Complete variable names. eg :set d^I + * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I + * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I + * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect + * EXPAND_HELP Complete tags from the file 'helpfile'/tags + * EXPAND_EVENTS Complete event names + * EXPAND_SYNTAX Complete :syntax command arguments + * EXPAND_HIGHLIGHT Complete highlight (syntax) group names + * EXPAND_AUGROUP Complete autocommand group names + * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I + * EXPAND_MAPPINGS Complete mapping and abbreviation names, + * eg :unmap a^I , :cunab x^I + * EXPAND_FUNCTIONS Complete internal or user defined function names, + * eg :call sub^I + * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I + * EXPAND_EXPRESSION Complete internal or user defined function/variable + * names in expressions, eg :while s^I + * EXPAND_ENV_VARS Complete environment variable names + * EXPAND_USER Complete user names + */ + static void +set_expand_context(expand_T *xp) +{ + cmdline_info_T *ccline = get_cmdline_info(); + + // only expansion for ':', '>' and '=' command-lines + if (ccline->cmdfirstc != ':' +#ifdef FEAT_EVAL + && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '=' + && !ccline->input_fn +#endif + ) + { + xp->xp_context = EXPAND_NOTHING; + return; + } + set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, TRUE); +} + + void +set_cmd_context( + expand_T *xp, + char_u *str, // start of command line + int len, // length of command line (excl. NUL) + int col, // position of cursor + int use_ccline UNUSED) // use ccline for info +{ +#ifdef FEAT_EVAL + cmdline_info_T *ccline = get_cmdline_info(); +#endif + int old_char = NUL; + char_u *nextcomm; + + // Avoid a UMR warning from Purify, only save the character if it has been + // written before. + if (col < len) + old_char = str[col]; + str[col] = NUL; + nextcomm = str; + +#ifdef FEAT_EVAL + if (use_ccline && ccline->cmdfirstc == '=') + { +# ifdef FEAT_CMDL_COMPL + // pass CMD_SIZE because there is no real command + set_context_for_expression(xp, str, CMD_SIZE); +# endif + } + else if (use_ccline && ccline->input_fn) + { + xp->xp_context = ccline->xp_context; + xp->xp_pattern = ccline->cmdbuff; +# if defined(FEAT_CMDL_COMPL) + xp->xp_arg = ccline->xp_arg; +# endif + } + else +#endif + while (nextcomm != NULL) + nextcomm = set_one_cmd_context(xp, nextcomm); + + // Store the string here so that call_user_expand_func() can get to them + // easily. + xp->xp_line = str; + xp->xp_col = col; + + str[col] = old_char; +} + +/* + * Expand the command line "str" from context "xp". + * "xp" must have been set by set_cmd_context(). + * xp->xp_pattern points into "str", to where the text that is to be expanded + * starts. + * Returns EXPAND_UNSUCCESSFUL when there is something illegal before the + * cursor. + * Returns EXPAND_NOTHING when there is nothing to expand, might insert the + * key that triggered expansion literally. + * Returns EXPAND_OK otherwise. + */ + int +expand_cmdline( + expand_T *xp, + char_u *str, // start of command line + int col, // position of cursor + int *matchcount, // return: nr of matches + char_u ***matches) // return: array of pointers to matches +{ + char_u *file_str = NULL; + int options = WILD_ADD_SLASH|WILD_SILENT; + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return EXPAND_UNSUCCESSFUL; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) + { + // Caller can use the character as a normal char instead + return EXPAND_NOTHING; + } + + // add star to file name, or convert to regexp if not exp. files. + xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + if (file_str == NULL) + return EXPAND_UNSUCCESSFUL; + + if (p_wic) + options += WILD_ICASE; + + // find all files that match the description + if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) + { + *matchcount = 0; + *matches = NULL; + } + vim_free(file_str); + + return EXPAND_OK; +} + +#ifdef FEAT_MULTI_LANG +/* + * Cleanup matches for help tags: + * Remove "@ab" if the top of 'helplang' is "ab" and the language of the first + * tag matches it. Otherwise remove "@en" if "en" is the only language. + */ + static void +cleanup_help_tags(int num_file, char_u **file) +{ + int i, j; + int len; + char_u buf[4]; + char_u *p = buf; + + if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) + { + *p++ = '@'; + *p++ = p_hlg[0]; + *p++ = p_hlg[1]; + } + *p = NUL; + + for (i = 0; i < num_file; ++i) + { + len = (int)STRLEN(file[i]) - 3; + if (len <= 0) + continue; + if (STRCMP(file[i] + len, "@en") == 0) + { + // Sorting on priority means the same item in another language may + // be anywhere. Search all items for a match up to the "@en". + for (j = 0; j < num_file; ++j) + if (j != i && (int)STRLEN(file[j]) == len + 3 + && STRNCMP(file[i], file[j], len + 1) == 0) + break; + if (j == num_file) + // item only exists with @en, remove it + file[i][len] = NUL; + } + } + + if (*buf != NUL) + for (i = 0; i < num_file; ++i) + { + len = (int)STRLEN(file[i]) - 3; + if (len <= 0) + continue; + if (STRCMP(file[i] + len, buf) == 0) + { + // remove the default language + file[i][len] = NUL; + } + } +} +#endif + +/* + * Do the expansion based on xp->xp_context and "pat". + */ + static int +ExpandFromContext( + expand_T *xp, + char_u *pat, + int *num_file, + char_u ***file, + int options) // WILD_ flags +{ +#ifdef FEAT_CMDL_COMPL + regmatch_T regmatch; +#endif + int ret; + int flags; + + flags = EW_DIR; // include directories + if (options & WILD_LIST_NOTFOUND) + flags |= EW_NOTFOUND; + if (options & WILD_ADD_SLASH) + flags |= EW_ADDSLASH; + if (options & WILD_KEEP_ALL) + flags |= EW_KEEPALL; + if (options & WILD_SILENT) + flags |= EW_SILENT; + if (options & WILD_ALLLINKS) + flags |= EW_ALLLINKS; + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES_IN_PATH) + { + // Expand file or directory names. + int free_pat = FALSE; + int i; + + // for ":set path=" and ":set tags=" halve backslashes for escaped + // space + if (xp->xp_backslash != XP_BS_NONE) + { + free_pat = TRUE; + pat = vim_strsave(pat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\') + { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') + STRMOVE(pat + i, pat + i + 3); + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + } + } + + if (xp->xp_context == EXPAND_FILES) + flags |= EW_FILE; + else if (xp->xp_context == EXPAND_FILES_IN_PATH) + flags |= (EW_FILE | EW_PATH); + else + flags = (flags | EW_DIR) & ~EW_FILE; + if (options & WILD_ICASE) + flags |= EW_ICASE; + + // Expand wildcards, supporting %:h and the like. + ret = expand_wildcards_eval(&pat, num_file, file, flags); + if (free_pat) + vim_free(pat); +#ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) + { + int i; + + for (i = 0; i < *num_file; ++i) + { + char_u *ptr = (*file)[i]; + + while (*ptr != NUL) + { + if (p_csl[0] == 's' && *ptr == '\\') + *ptr = '/'; + else if (p_csl[0] == 'b' && *ptr == '/') + *ptr = '\\'; + ptr += (*mb_ptr2len)(ptr); + } + } + } +#endif + return ret; + } + + *file = (char_u **)""; + *num_file = 0; + if (xp->xp_context == EXPAND_HELP) + { + // With an empty argument we would get all the help tags, which is + // very slow. Get matches for "help" instead. + if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat, + num_file, file, FALSE) == OK) + { +#ifdef FEAT_MULTI_LANG + cleanup_help_tags(*num_file, *file); +#endif + return OK; + } + return FAIL; + } + +#ifndef FEAT_CMDL_COMPL + return FAIL; +#else + if (xp->xp_context == EXPAND_SHELLCMD) + return expand_shellcmd(pat, num_file, file, flags); + if (xp->xp_context == EXPAND_OLD_SETTING) + return ExpandOldSetting(num_file, file); + if (xp->xp_context == EXPAND_BUFFERS) + return ExpandBufnames(pat, num_file, file, options); + if (xp->xp_context == EXPAND_TAGS + || xp->xp_context == EXPAND_TAGS_LISTFILES) + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + if (xp->xp_context == EXPAND_COLORS) + { + char *directories[] = {"colors", NULL}; + return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, + directories); + } + if (xp->xp_context == EXPAND_COMPILER) + { + char *directories[] = {"compiler", NULL}; + return ExpandRTDir(pat, 0, num_file, file, directories); + } + if (xp->xp_context == EXPAND_OWNSYNTAX) + { + char *directories[] = {"syntax", NULL}; + return ExpandRTDir(pat, 0, num_file, file, directories); + } + if (xp->xp_context == EXPAND_FILETYPE) + { + char *directories[] = {"syntax", "indent", "ftplugin", NULL}; + return ExpandRTDir(pat, 0, num_file, file, directories); + } +# if defined(FEAT_EVAL) + if (xp->xp_context == EXPAND_USER_LIST) + return ExpandUserList(xp, num_file, file); +# endif + if (xp->xp_context == EXPAND_PACKADD) + return ExpandPackAddDir(pat, num_file, file); + + regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + return FAIL; + + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + + if (xp->xp_context == EXPAND_SETTINGS + || xp->xp_context == EXPAND_BOOL_SETTINGS) + ret = ExpandSettings(xp, ®match, num_file, file); + else if (xp->xp_context == EXPAND_MAPPINGS) + ret = ExpandMappings(®match, num_file, file); +# if defined(FEAT_EVAL) + else if (xp->xp_context == EXPAND_USER_DEFINED) + ret = ExpandUserDefined(xp, ®match, num_file, file); +# endif + else + { + static struct expgen + { + int context; + char_u *((*func)(expand_T *, int)); + int ic; + int escaped; + } tab[] = + { + {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, + {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, + {EXPAND_MAPCLEAR, get_mapclear_arg, TRUE, TRUE}, + {EXPAND_MESSAGES, get_messages_arg, TRUE, TRUE}, + {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, + {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, + {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, + {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, + {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, + {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, +# ifdef FEAT_EVAL + {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, + {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, + {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, + {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, +# endif +# ifdef FEAT_MENU + {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, + {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, +# endif +# ifdef FEAT_SYN_HL + {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, +# endif +# ifdef FEAT_PROFILE + {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, +# endif + {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, + {EXPAND_EVENTS, get_event_name, TRUE, TRUE}, + {EXPAND_AUGROUP, get_augroup_name, TRUE, TRUE}, +# ifdef FEAT_CSCOPE + {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, +# endif +# ifdef FEAT_SIGNS + {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, +# endif +# ifdef FEAT_PROFILE + {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, +# endif +# if defined(HAVE_LOCALE_H) || defined(X_LOCALE) + {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, + {EXPAND_LOCALES, get_locales, TRUE, FALSE}, +# endif + {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, + {EXPAND_USER, get_users, TRUE, FALSE}, + {EXPAND_ARGLIST, get_arglist_name, TRUE, FALSE}, + }; + int i; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + ret = FAIL; + for (i = 0; i < (int)(sizeof(tab) / sizeof(struct expgen)); ++i) + if (xp->xp_context == tab[i].context) + { + if (tab[i].ic) + regmatch.rm_ic = TRUE; + ret = ExpandGeneric(xp, ®match, num_file, file, + tab[i].func, tab[i].escaped); + break; + } + } + + vim_regfree(regmatch.regprog); + + return ret; +#endif // FEAT_CMDL_COMPL +} + +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) +/* + * Expand a list of names. + * + * Generic function for command line completion. It calls a function to + * obtain strings, one by one. The strings are matched against a regexp + * program. Matching strings are copied into an array, which is returned. + * + * Returns OK when no problems encountered, FAIL for error (out of memory). + */ + int +ExpandGeneric( + expand_T *xp, + regmatch_T *regmatch, + int *num_file, + char_u ***file, + char_u *((*func)(expand_T *, int)), + // returns a string from the list + int escaped) +{ + int i; + int count = 0; + int round; + char_u *str; + + // do this loop twice: + // round == 0: count the number of matching names + // round == 1: copy the matching names into allocated memory + for (round = 0; round <= 1; ++round) + { + for (i = 0; ; ++i) + { + str = (*func)(xp, i); + if (str == NULL) // end of list + break; + if (*str == NUL) // skip empty strings + continue; + + if (vim_regexec(regmatch, str, (colnr_T)0)) + { + if (round) + { + if (escaped) + str = vim_strsave_escaped(str, (char_u *)" \t\\."); + else + str = vim_strsave(str); + (*file)[count] = str; +# ifdef FEAT_MENU + if (func == get_menu_names && str != NULL) + { + // test for separator added by get_menu_names() + str += STRLEN(str) - 1; + if (*str == '\001') + *str = '.'; + } +# endif + } + ++count; + } + } + if (round == 0) + { + if (count == 0) + return OK; + *num_file = count; + *file = ALLOC_MULT(char_u *, count); + if (*file == NULL) + { + *file = (char_u **)""; + return FAIL; + } + count = 0; + } + } + + // Sort the results. Keep menu's in the specified order. + if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) + { + if (xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC) + // functions should be sorted to the end. + qsort((void *)*file, (size_t)*num_file, sizeof(char_u *), + sort_func_compare); + else + sort_strings(*file, *num_file); + } + +# ifdef FEAT_CMDL_COMPL + // Reset the variables used for special highlight names expansion, so that + // they don't show up when getting normal highlight names by ID. + reset_expand_highlight(); +# endif + + return OK; +} + +/* + * Complete a shell command. + * Returns FAIL or OK; + */ + static int +expand_shellcmd( + char_u *filepat, // pattern to match with command names + int *num_file, // return: number of matches + char_u ***file, // return: array with matches + int flagsarg) // EW_ flags +{ + char_u *pat; + int i; + char_u *path = NULL; + int mustfree = FALSE; + garray_T ga; + char_u *buf = alloc(MAXPATHL); + size_t l; + char_u *s, *e; + int flags = flagsarg; + int ret; + int did_curdir = FALSE; + hashtab_T found_ht; + hashitem_T *hi; + hash_T hash; + + if (buf == NULL) + return FAIL; + + // for ":set path=" and ":set tags=" halve backslashes for escaped + // space + pat = vim_strsave(filepat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\' && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + + flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; + + if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2])))) + path = (char_u *)"."; + else + { + // For an absolute name we don't use $PATH. + if (!mch_isFullName(pat)) + path = vim_getenv((char_u *)"PATH", &mustfree); + if (path == NULL) + path = (char_u *)""; + } + + // Go over all directories in $PATH. Expand matches in that directory and + // collect them in "ga". When "." is not in $PATH also expand for the + // current directory, to find "subdir/cmd". + ga_init2(&ga, (int)sizeof(char *), 10); + hash_init(&found_ht); + for (s = path; ; s = e) + { +# if defined(MSWIN) + e = vim_strchr(s, ';'); +# else + e = vim_strchr(s, ':'); +# endif + if (e == NULL) + e = s + STRLEN(s); + + if (*s == NUL) + { + if (did_curdir) + break; + // Find directories in the current directory, path is empty. + did_curdir = TRUE; + flags |= EW_DIR; + } + else if (STRNCMP(s, ".", (int)(e - s)) == 0) + { + did_curdir = TRUE; + flags |= EW_DIR; + } + else + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; + + l = e - s; + if (l > MAXPATHL - 5) + break; + vim_strncpy(buf, s, l); + add_pathsep(buf); + l = STRLEN(buf); + vim_strncpy(buf + l, pat, MAXPATHL - 1 - l); + + // Expand matches in one directory of $PATH. + ret = expand_wildcards(1, &buf, num_file, file, flags); + if (ret == OK) + { + if (ga_grow(&ga, *num_file) == FAIL) + FreeWild(*num_file, *file); + else + { + for (i = 0; i < *num_file; ++i) + { + char_u *name = (*file)[i]; + + if (STRLEN(name) > l) + { + // Check if this name was already found. + hash = hash_hash(name + l); + hi = hash_lookup(&found_ht, name + l, hash); + if (HASHITEM_EMPTY(hi)) + { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char_u **)ga.ga_data)[ga.ga_len++] = name; + hash_add_item(&found_ht, hi, name, hash); + name = NULL; + } + } + vim_free(name); + } + vim_free(*file); + } + } + if (*e != NUL) + ++e; + } + *file = ga.ga_data; + *num_file = ga.ga_len; + + vim_free(buf); + vim_free(pat); + if (mustfree) + vim_free(path); + hash_clear(&found_ht); + return OK; +} + +# if defined(FEAT_EVAL) +/* + * Call "user_expand_func()" to invoke a user defined Vim script function and + * return the result (either a string or a List). + */ + static void * +call_user_expand_func( + void *(*user_expand_func)(char_u *, int, typval_T *), + expand_T *xp, + int *num_file, + char_u ***file) +{ + cmdline_info_T *ccline = get_cmdline_info(); + int keep = 0; + typval_T args[4]; + sctx_T save_current_sctx = current_sctx; + char_u *pat = NULL; + void *ret; + + if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) + return NULL; + *num_file = 0; + *file = NULL; + + if (ccline->cmdbuff != NULL) + { + keep = ccline->cmdbuff[ccline->cmdlen]; + ccline->cmdbuff[ccline->cmdlen] = 0; + } + + pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); + + args[0].v_type = VAR_STRING; + args[0].vval.v_string = pat; + args[1].v_type = VAR_STRING; + args[1].vval.v_string = xp->xp_line; + args[2].v_type = VAR_NUMBER; + args[2].vval.v_number = xp->xp_col; + args[3].v_type = VAR_UNKNOWN; + + current_sctx = xp->xp_script_ctx; + + ret = user_expand_func(xp->xp_arg, 3, args); + + current_sctx = save_current_sctx; + if (ccline->cmdbuff != NULL) + ccline->cmdbuff[ccline->cmdlen] = keep; + + vim_free(pat); + return ret; +} + +/* + * Expand names with a function defined by the user. + */ + static int +ExpandUserDefined( + expand_T *xp, + regmatch_T *regmatch, + int *num_file, + char_u ***file) +{ + char_u *retstr; + char_u *s; + char_u *e; + int keep; + garray_T ga; + int skip; + + retstr = call_user_expand_func(call_func_retstr, xp, num_file, file); + if (retstr == NULL) + return FAIL; + + ga_init2(&ga, (int)sizeof(char *), 3); + for (s = retstr; *s != NUL; s = e) + { + e = vim_strchr(s, '\n'); + if (e == NULL) + e = s + STRLEN(s); + keep = *e; + *e = NUL; + + skip = xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0; + *e = keep; + + if (!skip) + { + if (ga_grow(&ga, 1) == FAIL) + break; + ((char_u **)ga.ga_data)[ga.ga_len] = vim_strnsave(s, (int)(e - s)); + ++ga.ga_len; + } + + if (*e != NUL) + ++e; + } + vim_free(retstr); + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/* + * Expand names with a list returned by a function defined by the user. + */ + static int +ExpandUserList( + expand_T *xp, + int *num_file, + char_u ***file) +{ + list_T *retlist; + listitem_T *li; + garray_T ga; + + retlist = call_user_expand_func(call_func_retlist, xp, num_file, file); + if (retlist == NULL) + return FAIL; + + ga_init2(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + for (li = retlist->lv_first; li != NULL; li = li->li_next) + { + if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) + continue; // Skip non-string items and empty strings + + if (ga_grow(&ga, 1) == FAIL) + break; + + ((char_u **)ga.ga_data)[ga.ga_len] = + vim_strsave(li->li_tv.vval.v_string); + ++ga.ga_len; + } + list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} +# endif + +/* + * Expand color scheme, compiler or filetype names. + * Search from 'runtimepath': + * 'runtimepath'/{dirnames}/{pat}.vim + * When "flags" has DIP_START: search also from 'start' of 'packpath': + * 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim + * When "flags" has DIP_OPT: search also from 'opt' of 'packpath': + * 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim + * "dirnames" is an array with one or more directory names. + */ + static int +ExpandRTDir( + char_u *pat, + int flags, + int *num_file, + char_u ***file, + char *dirnames[]) +{ + char_u *s; + char_u *e; + char_u *match; + garray_T ga; + int i; + int pat_len; + + *num_file = 0; + *file = NULL; + pat_len = (int)STRLEN(pat); + ga_init2(&ga, (int)sizeof(char *), 10); + + for (i = 0; dirnames[i] != NULL; ++i) + { + s = alloc(STRLEN(dirnames[i]) + pat_len + 7); + if (s == NULL) + { + ga_clear_strings(&ga); + return FAIL; + } + sprintf((char *)s, "%s/%s*.vim", dirnames[i], pat); + globpath(p_rtp, s, &ga, 0); + vim_free(s); + } + + if (flags & DIP_START) { + for (i = 0; dirnames[i] != NULL; ++i) + { + s = alloc(STRLEN(dirnames[i]) + pat_len + 22); + if (s == NULL) + { + ga_clear_strings(&ga); + return FAIL; + } + sprintf((char *)s, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); + globpath(p_pp, s, &ga, 0); + vim_free(s); + } + } + + if (flags & DIP_OPT) { + for (i = 0; dirnames[i] != NULL; ++i) + { + s = alloc(STRLEN(dirnames[i]) + pat_len + 20); + if (s == NULL) + { + ga_clear_strings(&ga); + return FAIL; + } + sprintf((char *)s, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); + globpath(p_pp, s, &ga, 0); + vim_free(s); + } + } + + for (i = 0; i < ga.ga_len; ++i) + { + match = ((char_u **)ga.ga_data)[i]; + s = match; + e = s + STRLEN(s); + if (e - 4 > s && STRNICMP(e - 4, ".vim", 4) == 0) + { + e -= 4; + for (s = e; s > match; MB_PTR_BACK(match, s)) + if (s < match || vim_ispathsep(*s)) + break; + ++s; + *e = NUL; + mch_memmove(match, s, e - s + 1); + } + } + + if (ga.ga_len == 0) + return FAIL; + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + remove_duplicates(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/* + * Expand loadplugin names: + * 'packpath'/pack/ * /opt/{pat} + */ + static int +ExpandPackAddDir( + char_u *pat, + int *num_file, + char_u ***file) +{ + char_u *s; + char_u *e; + char_u *match; + garray_T ga; + int i; + int pat_len; + + *num_file = 0; + *file = NULL; + pat_len = (int)STRLEN(pat); + ga_init2(&ga, (int)sizeof(char *), 10); + + s = alloc(pat_len + 26); + if (s == NULL) + { + ga_clear_strings(&ga); + return FAIL; + } + sprintf((char *)s, "pack/*/opt/%s*", pat); + globpath(p_pp, s, &ga, 0); + vim_free(s); + + for (i = 0; i < ga.ga_len; ++i) + { + match = ((char_u **)ga.ga_data)[i]; + s = gettail(match); + e = s + STRLEN(s); + mch_memmove(match, s, e - s + 1); + } + + if (ga.ga_len == 0) + return FAIL; + + // Sort and remove duplicates which can happen when specifying multiple + // directories in dirnames. + remove_duplicates(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} +#endif + +#if defined(FEAT_CMDL_COMPL) || defined(FEAT_EVAL) || defined(PROTO) +/* + * Expand "file" for all comma-separated directories in "path". + * Adds the matches to "ga". Caller must init "ga". + */ + void +globpath( + char_u *path, + char_u *file, + garray_T *ga, + int expand_options) +{ + expand_T xpc; + char_u *buf; + int i; + int num_p; + char_u **p; + + buf = alloc(MAXPATHL); + if (buf == NULL) + return; + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + + // Loop over all entries in {path}. + while (*path != NUL) + { + // Copy one item of the path to buf[] and concatenate the file name. + copy_option_part(&path, buf, MAXPATHL, ","); + if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) + { +# if defined(MSWIN) + // Using the platform's path separator (\) makes vim incorrectly + // treat it as an escape character, use '/' instead. + if (*buf != NUL && !after_pathsep(buf, buf + STRLEN(buf))) + STRCAT(buf, "/"); +# else + add_pathsep(buf); +# endif + STRCAT(buf, file); + if (ExpandFromContext(&xpc, buf, &num_p, &p, + WILD_SILENT|expand_options) != FAIL && num_p > 0) + { + ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT|expand_options); + + if (ga_grow(ga, num_p) == OK) + { + for (i = 0; i < num_p; ++i) + { + ((char_u **)ga->ga_data)[ga->ga_len] = + vim_strnsave(p[i], (int)STRLEN(p[i])); + ++ga->ga_len; + } + } + + FreeWild(num_p, p); + } + } + } + + vim_free(buf); +} +#endif + +#if defined(FEAT_CMDL_COMPL) | defined(PROTO) +/* + * "getcompletion()" function + */ + void +f_getcompletion(typval_T *argvars, typval_T *rettv) +{ + char_u *pat; + expand_T xpc; + int filtered = FALSE; + int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH + | WILD_NO_BEEP; + + if (argvars[2].v_type != VAR_UNKNOWN) + filtered = tv_get_number_chk(&argvars[2], NULL); + + if (p_wic) + options |= WILD_ICASE; + + // For filtered results, 'wildignore' is used + if (!filtered) + options |= WILD_KEEP_ALL; + + ExpandInit(&xpc); + xpc.xp_pattern = tv_get_string(&argvars[0]); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_context = cmdcomplete_str_to_type(tv_get_string(&argvars[1])); + if (xpc.xp_context == EXPAND_NOTHING) + { + if (argvars[1].v_type == VAR_STRING) + semsg(_(e_invarg2), argvars[1].vval.v_string); + else + emsg(_(e_invarg)); + return; + } + +# if defined(FEAT_MENU) + if (xpc.xp_context == EXPAND_MENUS) + { + set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, FALSE); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif +# ifdef FEAT_CSCOPE + if (xpc.xp_context == EXPAND_CSCOPE) + { + set_context_in_cscope_cmd(&xpc, xpc.xp_pattern, CMD_cscope); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif +# ifdef FEAT_SIGNS + if (xpc.xp_context == EXPAND_SIGN) + { + set_context_in_sign_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif + + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + if ((rettv_list_alloc(rettv) != FAIL) && (pat != NULL)) + { + int i; + + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + + for (i = 0; i < xpc.xp_numfiles; i++) + list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + } + vim_free(pat); + ExpandCleanup(&xpc); +} +#endif // FEAT_CMDL_COMPL diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -135,9 +135,6 @@ static void f_getchar(typval_T *argvars, static void f_getcharmod(typval_T *argvars, typval_T *rettv); static void f_getcharsearch(typval_T *argvars, typval_T *rettv); static void f_getcmdline(typval_T *argvars, typval_T *rettv); -#if defined(FEAT_CMDL_COMPL) -static void f_getcompletion(typval_T *argvars, typval_T *rettv); -#endif static void f_getcmdpos(typval_T *argvars, typval_T *rettv); static void f_getcmdtype(typval_T *argvars, typval_T *rettv); static void f_getcmdwintype(typval_T *argvars, typval_T *rettv); @@ -4701,79 +4698,6 @@ f_getcmdwintype(typval_T *argvars UNUSED #endif } -#if defined(FEAT_CMDL_COMPL) -/* - * "getcompletion()" function - */ - static void -f_getcompletion(typval_T *argvars, typval_T *rettv) -{ - char_u *pat; - expand_T xpc; - int filtered = FALSE; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP; - - if (argvars[2].v_type != VAR_UNKNOWN) - filtered = tv_get_number_chk(&argvars[2], NULL); - - if (p_wic) - options |= WILD_ICASE; - - /* For filtered results, 'wildignore' is used */ - if (!filtered) - options |= WILD_KEEP_ALL; - - ExpandInit(&xpc); - xpc.xp_pattern = tv_get_string(&argvars[0]); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type(tv_get_string(&argvars[1])); - if (xpc.xp_context == EXPAND_NOTHING) - { - if (argvars[1].v_type == VAR_STRING) - semsg(_(e_invarg2), argvars[1].vval.v_string); - else - emsg(_(e_invarg)); - return; - } - -# if defined(FEAT_MENU) - if (xpc.xp_context == EXPAND_MENUS) - { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, FALSE); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); - } -# endif -#ifdef FEAT_CSCOPE - if (xpc.xp_context == EXPAND_CSCOPE) - { - set_context_in_cscope_cmd(&xpc, xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); - } -#endif -#ifdef FEAT_SIGNS - if (xpc.xp_context == EXPAND_SIGN) - { - set_context_in_sign_cmd(&xpc, xpc.xp_pattern); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); - } -#endif - - pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - if ((rettv_list_alloc(rettv) != FAIL) && (pat != NULL)) - { - int i; - - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - - for (i = 0; i < xpc.xp_numfiles; i++) - list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); - } - vim_free(pat); - ExpandCleanup(&xpc); -} -#endif - /* * "getcwd()" function * diff --git a/src/ex_getln.c b/src/ex_getln.c --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -17,39 +17,10 @@ # define MAX(x,y) ((x) > (y) ? (x) : (y)) #endif -/* - * Variables shared between getcmdline(), redrawcmdline() and others. - * These need to be saved when using CTRL-R |, that's why they are in a - * structure. - */ -struct cmdline_info -{ - char_u *cmdbuff; /* pointer to command line buffer */ - int cmdbufflen; /* length of cmdbuff */ - int cmdlen; /* number of chars in command line */ - int cmdpos; /* current cursor position */ - int cmdspos; /* cursor column on screen */ - int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ - int cmdindent; /* number of spaces before cmdline */ - char_u *cmdprompt; /* message in front of cmdline */ - int cmdattr; /* attributes for prompt */ - int overstrike; /* Typing mode on the command line. Shared by - getcmdline() and put_on_cmdline(). */ - expand_T *xpc; /* struct being used for expansion, xp_pattern - may point into cmdbuff */ - int xp_context; /* type of expansion */ -# ifdef FEAT_EVAL - char_u *xp_arg; /* user-defined expansion arg */ - int input_fn; /* when TRUE Invoked for input() function */ -# endif -}; - // The current cmdline_info. It is initialized in getcmdline() and after that // used by other functions. When invoking getcmdline() recursively it needs // to be saved with save_cmdline() and restored with restore_cmdline(). -static struct cmdline_info ccline; - -static int cmd_showtail; /* Only show path tail in lists ? */ +static cmdline_info_T ccline; #ifdef FEAT_EVAL static int new_cmdpos; /* position set by set_cmdline_pos() */ @@ -69,41 +40,20 @@ static void set_cmdspos(void); static void set_cmdspos_cursor(void); static void correct_cmdspos(int idx, int cells); static void alloc_cmdbuff(int len); -static int realloc_cmdbuff(int len); static void draw_cmdline(int start, int len); -static void save_cmdline(struct cmdline_info *ccp); -static void restore_cmdline(struct cmdline_info *ccp); +static void save_cmdline(cmdline_info_T *ccp); +static void restore_cmdline(cmdline_info_T *ccp); static int cmdline_paste(int regname, int literally, int remcr); #ifdef FEAT_WILDMENU static void cmdline_del(int from); #endif static void redrawcmdprompt(void); -static void cursorcmd(void); static int ccheck_abbr(int); -static int nextwild(expand_T *xp, int type, int options, int escape); -static void escape_fname(char_u **pp); -static int showmatches(expand_T *xp, int wildmenu); -static void set_expand_context(expand_T *xp); -static int ExpandFromContext(expand_T *xp, char_u *, int *, char_u ***, int); -static int expand_showtail(expand_T *xp); -#ifdef FEAT_CMDL_COMPL -static int expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int flagsarg); -static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char *dirname[]); -static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file); -# if defined(FEAT_EVAL) -static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file); -static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file); -# endif -#endif #ifdef FEAT_CMDWIN static int open_cmdwin(void); #endif -#if defined(FEAT_CMDL_COMPL) || defined(PROTO) -static int sort_func_compare(const void *s1, const void *s2); -#endif - static void trigger_cmd_autocmd(int typechar, int evt) @@ -789,7 +739,7 @@ cmdline_has_arabic(int start, int len) void cmdline_init(void) { - vim_memset(&ccline, 0, sizeof(struct cmdline_info)); + vim_memset(&ccline, 0, sizeof(cmdline_info_T)); } /* @@ -855,7 +805,7 @@ getcmdline_int( #endif expand_T xpc; long *b_im_ptr = NULL; - struct cmdline_info save_ccline; + cmdline_info_T save_ccline; int did_save_ccline = FALSE; int cmdline_type; @@ -867,7 +817,7 @@ getcmdline_int( did_save_ccline = TRUE; } if (init_ccline) - vim_memset(&ccline, 0, sizeof(struct cmdline_info)); + vim_memset(&ccline, 0, sizeof(cmdline_info_T)); #ifdef FEAT_EVAL if (firstc == -1) @@ -2474,7 +2424,7 @@ getcmdline_prompt( char_u *xp_arg) /* user-defined expansion argument */ { char_u *s; - struct cmdline_info save_ccline; + cmdline_info_T save_ccline; int did_save_ccline = FALSE; int msg_col_save = msg_col; int msg_silent_save = msg_silent; @@ -2486,7 +2436,7 @@ getcmdline_prompt( did_save_ccline = TRUE; } - vim_memset(&ccline, 0, sizeof(struct cmdline_info)); + vim_memset(&ccline, 0, sizeof(cmdline_info_T)); ccline.cmdprompt = prompt; ccline.cmdattr = attr; # ifdef FEAT_EVAL @@ -3099,7 +3049,7 @@ alloc_cmdbuff(int len) * Re-allocate the command line to length len + something extra. * return FAIL for failure, OK otherwise */ - static int + int realloc_cmdbuff(int len) { char_u *p; @@ -3460,8 +3410,8 @@ put_on_cmdline(char_u *str, int len, int return retval; } -static struct cmdline_info prev_ccline; -static int prev_ccline_used = FALSE; +static cmdline_info_T prev_ccline; +static int prev_ccline_used = FALSE; /* * Save ccline, because obtaining the "=" register may execute "normal :cmd" @@ -3469,11 +3419,11 @@ static int prev_ccline_used = FALSE * available globally in prev_ccline. */ static void -save_cmdline(struct cmdline_info *ccp) +save_cmdline(cmdline_info_T *ccp) { if (!prev_ccline_used) { - vim_memset(&prev_ccline, 0, sizeof(struct cmdline_info)); + vim_memset(&prev_ccline, 0, sizeof(cmdline_info_T)); prev_ccline_used = TRUE; } *ccp = prev_ccline; @@ -3485,7 +3435,7 @@ save_cmdline(struct cmdline_info *ccp) * Restore ccline after it has been saved with save_cmdline(). */ static void -restore_cmdline(struct cmdline_info *ccp) +restore_cmdline(cmdline_info_T *ccp) { ccline = prev_ccline; prev_ccline = *ccp; @@ -3723,7 +3673,7 @@ compute_cmdrow(void) + lastwin->w_status_height; } - static void + void cursorcmd(void) { if (cmd_silent) @@ -3801,522 +3751,6 @@ ccheck_abbr(int c) return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos); } -#if defined(FEAT_CMDL_COMPL) || defined(PROTO) - static int -sort_func_compare(const void *s1, const void *s2) -{ - char_u *p1 = *(char_u **)s1; - char_u *p2 = *(char_u **)s2; - - if (*p1 != '<' && *p2 == '<') return -1; - if (*p1 == '<' && *p2 != '<') return 1; - return STRCMP(p1, p2); -} -#endif - -/* - * Return FAIL if this is not an appropriate context in which to do - * completion of anything, return OK if it is (even if there are no matches). - * For the caller, this means that the character is just passed through like a - * normal character (instead of being expanded). This allows :s/^I^D etc. - */ - static int -nextwild( - expand_T *xp, - int type, - int options, /* extra options for ExpandOne() */ - int escape) /* if TRUE, escape the returned matches */ -{ - int i, j; - char_u *p1; - char_u *p2; - int difflen; - int v; - - if (xp->xp_numfiles == -1) - { - set_expand_context(xp); - cmd_showtail = expand_showtail(xp); - } - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) - { - beep_flush(); - return OK; /* Something illegal on command line */ - } - if (xp->xp_context == EXPAND_NOTHING) - { - /* Caller can use the character as a normal char instead */ - return FAIL; - } - - msg_puts("..."); /* show that we are busy */ - out_flush(); - - i = (int)(xp->xp_pattern - ccline.cmdbuff); - xp->xp_pattern_len = ccline.cmdpos - i; - - if (type == WILD_NEXT || type == WILD_PREV) - { - /* - * Get next/previous match for a previous expanded pattern. - */ - p2 = ExpandOne(xp, NULL, NULL, 0, type); - } - else - { - /* - * Translate string into pattern and expand it. - */ - if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, - xp->xp_context)) == NULL) - p2 = NULL; - else - { - int use_options = options | - WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; - 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); - vim_free(p1); - /* longest match: make sure it is not shorter, happens with :help */ - if (p2 != NULL && type == WILD_LONGEST) - { - for (j = 0; j < xp->xp_pattern_len; ++j) - if (ccline.cmdbuff[i + j] == '*' - || ccline.cmdbuff[i + j] == '?') - break; - if ((int)STRLEN(p2) < j) - VIM_CLEAR(p2); - } - } - } - - if (p2 != NULL && !got_int) - { - difflen = (int)STRLEN(p2) - xp->xp_pattern_len; - if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) - { - v = realloc_cmdbuff(ccline.cmdlen + difflen + 4); - xp->xp_pattern = ccline.cmdbuff + i; - } - else - v = OK; - if (v == OK) - { - mch_memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], - &ccline.cmdbuff[ccline.cmdpos], - (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); - mch_memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); - ccline.cmdlen += difflen; - ccline.cmdpos += difflen; - } - } - vim_free(p2); - - redrawcmd(); - cursorcmd(); - - /* When expanding a ":map" command and no matches are found, assume that - * the key is supposed to be inserted literally */ - if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) - return FAIL; - - if (xp->xp_numfiles <= 0 && p2 == NULL) - beep_flush(); - else if (xp->xp_numfiles == 1) - /* free expanded pattern */ - (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); - - return OK; -} - -/* - * Do wildcard expansion on the string 'str'. - * Chars that should not be expanded must be preceded with a backslash. - * Return a pointer to allocated memory containing the new string. - * Return NULL for failure. - * - * "orig" is the originally expanded string, copied to allocated memory. It - * should either be kept in orig_save or freed. When "mode" is WILD_NEXT or - * WILD_PREV "orig" should be NULL. - * - * Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" - * is WILD_EXPAND_FREE or WILD_ALL. - * - * mode = WILD_FREE: just free previously expanded matches - * mode = WILD_EXPAND_FREE: normal expansion, do not keep matches - * mode = WILD_EXPAND_KEEP: normal expansion, keep matches - * mode = WILD_NEXT: use next match in multiple match, wrap to first - * mode = WILD_PREV: use previous match in multiple match, wrap to first - * mode = WILD_ALL: return all matches concatenated - * mode = WILD_LONGEST: return longest matched part - * mode = WILD_ALL_KEEP: get all matches, keep matches - * - * options = WILD_LIST_NOTFOUND: list entries without a match - * options = WILD_HOME_REPLACE: do home_replace() for buffer names - * options = WILD_USE_NL: Use '\n' for WILD_ALL - * options = WILD_NO_BEEP: Don't beep for multiple matches - * options = WILD_ADD_SLASH: add a slash after directory names - * options = WILD_KEEP_ALL: don't remove 'wildignore' entries - * options = WILD_SILENT: don't print warning messages - * options = WILD_ESCAPE: put backslash before special chars - * options = WILD_ICASE: ignore case for files - * - * The variables xp->xp_context and xp->xp_backslash must have been set! - */ - char_u * -ExpandOne( - expand_T *xp, - char_u *str, - char_u *orig, /* allocated copy of original of expanded string */ - int options, - int mode) -{ - char_u *ss = NULL; - static int findex; - static char_u *orig_save = NULL; /* kept value of orig */ - int orig_saved = FALSE; - int i; - long_u len; - int non_suf_match; /* number without matching suffix */ - - /* - * first handle the case of using an old match - */ - if (mode == WILD_NEXT || mode == WILD_PREV) - { - if (xp->xp_numfiles > 0) - { - if (mode == WILD_PREV) - { - if (findex == -1) - findex = xp->xp_numfiles; - --findex; - } - else /* mode == WILD_NEXT */ - ++findex; - - /* - * When wrapping around, return the original string, set findex to - * -1. - */ - if (findex < 0) - { - if (orig_save == NULL) - findex = xp->xp_numfiles - 1; - else - findex = -1; - } - if (findex >= xp->xp_numfiles) - { - if (orig_save == NULL) - findex = 0; - else - findex = -1; - } -#ifdef FEAT_WILDMENU - if (p_wmnu) - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, - findex, cmd_showtail); -#endif - if (findex == -1) - return vim_strsave(orig_save); - return vim_strsave(xp->xp_files[findex]); - } - else - return NULL; - } - - /* free old names */ - if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) - { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - VIM_CLEAR(orig_save); - } - findex = 0; - - if (mode == WILD_FREE) /* only release file name */ - return NULL; - - if (xp->xp_numfiles == -1) - { - vim_free(orig_save); - orig_save = orig; - orig_saved = TRUE; - - /* - * Do the expansion. - */ - if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, - options) == FAIL) - { -#ifdef FNAME_ILLEGAL - /* Illegal file name has been silently skipped. But when there - * are wildcards, the real problem is that there was no match, - * causing the pattern to be added, which has illegal characters. - */ - if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) - semsg(_(e_nomatch2), str); -#endif - } - else if (xp->xp_numfiles == 0) - { - if (!(options & WILD_SILENT)) - semsg(_(e_nomatch2), str); - } - else - { - /* Escape the matches for use on the command line. */ - ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); - - /* - * Check for matching suffixes in file names. - */ - if (mode != WILD_ALL && mode != WILD_ALL_KEEP - && mode != WILD_LONGEST) - { - if (xp->xp_numfiles) - non_suf_match = xp->xp_numfiles; - else - non_suf_match = 1; - if ((xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES) - && xp->xp_numfiles > 1) - { - /* - * More than one match; check suffix. - * The files will have been sorted on matching suffix in - * expand_wildcards, only need to check the first two. - */ - non_suf_match = 0; - for (i = 0; i < 2; ++i) - if (match_suffix(xp->xp_files[i])) - ++non_suf_match; - } - if (non_suf_match != 1) - { - /* Can we ever get here unless it's while expanding - * interactively? If not, we can get rid of this all - * together. Don't really want to wait for this message - * (and possibly have to hit return to continue!). - */ - if (!(options & WILD_SILENT)) - emsg(_(e_toomany)); - else if (!(options & WILD_NO_BEEP)) - beep_flush(); - } - if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) - ss = vim_strsave(xp->xp_files[0]); - } - } - } - - /* Find longest common part */ - if (mode == WILD_LONGEST && xp->xp_numfiles > 0) - { - int mb_len = 1; - int c0, ci; - - for (len = 0; xp->xp_files[0][len]; len += mb_len) - { - if (has_mbyte) - { - mb_len = (*mb_ptr2len)(&xp->xp_files[0][len]); - c0 =(* mb_ptr2char)(&xp->xp_files[0][len]); - } - else - c0 = xp->xp_files[0][len]; - for (i = 1; i < xp->xp_numfiles; ++i) - { - if (has_mbyte) - ci =(* mb_ptr2char)(&xp->xp_files[i][len]); - else - ci = xp->xp_files[i][len]; - if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) - { - if (MB_TOLOWER(c0) != MB_TOLOWER(ci)) - break; - } - else if (c0 != ci) - break; - } - if (i < xp->xp_numfiles) - { - if (!(options & WILD_NO_BEEP)) - vim_beep(BO_WILD); - break; - } - } - - ss = alloc(len + 1); - if (ss) - vim_strncpy(ss, xp->xp_files[0], (size_t)len); - findex = -1; /* next p_wc gets first one */ - } - - /* Concatenate all matching names */ - if (mode == WILD_ALL && xp->xp_numfiles > 0) - { - len = 0; - for (i = 0; i < xp->xp_numfiles; ++i) - len += (long_u)STRLEN(xp->xp_files[i]) + 1; - ss = alloc(len); - if (ss != NULL) - { - *ss = NUL; - for (i = 0; i < xp->xp_numfiles; ++i) - { - STRCAT(ss, xp->xp_files[i]); - if (i != xp->xp_numfiles - 1) - STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); - } - } - } - - if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) - ExpandCleanup(xp); - - /* Free "orig" if it wasn't stored in "orig_save". */ - if (!orig_saved) - vim_free(orig); - - return ss; -} - -/* - * Prepare an expand structure for use. - */ - void -ExpandInit(expand_T *xp) -{ - xp->xp_pattern = NULL; - xp->xp_pattern_len = 0; - xp->xp_backslash = XP_BS_NONE; -#ifndef BACKSLASH_IN_FILENAME - xp->xp_shell = FALSE; -#endif - xp->xp_numfiles = -1; - xp->xp_files = NULL; -#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - xp->xp_arg = NULL; -#endif - xp->xp_line = NULL; -} - -/* - * Cleanup an expand structure after use. - */ - void -ExpandCleanup(expand_T *xp) -{ - if (xp->xp_numfiles >= 0) - { - FreeWild(xp->xp_numfiles, xp->xp_files); - xp->xp_numfiles = -1; - } -} - - void -ExpandEscape( - expand_T *xp, - char_u *str, - int numfiles, - char_u **files, - int options) -{ - int i; - char_u *p; - - /* - * May change home directory back to "~" - */ - if (options & WILD_HOME_REPLACE) - tilde_replace(str, numfiles, files); - - if (options & WILD_ESCAPE) - { - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_FILES_IN_PATH - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS - || xp->xp_context == EXPAND_DIRECTORIES) - { - /* - * Insert a backslash into a file name before a space, \, %, # - * and wildmatch characters, except '~'. - */ - for (i = 0; i < numfiles; ++i) - { - /* for ":set path=" we need to escape spaces twice */ - if (xp->xp_backslash == XP_BS_THREE) - { - p = vim_strsave_escaped(files[i], (char_u *)" "); - if (p != NULL) - { - vim_free(files[i]); - files[i] = p; -#if defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - if (p != NULL) - { - vim_free(files[i]); - files[i] = p; - } -#endif - } - } -#ifdef BACKSLASH_IN_FILENAME - p = vim_strsave_fnameescape(files[i], FALSE); -#else - p = vim_strsave_fnameescape(files[i], xp->xp_shell); -#endif - if (p != NULL) - { - vim_free(files[i]); - files[i] = p; - } - - /* If 'str' starts with "\~", replace "~" at start of - * files[i] with "\~". */ - if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') - escape_fname(&files[i]); - } - xp->xp_backslash = XP_BS_NONE; - - /* If the first file starts with a '+' escape it. Otherwise it - * could be seen as "+cmd". */ - if (*files[0] == '+') - escape_fname(&files[0]); - } - else if (xp->xp_context == EXPAND_TAGS) - { - /* - * Insert a backslash before characters in a tag name that - * would terminate the ":tag" command. - */ - for (i = 0; i < numfiles; ++i) - { - p = vim_strsave_escaped(files[i], (char_u *)"\\|\""); - if (p != NULL) - { - vim_free(files[i]); - files[i] = p; - } - } - } - } -} - /* * Escape special characters in "fname" for when used as a file name argument * after a Vim command, or, when "shell" is non-zero, a shell command. @@ -4361,7 +3795,7 @@ vim_strsave_fnameescape(char_u *fname, i /* * Put a backslash before the file name in "pp", which is in allocated memory. */ - static void + void escape_fname(char_u **pp) { char_u *p; @@ -4404,1456 +3838,20 @@ tilde_replace( } /* - * Show all matches for completion on the command line. - * Returns EXPAND_NOTHING when the character that triggered expansion should - * be inserted like a normal character. - */ - static int -showmatches(expand_T *xp, int wildmenu UNUSED) -{ -#define L_SHOWFILE(m) (showtail ? sm_gettail(files_found[m]) : files_found[m]) - int num_files; - char_u **files_found; - int i, j, k; - int maxlen; - int lines; - int columns; - char_u *p; - int lastlen; - int attr; - int showtail; - - if (xp->xp_numfiles == -1) - { - set_expand_context(xp); - i = expand_cmdline(xp, ccline.cmdbuff, ccline.cmdpos, - &num_files, &files_found); - showtail = expand_showtail(xp); - if (i != EXPAND_OK) - return i; - - } - else - { - num_files = xp->xp_numfiles; - files_found = xp->xp_files; - showtail = cmd_showtail; - } - -#ifdef FEAT_WILDMENU - if (!wildmenu) - { -#endif - msg_didany = FALSE; /* lines_left will be set */ - msg_start(); /* prepare for paging */ - msg_putchar('\n'); - out_flush(); - cmdline_row = msg_row; - msg_didany = FALSE; /* lines_left will be set again */ - msg_start(); /* prepare for paging */ -#ifdef FEAT_WILDMENU - } -#endif - - if (got_int) - got_int = FALSE; /* only int. the completion, not the cmd line */ -#ifdef FEAT_WILDMENU - else if (wildmenu) - win_redr_status_matches(xp, num_files, files_found, -1, showtail); -#endif - else - { - /* find the length of the longest file name */ - maxlen = 0; - for (i = 0; i < num_files; ++i) - { - if (!showtail && (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) - { - home_replace(NULL, files_found[i], NameBuff, MAXPATHL, TRUE); - j = vim_strsize(NameBuff); - } - else - j = vim_strsize(L_SHOWFILE(i)); - if (j > maxlen) - maxlen = j; - } - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) - lines = num_files; - else - { - /* compute the number of columns and lines for the listing */ - maxlen += 2; /* two spaces between file names */ - columns = ((int)Columns + 2) / maxlen; - if (columns < 1) - columns = 1; - lines = (num_files + columns - 1) / columns; - } - - attr = HL_ATTR(HLF_D); /* find out highlighting for directories */ - - if (xp->xp_context == EXPAND_TAGS_LISTFILES) - { - msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); - msg_clr_eos(); - msg_advance(maxlen - 3); - msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); - } - - /* list the files line by line */ - for (i = 0; i < lines; ++i) - { - lastlen = 999; - for (k = i; k < num_files; k += lines) - { - if (xp->xp_context == EXPAND_TAGS_LISTFILES) - { - msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); - p = files_found[k] + STRLEN(files_found[k]) + 1; - msg_advance(maxlen + 1); - msg_puts((char *)p); - msg_advance(maxlen + 3); - msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); - break; - } - for (j = maxlen - lastlen; --j >= 0; ) - msg_putchar(' '); - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS) - { - /* highlight directories */ - if (xp->xp_numfiles != -1) - { - char_u *halved_slash; - char_u *exp_path; - - /* Expansion was done before and special characters - * were escaped, need to halve backslashes. Also - * $HOME has been replaced with ~/. */ - exp_path = expand_env_save_opt(files_found[k], TRUE); - halved_slash = backslash_halve_save( - exp_path != NULL ? exp_path : files_found[k]); - j = mch_isdir(halved_slash != NULL ? halved_slash - : files_found[k]); - vim_free(exp_path); - vim_free(halved_slash); - } - else - /* Expansion was done here, file names are literal. */ - j = mch_isdir(files_found[k]); - if (showtail) - p = L_SHOWFILE(k); - else - { - home_replace(NULL, files_found[k], NameBuff, MAXPATHL, - TRUE); - p = NameBuff; - } - } - else - { - j = FALSE; - p = L_SHOWFILE(k); - } - lastlen = msg_outtrans_attr(p, j ? attr : 0); - } - if (msg_col > 0) /* when not wrapped around */ - { - msg_clr_eos(); - msg_putchar('\n'); - } - out_flush(); /* show one line at a time */ - if (got_int) - { - got_int = FALSE; - break; - } - } - - /* - * we redraw the command below the lines that we have just listed - * This is a bit tricky, but it saves a lot of screen updating. - */ - cmdline_row = msg_row; /* will put it back later */ - } - - if (xp->xp_numfiles == -1) - FreeWild(num_files, files_found); - - return EXPAND_OK; -} - -/* - * Private gettail for showmatches() (and win_redr_status_matches()): - * Find tail of file name path, but ignore trailing "/". - */ - char_u * -sm_gettail(char_u *s) -{ - char_u *p; - char_u *t = s; - int had_sep = FALSE; - - for (p = s; *p != NUL; ) - { - if (vim_ispathsep(*p) -#ifdef BACKSLASH_IN_FILENAME - && !rem_backslash(p) -#endif - ) - had_sep = TRUE; - else if (had_sep) - { - t = p; - had_sep = FALSE; - } - MB_PTR_ADV(p); - } - return t; -} - -/* - * Return TRUE if we only need to show the tail of completion matches. - * When not completing file names or there is a wildcard in the path FALSE is - * returned. - */ - static int -expand_showtail(expand_T *xp) -{ - char_u *s; - char_u *end; - - /* When not completing file names a "/" may mean something different. */ - if (xp->xp_context != EXPAND_FILES - && xp->xp_context != EXPAND_SHELLCMD - && xp->xp_context != EXPAND_DIRECTORIES) - return FALSE; - - end = gettail(xp->xp_pattern); - if (end == xp->xp_pattern) /* there is no path separator */ - return FALSE; - - for (s = xp->xp_pattern; s < end; s++) - { - /* Skip escaped wildcards. Only when the backslash is not a path - * separator, on DOS the '*' "path\*\file" must not be skipped. */ - if (rem_backslash(s)) - ++s; - else if (vim_strchr((char_u *)"*?[", *s) != NULL) - return FALSE; - } - return TRUE; -} - -/* - * Prepare a string for expansion. - * When expanding file names: The string will be used with expand_wildcards(). - * Copy "fname[len]" into allocated memory and add a '*' at the end. - * When expanding other names: The string will be used with regcomp(). Copy - * the name into allocated memory and prepend "^". + * Get a pointer to the current command line info. */ - char_u * -addstar( - char_u *fname, - int len, - int context) /* EXPAND_FILES etc. */ -{ - char_u *retval; - int i, j; - int new_len; - char_u *tail; - int ends_in_star; - - if (context != EXPAND_FILES - && context != EXPAND_FILES_IN_PATH - && context != EXPAND_SHELLCMD - && context != EXPAND_DIRECTORIES) - { - /* - * Matching will be done internally (on something other than files). - * So we convert the file-matching-type wildcards into our kind for - * use with vim_regcomp(). First work out how long it will be: - */ - - /* For help tags the translation is done in find_help_tags(). - * For a tag pattern starting with "/" no translation is needed. */ - if (context == EXPAND_HELP - || context == EXPAND_COLORS - || context == EXPAND_COMPILER - || context == EXPAND_OWNSYNTAX - || context == EXPAND_FILETYPE - || context == EXPAND_PACKADD - || ((context == EXPAND_TAGS_LISTFILES - || context == EXPAND_TAGS) - && fname[0] == '/')) - retval = vim_strnsave(fname, len); - else - { - new_len = len + 2; /* +2 for '^' at start, NUL at end */ - for (i = 0; i < len; i++) - { - if (fname[i] == '*' || fname[i] == '~') - new_len++; /* '*' needs to be replaced by ".*" - '~' needs to be replaced by "\~" */ - - /* Buffer names are like file names. "." should be literal */ - if (context == EXPAND_BUFFERS && fname[i] == '.') - new_len++; /* "." becomes "\." */ - - /* Custom expansion takes care of special things, match - * backslashes literally (perhaps also for other types?) */ - if ((context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) && fname[i] == '\\') - new_len++; /* '\' becomes "\\" */ - } - retval = alloc(new_len); - if (retval != NULL) - { - retval[0] = '^'; - j = 1; - for (i = 0; i < len; i++, j++) - { - /* Skip backslash. But why? At least keep it for custom - * expansion. */ - if (context != EXPAND_USER_DEFINED - && context != EXPAND_USER_LIST - && fname[i] == '\\' - && ++i == len) - break; - - switch (fname[i]) - { - case '*': retval[j++] = '.'; - break; - case '~': retval[j++] = '\\'; - break; - case '?': retval[j] = '.'; - continue; - case '.': if (context == EXPAND_BUFFERS) - retval[j++] = '\\'; - break; - case '\\': if (context == EXPAND_USER_DEFINED - || context == EXPAND_USER_LIST) - retval[j++] = '\\'; - break; - } - retval[j] = fname[i]; - } - retval[j] = NUL; - } - } - } - else - { - retval = alloc(len + 4); - if (retval != NULL) - { - vim_strncpy(retval, fname, len); - - /* - * Don't add a star to *, ~, ~user, $var or `cmd`. - * * would become **, which walks the whole tree. - * ~ would be at the start of the file name, but not the tail. - * $ could be anywhere in the tail. - * ` could be anywhere in the file name. - * When the name ends in '$' don't add a star, remove the '$'. - */ - tail = gettail(retval); - ends_in_star = (len > 0 && retval[len - 1] == '*'); -#ifndef BACKSLASH_IN_FILENAME - for (i = len - 2; i >= 0; --i) - { - if (retval[i] != '\\') - break; - ends_in_star = !ends_in_star; - } -#endif - if ((*retval != '~' || tail != retval) - && !ends_in_star - && vim_strchr(tail, '$') == NULL - && vim_strchr(retval, '`') == NULL) - retval[len++] = '*'; - else if (len > 0 && retval[len - 1] == '$') - --len; - retval[len] = NUL; - } - } - return retval; -} - -/* - * Must parse the command line so far to work out what context we are in. - * Completion can then be done based on that context. - * This routine sets the variables: - * xp->xp_pattern The start of the pattern to be expanded within - * the command line (ends at the cursor). - * xp->xp_context The type of thing to expand. Will be one of: - * - * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on - * the command line, like an unknown command. Caller - * should beep. - * EXPAND_NOTHING Unrecognised context for completion, use char like - * a normal char, rather than for completion. eg - * :s/^I/ - * EXPAND_COMMANDS Cursor is still touching the command, so complete - * it. - * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. - * EXPAND_FILES After command with EX_XFILE set, or after setting - * with P_EXPAND set. eg :e ^I, :w>>^I - * EXPAND_DIRECTORIES In some cases this is used instead of the latter - * when we know only directories are of interest. eg - * :set dir=^I - * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". - * EXPAND_SETTINGS Complete variable names. eg :set d^I - * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I - * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I - * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect - * EXPAND_HELP Complete tags from the file 'helpfile'/tags - * EXPAND_EVENTS Complete event names - * EXPAND_SYNTAX Complete :syntax command arguments - * EXPAND_HIGHLIGHT Complete highlight (syntax) group names - * EXPAND_AUGROUP Complete autocommand group names - * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I - * EXPAND_MAPPINGS Complete mapping and abbreviation names, - * eg :unmap a^I , :cunab x^I - * EXPAND_FUNCTIONS Complete internal or user defined function names, - * eg :call sub^I - * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I - * EXPAND_EXPRESSION Complete internal or user defined function/variable - * names in expressions, eg :while s^I - * EXPAND_ENV_VARS Complete environment variable names - * EXPAND_USER Complete user names - */ - static void -set_expand_context(expand_T *xp) -{ - /* only expansion for ':', '>' and '=' command-lines */ - if (ccline.cmdfirstc != ':' -#ifdef FEAT_EVAL - && ccline.cmdfirstc != '>' && ccline.cmdfirstc != '=' - && !ccline.input_fn -#endif - ) - { - xp->xp_context = EXPAND_NOTHING; - return; - } - set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, TRUE); -} - - void -set_cmd_context( - expand_T *xp, - char_u *str, /* start of command line */ - int len, /* length of command line (excl. NUL) */ - int col, /* position of cursor */ - int use_ccline UNUSED) /* use ccline for info */ -{ - int old_char = NUL; - char_u *nextcomm; - - /* - * Avoid a UMR warning from Purify, only save the character if it has been - * written before. - */ - if (col < len) - old_char = str[col]; - str[col] = NUL; - nextcomm = str; - -#ifdef FEAT_EVAL - if (use_ccline && ccline.cmdfirstc == '=') - { -# ifdef FEAT_CMDL_COMPL - /* pass CMD_SIZE because there is no real command */ - set_context_for_expression(xp, str, CMD_SIZE); -# endif - } - else if (use_ccline && ccline.input_fn) - { - xp->xp_context = ccline.xp_context; - xp->xp_pattern = ccline.cmdbuff; -# if defined(FEAT_CMDL_COMPL) - xp->xp_arg = ccline.xp_arg; -# endif - } - else -#endif - while (nextcomm != NULL) - nextcomm = set_one_cmd_context(xp, nextcomm); - - /* Store the string here so that call_user_expand_func() can get to them - * easily. */ - xp->xp_line = str; - xp->xp_col = col; - - str[col] = old_char; -} - -/* - * Expand the command line "str" from context "xp". - * "xp" must have been set by set_cmd_context(). - * xp->xp_pattern points into "str", to where the text that is to be expanded - * starts. - * Returns EXPAND_UNSUCCESSFUL when there is something illegal before the - * cursor. - * Returns EXPAND_NOTHING when there is nothing to expand, might insert the - * key that triggered expansion literally. - * Returns EXPAND_OK otherwise. - */ - int -expand_cmdline( - expand_T *xp, - char_u *str, /* start of command line */ - int col, /* position of cursor */ - int *matchcount, /* return: nr of matches */ - char_u ***matches) /* return: array of pointers to matches */ -{ - char_u *file_str = NULL; - int options = WILD_ADD_SLASH|WILD_SILENT; - - if (xp->xp_context == EXPAND_UNSUCCESSFUL) - { - beep_flush(); - return EXPAND_UNSUCCESSFUL; /* Something illegal on command line */ - } - if (xp->xp_context == EXPAND_NOTHING) - { - /* Caller can use the character as a normal char instead */ - return EXPAND_NOTHING; - } - - /* add star to file name, or convert to regexp if not exp. files. */ - xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); - file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); - if (file_str == NULL) - return EXPAND_UNSUCCESSFUL; - - if (p_wic) - options += WILD_ICASE; - - /* find all files that match the description */ - if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) - { - *matchcount = 0; - *matches = NULL; - } - vim_free(file_str); - - return EXPAND_OK; -} - -#ifdef FEAT_MULTI_LANG -/* - * Cleanup matches for help tags: - * Remove "@ab" if the top of 'helplang' is "ab" and the language of the first - * tag matches it. Otherwise remove "@en" if "en" is the only language. - */ - static void -cleanup_help_tags(int num_file, char_u **file) -{ - int i, j; - int len; - char_u buf[4]; - char_u *p = buf; - - if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) - { - *p++ = '@'; - *p++ = p_hlg[0]; - *p++ = p_hlg[1]; - } - *p = NUL; - - for (i = 0; i < num_file; ++i) - { - len = (int)STRLEN(file[i]) - 3; - if (len <= 0) - continue; - if (STRCMP(file[i] + len, "@en") == 0) - { - /* Sorting on priority means the same item in another language may - * be anywhere. Search all items for a match up to the "@en". */ - for (j = 0; j < num_file; ++j) - if (j != i && (int)STRLEN(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) - break; - if (j == num_file) - /* item only exists with @en, remove it */ - file[i][len] = NUL; - } - } - - if (*buf != NUL) - for (i = 0; i < num_file; ++i) - { - len = (int)STRLEN(file[i]) - 3; - if (len <= 0) - continue; - if (STRCMP(file[i] + len, buf) == 0) - { - /* remove the default language */ - file[i][len] = NUL; - } - } -} -#endif - -/* - * Do the expansion based on xp->xp_context and "pat". - */ - static int -ExpandFromContext( - expand_T *xp, - char_u *pat, - int *num_file, - char_u ***file, - int options) // WILD_ flags + cmdline_info_T * +get_cmdline_info(void) { -#ifdef FEAT_CMDL_COMPL - regmatch_T regmatch; -#endif - int ret; - int flags; - - flags = EW_DIR; /* include directories */ - if (options & WILD_LIST_NOTFOUND) - flags |= EW_NOTFOUND; - if (options & WILD_ADD_SLASH) - flags |= EW_ADDSLASH; - if (options & WILD_KEEP_ALL) - flags |= EW_KEEPALL; - if (options & WILD_SILENT) - flags |= EW_SILENT; - if (options & WILD_ALLLINKS) - flags |= EW_ALLLINKS; - - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES_IN_PATH) - { - /* - * Expand file or directory names. - */ - int free_pat = FALSE; - int i; - - /* for ":set path=" and ":set tags=" halve backslashes for escaped - * space */ - if (xp->xp_backslash != XP_BS_NONE) - { - free_pat = TRUE; - pat = vim_strsave(pat); - for (i = 0; pat[i]; ++i) - if (pat[i] == '\\') - { - if (xp->xp_backslash == XP_BS_THREE - && pat[i + 1] == '\\' - && pat[i + 2] == '\\' - && pat[i + 3] == ' ') - STRMOVE(pat + i, pat + i + 3); - if (xp->xp_backslash == XP_BS_ONE - && pat[i + 1] == ' ') - STRMOVE(pat + i, pat + i + 1); - } - } - - if (xp->xp_context == EXPAND_FILES) - flags |= EW_FILE; - else if (xp->xp_context == EXPAND_FILES_IN_PATH) - flags |= (EW_FILE | EW_PATH); - else - flags = (flags | EW_DIR) & ~EW_FILE; - if (options & WILD_ICASE) - flags |= EW_ICASE; - - /* Expand wildcards, supporting %:h and the like. */ - ret = expand_wildcards_eval(&pat, num_file, file, flags); - if (free_pat) - vim_free(pat); -#ifdef BACKSLASH_IN_FILENAME - if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) - { - int i; - - for (i = 0; i < *num_file; ++i) - { - char_u *ptr = (*file)[i]; - - while (*ptr != NUL) - { - if (p_csl[0] == 's' && *ptr == '\\') - *ptr = '/'; - else if (p_csl[0] == 'b' && *ptr == '/') - *ptr = '\\'; - ptr += (*mb_ptr2len)(ptr); - } - } - } -#endif - return ret; - } - - *file = (char_u **)""; - *num_file = 0; - if (xp->xp_context == EXPAND_HELP) - { - /* With an empty argument we would get all the help tags, which is - * very slow. Get matches for "help" instead. */ - if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat, - num_file, file, FALSE) == OK) - { -#ifdef FEAT_MULTI_LANG - cleanup_help_tags(*num_file, *file); -#endif - return OK; - } - return FAIL; - } - -#ifndef FEAT_CMDL_COMPL - return FAIL; -#else - if (xp->xp_context == EXPAND_SHELLCMD) - return expand_shellcmd(pat, num_file, file, flags); - if (xp->xp_context == EXPAND_OLD_SETTING) - return ExpandOldSetting(num_file, file); - if (xp->xp_context == EXPAND_BUFFERS) - return ExpandBufnames(pat, num_file, file, options); - if (xp->xp_context == EXPAND_TAGS - || xp->xp_context == EXPAND_TAGS_LISTFILES) - return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); - if (xp->xp_context == EXPAND_COLORS) - { - char *directories[] = {"colors", NULL}; - return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, - directories); - } - if (xp->xp_context == EXPAND_COMPILER) - { - char *directories[] = {"compiler", NULL}; - return ExpandRTDir(pat, 0, num_file, file, directories); - } - if (xp->xp_context == EXPAND_OWNSYNTAX) - { - char *directories[] = {"syntax", NULL}; - return ExpandRTDir(pat, 0, num_file, file, directories); - } - if (xp->xp_context == EXPAND_FILETYPE) - { - char *directories[] = {"syntax", "indent", "ftplugin", NULL}; - return ExpandRTDir(pat, 0, num_file, file, directories); - } -# if defined(FEAT_EVAL) - if (xp->xp_context == EXPAND_USER_LIST) - return ExpandUserList(xp, num_file, file); -# endif - if (xp->xp_context == EXPAND_PACKADD) - return ExpandPackAddDir(pat, num_file, file); - - regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) - return FAIL; - - /* set ignore-case according to p_ic, p_scs and pat */ - regmatch.rm_ic = ignorecase(pat); - - if (xp->xp_context == EXPAND_SETTINGS - || xp->xp_context == EXPAND_BOOL_SETTINGS) - ret = ExpandSettings(xp, ®match, num_file, file); - else if (xp->xp_context == EXPAND_MAPPINGS) - ret = ExpandMappings(®match, num_file, file); -# if defined(FEAT_EVAL) - else if (xp->xp_context == EXPAND_USER_DEFINED) - ret = ExpandUserDefined(xp, ®match, num_file, file); -# endif - else - { - static struct expgen - { - int context; - char_u *((*func)(expand_T *, int)); - int ic; - int escaped; - } tab[] = - { - {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, - {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, - {EXPAND_MAPCLEAR, get_mapclear_arg, TRUE, TRUE}, - {EXPAND_MESSAGES, get_messages_arg, TRUE, TRUE}, - {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, - {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, - {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, - {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, - {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, - {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, -#ifdef FEAT_EVAL - {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, - {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, - {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, - {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, -#endif -#ifdef FEAT_MENU - {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, - {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, -#endif -#ifdef FEAT_SYN_HL - {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, -#endif -#ifdef FEAT_PROFILE - {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, -#endif - {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, - {EXPAND_EVENTS, get_event_name, TRUE, TRUE}, - {EXPAND_AUGROUP, get_augroup_name, TRUE, TRUE}, -#ifdef FEAT_CSCOPE - {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, -#endif -#ifdef FEAT_SIGNS - {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, -#endif -#ifdef FEAT_PROFILE - {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, -#endif -#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) - {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, - {EXPAND_LOCALES, get_locales, TRUE, FALSE}, -#endif - {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, - {EXPAND_USER, get_users, TRUE, FALSE}, - {EXPAND_ARGLIST, get_arglist_name, TRUE, FALSE}, - }; - int i; - - /* - * Find a context in the table and call the ExpandGeneric() with the - * right function to do the expansion. - */ - ret = FAIL; - for (i = 0; i < (int)(sizeof(tab) / sizeof(struct expgen)); ++i) - if (xp->xp_context == tab[i].context) - { - if (tab[i].ic) - regmatch.rm_ic = TRUE; - ret = ExpandGeneric(xp, ®match, num_file, file, - tab[i].func, tab[i].escaped); - break; - } - } - - vim_regfree(regmatch.regprog); - - return ret; -#endif /* FEAT_CMDL_COMPL */ -} - -#if defined(FEAT_CMDL_COMPL) || defined(PROTO) -/* - * Expand a list of names. - * - * Generic function for command line completion. It calls a function to - * obtain strings, one by one. The strings are matched against a regexp - * program. Matching strings are copied into an array, which is returned. - * - * Returns OK when no problems encountered, FAIL for error (out of memory). - */ - int -ExpandGeneric( - expand_T *xp, - regmatch_T *regmatch, - int *num_file, - char_u ***file, - char_u *((*func)(expand_T *, int)), - /* returns a string from the list */ - int escaped) -{ - int i; - int count = 0; - int round; - char_u *str; - - /* do this loop twice: - * round == 0: count the number of matching names - * round == 1: copy the matching names into allocated memory - */ - for (round = 0; round <= 1; ++round) - { - for (i = 0; ; ++i) - { - str = (*func)(xp, i); - if (str == NULL) /* end of list */ - break; - if (*str == NUL) /* skip empty strings */ - continue; - - if (vim_regexec(regmatch, str, (colnr_T)0)) - { - if (round) - { - if (escaped) - str = vim_strsave_escaped(str, (char_u *)" \t\\."); - else - str = vim_strsave(str); - (*file)[count] = str; -#ifdef FEAT_MENU - if (func == get_menu_names && str != NULL) - { - /* test for separator added by get_menu_names() */ - str += STRLEN(str) - 1; - if (*str == '\001') - *str = '.'; - } -#endif - } - ++count; - } - } - if (round == 0) - { - if (count == 0) - return OK; - *num_file = count; - *file = ALLOC_MULT(char_u *, count); - if (*file == NULL) - { - *file = (char_u **)""; - return FAIL; - } - count = 0; - } - } - - /* Sort the results. Keep menu's in the specified order. */ - if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) - { - if (xp->xp_context == EXPAND_EXPRESSION - || xp->xp_context == EXPAND_FUNCTIONS - || xp->xp_context == EXPAND_USER_FUNC) - /* functions should be sorted to the end. */ - qsort((void *)*file, (size_t)*num_file, sizeof(char_u *), - sort_func_compare); - else - sort_strings(*file, *num_file); - } - -#ifdef FEAT_CMDL_COMPL - /* Reset the variables used for special highlight names expansion, so that - * they don't show up when getting normal highlight names by ID. */ - reset_expand_highlight(); -#endif - - return OK; + return &ccline; } -/* - * Complete a shell command. - * Returns FAIL or OK; - */ - static int -expand_shellcmd( - char_u *filepat, /* pattern to match with command names */ - int *num_file, /* return: number of matches */ - char_u ***file, /* return: array with matches */ - int flagsarg) /* EW_ flags */ -{ - char_u *pat; - int i; - char_u *path = NULL; - int mustfree = FALSE; - garray_T ga; - char_u *buf = alloc(MAXPATHL); - size_t l; - char_u *s, *e; - int flags = flagsarg; - int ret; - int did_curdir = FALSE; - hashtab_T found_ht; - hashitem_T *hi; - hash_T hash; - - if (buf == NULL) - return FAIL; - - /* for ":set path=" and ":set tags=" halve backslashes for escaped - * space */ - pat = vim_strsave(filepat); - for (i = 0; pat[i]; ++i) - if (pat[i] == '\\' && pat[i + 1] == ' ') - STRMOVE(pat + i, pat + i + 1); - - flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; - - if (pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' && vim_ispathsep(pat[2])))) - path = (char_u *)"."; - else - { - /* For an absolute name we don't use $PATH. */ - if (!mch_isFullName(pat)) - path = vim_getenv((char_u *)"PATH", &mustfree); - if (path == NULL) - path = (char_u *)""; - } - - /* - * Go over all directories in $PATH. Expand matches in that directory and - * collect them in "ga". When "." is not in $PATH also expand for the - * current directory, to find "subdir/cmd". - */ - ga_init2(&ga, (int)sizeof(char *), 10); - hash_init(&found_ht); - for (s = path; ; s = e) - { -#if defined(MSWIN) - e = vim_strchr(s, ';'); -#else - e = vim_strchr(s, ':'); -#endif - if (e == NULL) - e = s + STRLEN(s); - - if (*s == NUL) - { - if (did_curdir) - break; - // Find directories in the current directory, path is empty. - did_curdir = TRUE; - flags |= EW_DIR; - } - else if (STRNCMP(s, ".", (int)(e - s)) == 0) - { - did_curdir = TRUE; - flags |= EW_DIR; - } - else - // Do not match directories inside a $PATH item. - flags &= ~EW_DIR; - - l = e - s; - if (l > MAXPATHL - 5) - break; - vim_strncpy(buf, s, l); - add_pathsep(buf); - l = STRLEN(buf); - vim_strncpy(buf + l, pat, MAXPATHL - 1 - l); - - /* Expand matches in one directory of $PATH. */ - ret = expand_wildcards(1, &buf, num_file, file, flags); - if (ret == OK) - { - if (ga_grow(&ga, *num_file) == FAIL) - FreeWild(*num_file, *file); - else - { - for (i = 0; i < *num_file; ++i) - { - char_u *name = (*file)[i]; - - if (STRLEN(name) > l) - { - // Check if this name was already found. - hash = hash_hash(name + l); - hi = hash_lookup(&found_ht, name + l, hash); - if (HASHITEM_EMPTY(hi)) - { - // Remove the path that was prepended. - STRMOVE(name, name + l); - ((char_u **)ga.ga_data)[ga.ga_len++] = name; - hash_add_item(&found_ht, hi, name, hash); - name = NULL; - } - } - vim_free(name); - } - vim_free(*file); - } - } - if (*e != NUL) - ++e; - } - *file = ga.ga_data; - *num_file = ga.ga_len; - - vim_free(buf); - vim_free(pat); - if (mustfree) - vim_free(path); - hash_clear(&found_ht); - return OK; -} - - -# if defined(FEAT_EVAL) -/* - * Call "user_expand_func()" to invoke a user defined Vim script function and - * return the result (either a string or a List). - */ - static void * -call_user_expand_func( - void *(*user_expand_func)(char_u *, int, typval_T *), - expand_T *xp, - int *num_file, - char_u ***file) -{ - int keep = 0; - typval_T args[4]; - sctx_T save_current_sctx = current_sctx; - char_u *pat = NULL; - void *ret; - - if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) - return NULL; - *num_file = 0; - *file = NULL; - - if (ccline.cmdbuff != NULL) - { - keep = ccline.cmdbuff[ccline.cmdlen]; - ccline.cmdbuff[ccline.cmdlen] = 0; - } - - pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); - - args[0].v_type = VAR_STRING; - args[0].vval.v_string = pat; - args[1].v_type = VAR_STRING; - args[1].vval.v_string = xp->xp_line; - args[2].v_type = VAR_NUMBER; - args[2].vval.v_number = xp->xp_col; - args[3].v_type = VAR_UNKNOWN; - - current_sctx = xp->xp_script_ctx; - - ret = user_expand_func(xp->xp_arg, 3, args); - - current_sctx = save_current_sctx; - if (ccline.cmdbuff != NULL) - ccline.cmdbuff[ccline.cmdlen] = keep; - - vim_free(pat); - return ret; -} - -/* - * Expand names with a function defined by the user. - */ - static int -ExpandUserDefined( - expand_T *xp, - regmatch_T *regmatch, - int *num_file, - char_u ***file) -{ - char_u *retstr; - char_u *s; - char_u *e; - int keep; - garray_T ga; - int skip; - - retstr = call_user_expand_func(call_func_retstr, xp, num_file, file); - if (retstr == NULL) - return FAIL; - - ga_init2(&ga, (int)sizeof(char *), 3); - for (s = retstr; *s != NUL; s = e) - { - e = vim_strchr(s, '\n'); - if (e == NULL) - e = s + STRLEN(s); - keep = *e; - *e = NUL; - - skip = xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0; - *e = keep; - - if (!skip) - { - if (ga_grow(&ga, 1) == FAIL) - break; - ((char_u **)ga.ga_data)[ga.ga_len] = vim_strnsave(s, (int)(e - s)); - ++ga.ga_len; - } - - if (*e != NUL) - ++e; - } - vim_free(retstr); - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -/* - * Expand names with a list returned by a function defined by the user. - */ - static int -ExpandUserList( - expand_T *xp, - int *num_file, - char_u ***file) -{ - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func(call_func_retlist, xp, num_file, file); - if (retlist == NULL) - return FAIL; - - ga_init2(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) - { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ - - if (ga_grow(&ga, 1) == FAIL) - break; - - ((char_u **)ga.ga_data)[ga.ga_len] = - vim_strsave(li->li_tv.vval.v_string); - ++ga.ga_len; - } - list_unref(retlist); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} -#endif - -/* - * Expand color scheme, compiler or filetype names. - * Search from 'runtimepath': - * 'runtimepath'/{dirnames}/{pat}.vim - * When "flags" has DIP_START: search also from 'start' of 'packpath': - * 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim - * When "flags" has DIP_OPT: search also from 'opt' of 'packpath': - * 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim - * "dirnames" is an array with one or more directory names. - */ - static int -ExpandRTDir( - char_u *pat, - int flags, - int *num_file, - char_u ***file, - char *dirnames[]) -{ - char_u *s; - char_u *e; - char_u *match; - garray_T ga; - int i; - int pat_len; - - *num_file = 0; - *file = NULL; - pat_len = (int)STRLEN(pat); - ga_init2(&ga, (int)sizeof(char *), 10); - - for (i = 0; dirnames[i] != NULL; ++i) - { - s = alloc(STRLEN(dirnames[i]) + pat_len + 7); - if (s == NULL) - { - ga_clear_strings(&ga); - return FAIL; - } - sprintf((char *)s, "%s/%s*.vim", dirnames[i], pat); - globpath(p_rtp, s, &ga, 0); - vim_free(s); - } - - if (flags & DIP_START) { - for (i = 0; dirnames[i] != NULL; ++i) - { - s = alloc(STRLEN(dirnames[i]) + pat_len + 22); - if (s == NULL) - { - ga_clear_strings(&ga); - return FAIL; - } - sprintf((char *)s, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); - globpath(p_pp, s, &ga, 0); - vim_free(s); - } - } - - if (flags & DIP_OPT) { - for (i = 0; dirnames[i] != NULL; ++i) - { - s = alloc(STRLEN(dirnames[i]) + pat_len + 20); - if (s == NULL) - { - ga_clear_strings(&ga); - return FAIL; - } - sprintf((char *)s, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); - globpath(p_pp, s, &ga, 0); - vim_free(s); - } - } - - for (i = 0; i < ga.ga_len; ++i) - { - match = ((char_u **)ga.ga_data)[i]; - s = match; - e = s + STRLEN(s); - if (e - 4 > s && STRNICMP(e - 4, ".vim", 4) == 0) - { - e -= 4; - for (s = e; s > match; MB_PTR_BACK(match, s)) - if (s < match || vim_ispathsep(*s)) - break; - ++s; - *e = NUL; - mch_memmove(match, s, e - s + 1); - } - } - - if (ga.ga_len == 0) - return FAIL; - - /* Sort and remove duplicates which can happen when specifying multiple - * directories in dirnames. */ - remove_duplicates(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -/* - * Expand loadplugin names: - * 'packpath'/pack/ * /opt/{pat} - */ - static int -ExpandPackAddDir( - char_u *pat, - int *num_file, - char_u ***file) -{ - char_u *s; - char_u *e; - char_u *match; - garray_T ga; - int i; - int pat_len; - - *num_file = 0; - *file = NULL; - pat_len = (int)STRLEN(pat); - ga_init2(&ga, (int)sizeof(char *), 10); - - s = alloc(pat_len + 26); - if (s == NULL) - { - ga_clear_strings(&ga); - return FAIL; - } - sprintf((char *)s, "pack/*/opt/%s*", pat); - globpath(p_pp, s, &ga, 0); - vim_free(s); - - for (i = 0; i < ga.ga_len; ++i) - { - match = ((char_u **)ga.ga_data)[i]; - s = gettail(match); - e = s + STRLEN(s); - mch_memmove(match, s, e - s + 1); - } - - if (ga.ga_len == 0) - return FAIL; - - /* Sort and remove duplicates which can happen when specifying multiple - * directories in dirnames. */ - remove_duplicates(&ga); - - *file = ga.ga_data; - *num_file = ga.ga_len; - return OK; -} - -#endif - -#if defined(FEAT_CMDL_COMPL) || defined(FEAT_EVAL) || defined(PROTO) -/* - * Expand "file" for all comma-separated directories in "path". - * Adds the matches to "ga". Caller must init "ga". - */ - void -globpath( - char_u *path, - char_u *file, - garray_T *ga, - int expand_options) -{ - expand_T xpc; - char_u *buf; - int i; - int num_p; - char_u **p; - - buf = alloc(MAXPATHL); - if (buf == NULL) - return; - - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - - /* Loop over all entries in {path}. */ - while (*path != NUL) - { - /* Copy one item of the path to buf[] and concatenate the file name. */ - copy_option_part(&path, buf, MAXPATHL, ","); - if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) - { -# if defined(MSWIN) - /* Using the platform's path separator (\) makes vim incorrectly - * treat it as an escape character, use '/' instead. */ - if (*buf != NUL && !after_pathsep(buf, buf + STRLEN(buf))) - STRCAT(buf, "/"); -# else - add_pathsep(buf); -# endif - STRCAT(buf, file); - if (ExpandFromContext(&xpc, buf, &num_p, &p, - WILD_SILENT|expand_options) != FAIL && num_p > 0) - { - ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT|expand_options); - - if (ga_grow(ga, num_p) == OK) - { - for (i = 0; i < num_p; ++i) - { - ((char_u **)ga->ga_data)[ga->ga_len] = - vim_strnsave(p[i], (int)STRLEN(p[i])); - ++ga->ga_len; - } - } - - FreeWild(num_p, p); - } - } - } - - vim_free(buf); -} - -#endif - #if defined(FEAT_EVAL) || defined(FEAT_CMDWIN) || defined(PROTO) /* * Get pointer to the command line info to use. save_ccline() may clear * ccline and put the previous value in prev_ccline. */ - static struct cmdline_info * + static cmdline_info_T * get_ccline_ptr(void) { if ((State & CMDLINE) == 0) @@ -5875,7 +3873,7 @@ get_ccline_ptr(void) char_u * get_cmdline_str(void) { - struct cmdline_info *p; + cmdline_info_T *p; if (cmdline_star > 0) return NULL; @@ -5894,7 +3892,7 @@ get_cmdline_str(void) int get_cmdline_pos(void) { - struct cmdline_info *p = get_ccline_ptr(); + cmdline_info_T *p = get_ccline_ptr(); if (p == NULL) return -1; @@ -5910,7 +3908,7 @@ get_cmdline_pos(void) set_cmdline_pos( int pos) { - struct cmdline_info *p = get_ccline_ptr(); + cmdline_info_T *p = get_ccline_ptr(); if (p == NULL) return 1; @@ -5935,7 +3933,7 @@ set_cmdline_pos( int get_cmdline_type(void) { - struct cmdline_info *p = get_ccline_ptr(); + cmdline_info_T *p = get_ccline_ptr(); if (p == NULL) return NUL; diff --git a/src/proto.h b/src/proto.h --- a/src/proto.h +++ b/src/proto.h @@ -66,6 +66,7 @@ extern int _stricoll(char *a, char *b); # include "buffer.pro" # include "change.pro" # include "charset.pro" +# include "cmdexpand.pro" # include "cmdhist.pro" # ifdef FEAT_CSCOPE # include "if_cscope.pro" diff --git a/src/proto/cmdexpand.pro b/src/proto/cmdexpand.pro new file mode 100644 --- /dev/null +++ b/src/proto/cmdexpand.pro @@ -0,0 +1,14 @@ +/* cmdexpand.c */ +int nextwild(expand_T *xp, int type, int options, int escape); +char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode); +void ExpandInit(expand_T *xp); +void ExpandCleanup(expand_T *xp); +int showmatches(expand_T *xp, int wildmenu); +char_u *sm_gettail(char_u *s); +char_u *addstar(char_u *fname, int len, int context); +void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline); +int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char_u ***matches); +int ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file, char_u *((*func)(expand_T *, int)), int escaped); +void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options); +void f_getcompletion(typval_T *argvars, typval_T *rettv); +/* vim: set ft=c : */ diff --git a/src/proto/ex_getln.pro b/src/proto/ex_getln.pro --- a/src/proto/ex_getln.pro +++ b/src/proto/ex_getln.pro @@ -12,6 +12,7 @@ char_u *getexmodeline(int promptc, void int cmdline_overstrike(void); int cmdline_at_end(void); colnr_T cmdline_getvcol_cursor(void); +int realloc_cmdbuff(int len); void free_arshape_buf(void); void putcmdline(int c, int shift); void unputcmdline(void); @@ -21,19 +22,12 @@ void redrawcmdline(void); void redrawcmdline_ex(int do_compute_cmdrow); void redrawcmd(void); void compute_cmdrow(void); +void cursorcmd(void); void gotocmdline(int clr); -char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode); -void ExpandInit(expand_T *xp); -void ExpandCleanup(expand_T *xp); -void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int options); char_u *vim_strsave_fnameescape(char_u *fname, int shell); +void escape_fname(char_u **pp); void tilde_replace(char_u *orig_pat, int num_files, char_u **files); -char_u *sm_gettail(char_u *s); -char_u *addstar(char_u *fname, int len, int context); -void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline); -int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char_u ***matches); -int ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file, char_u *((*func)(expand_T *, int)), int escaped); -void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options); +cmdline_info_T *get_cmdline_info(void); char_u *get_cmdline_str(void); int get_cmdline_pos(void); int set_cmdline_pos(int pos); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -579,6 +579,33 @@ typedef struct expand #define XP_BS_THREE 2 // uses three backslashes before a space /* + * Variables shared between getcmdline(), redrawcmdline() and others. + * These need to be saved when using CTRL-R |, that's why they are in a + * structure. + */ +typedef struct +{ + char_u *cmdbuff; /* pointer to command line buffer */ + int cmdbufflen; /* length of cmdbuff */ + int cmdlen; /* number of chars in command line */ + int cmdpos; /* current cursor position */ + int cmdspos; /* cursor column on screen */ + int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ + int cmdindent; /* number of spaces before cmdline */ + char_u *cmdprompt; /* message in front of cmdline */ + int cmdattr; /* attributes for prompt */ + int overstrike; /* Typing mode on the command line. Shared by + getcmdline() and put_on_cmdline(). */ + expand_T *xpc; /* struct being used for expansion, xp_pattern + may point into cmdbuff */ + int xp_context; /* type of expansion */ +# ifdef FEAT_EVAL + char_u *xp_arg; /* user-defined expansion arg */ + int input_fn; /* when TRUE Invoked for input() function */ +# endif +} cmdline_info_T; + +/* * Command modifiers ":vertical", ":browse", ":confirm" and ":hide" set a flag. * This needs to be saved for recursive commands, put them in a structure for * easy manipulation. diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -770,6 +770,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1886, +/**/ 1885, /**/ 1884,