/* * 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. */ /* * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) * * A lot of this file was originally written by Juergen Weigert and later * changed beyond recognition. */ #include #include #include #include #include #include "nvim/api/private/handle.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/os_unix.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/misc1.h" #include "nvim/misc2.h" #include "nvim/mouse.h" #include "nvim/garray.h" #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tempfile.h" #include "nvim/term.h" #include "nvim/ui.h" #include "nvim/types.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/event.h" #include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/os/job.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/defs.h" #ifdef HAVE_STROPTS_H # include #endif #ifdef HAVE_SELINUX # include static int selinux_enabled = -1; #endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os_unix.c.generated.h" #endif static char_u *oldtitle = NULL; static int did_set_title = FALSE; static char_u *oldicon = NULL; static int did_set_icon = FALSE; static int get_x11_title(int test_only) { return FALSE; } static int get_x11_icon(int test_only) { if (!test_only) { if (STRNCMP(T_NAME, "builtin_", 8) == 0) oldicon = vim_strsave(T_NAME + 8); else oldicon = vim_strsave(T_NAME); } return FALSE; } int mch_can_restore_title(void) { return get_x11_title(TRUE); } int mch_can_restore_icon(void) { return get_x11_icon(TRUE); } /* * Set the window title and icon. */ void mch_settitle(char_u *title, char_u *icon) { static int recursive = 0; if (T_NAME == NULL) /* no terminal name (yet) */ return; if (title == NULL && icon == NULL) /* nothing to do */ return; /* When one of the X11 functions causes a deadly signal, we get here again * recursively. Avoid hanging then (something is probably locked). */ if (recursive) return; ++recursive; if (title != NULL) { ui_set_title((char *)title); did_set_title = TRUE; } if (icon != NULL) { ui_set_icon((char *)icon); did_set_icon = TRUE; } --recursive; } /* * Restore the window/icon title. * "which" is one of: * 1 only restore title * 2 only restore icon * 3 restore title and icon */ void mch_restore_title(int which) { /* only restore the title or icon when it has been set */ mch_settitle(((which & 1) && did_set_title) ? (oldtitle ? oldtitle : p_titleold) : NULL, ((which & 2) && did_set_icon) ? oldicon : NULL); } /* * Return TRUE if "name" looks like some xterm name. * Seiichi Sato mentioned that "mlterm" works like xterm. */ int vim_is_xterm(char_u *name) { if (name == NULL) return FALSE; return STRNICMP(name, "xterm", 5) == 0 || STRNICMP(name, "nxterm", 6) == 0 || STRNICMP(name, "kterm", 5) == 0 || STRNICMP(name, "mlterm", 6) == 0 || STRNICMP(name, "rxvt", 4) == 0 || STRCMP(name, "builtin_xterm") == 0; } /* * Return non-zero when using an xterm mouse, according to 'ttymouse'. * Return 1 for "xterm". * Return 2 for "xterm2". * Return 3 for "urxvt". * Return 4 for "sgr". */ int use_xterm_mouse(void) { if (ttym_flags == TTYM_SGR) return 4; if (ttym_flags == TTYM_URXVT) return 3; if (ttym_flags == TTYM_XTERM2) return 2; if (ttym_flags == TTYM_XTERM) return 1; return 0; } #if defined(USE_FNAME_CASE) /* * Set the case of the file name, if it already exists. This will cause the * file name to remain exactly the same. * Only required for file systems where case is ignored and preserved. */ void fname_case( char_u *name, int len /* buffer size, only used when name gets longer */ ) { char_u *slash, *tail; DIR *dirp; struct dirent *dp; FileInfo file_info; if (os_fileinfo_link((char *)name, &file_info)) { /* Open the directory where the file is located. */ slash = vim_strrchr(name, '/'); if (slash == NULL) { dirp = opendir("."); tail = name; } else { *slash = NUL; dirp = opendir((char *)name); *slash = '/'; tail = slash + 1; } if (dirp != NULL) { while ((dp = readdir(dirp)) != NULL) { /* Only accept names that differ in case and are the same byte * length. TODO: accept different length name. */ if (STRICMP(tail, dp->d_name) == 0 && STRLEN(tail) == STRLEN(dp->d_name)) { char_u newname[MAXPATHL + 1]; /* Verify the inode is equal. */ STRLCPY(newname, name, MAXPATHL + 1); STRLCPY(newname + (tail - name), dp->d_name, MAXPATHL - (tail - name) + 1); FileInfo file_info_new; if (os_fileinfo_link((char *)newname, &file_info_new) && os_fileinfo_id_equal(&file_info, &file_info_new)) { STRCPY(tail, dp->d_name); break; } } } closedir(dirp); } } } #endif #if defined(HAVE_ACL) # ifdef HAVE_SYS_ACL_H # include # endif # ifdef HAVE_SYS_ACCESS_H # include # endif #if defined(HAVE_SELINUX) /* * Copy security info from "from_file" to "to_file". */ void mch_copy_sec(char_u *from_file, char_u *to_file) { if (from_file == NULL) return; if (selinux_enabled == -1) selinux_enabled = is_selinux_enabled(); if (selinux_enabled > 0) { security_context_t from_context = NULL; security_context_t to_context = NULL; if (getfilecon((char *)from_file, &from_context) < 0) { /* If the filesystem doesn't support extended attributes, the original had no special security context and the target cannot have one either. */ if (errno == EOPNOTSUPP) return; MSG_PUTS(_("\nCould not get security context for ")); msg_outtrans(from_file); msg_putchar('\n'); return; } if (getfilecon((char *)to_file, &to_context) < 0) { MSG_PUTS(_("\nCould not get security context for ")); msg_outtrans(to_file); msg_putchar('\n'); freecon (from_context); return; } if (strcmp(from_context, to_context) != 0) { if (setfilecon((char *)to_file, from_context) < 0) { MSG_PUTS(_("\nCould not set security context for ")); msg_outtrans(to_file); msg_putchar('\n'); } } freecon(to_context); freecon(from_context); } } #endif /* HAVE_SELINUX */ /* * Return a pointer to the ACL of file "fname" in allocated memory. * Return NULL if the ACL is not available for whatever reason. */ vim_acl_T mch_get_acl(char_u *fname) { vim_acl_T ret = NULL; return ret; } /* * Set the ACL of file "fname" to "acl" (unless it's NULL). */ void mch_set_acl(char_u *fname, vim_acl_T aclent) { if (aclent == NULL) return; } void mch_free_acl(vim_acl_T aclent) { if (aclent == NULL) return; } #endif /* * Set hidden flag for "name". */ void mch_hide(char_u *name) { /* can't hide a file */ } /* * Check what "name" is: * NODE_NORMAL: file or directory (or doesn't exist) * NODE_WRITABLE: writable device, socket, fifo, etc. * NODE_OTHER: non-writable things */ int mch_nodetype(char_u *name) { struct stat st; if (stat((char *)name, &st)) return NODE_NORMAL; if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) return NODE_NORMAL; if (S_ISBLK(st.st_mode)) /* block device isn't writable */ return NODE_OTHER; /* Everything else is writable? */ return NODE_WRITABLE; } #if defined(EXITFREE) void mch_free_mem(void) { free(oldtitle); free(oldicon); } #endif /* * Output a newline when exiting. * Make sure the newline goes to the same stream as the text. */ static void exit_scroll(void) { if (silent_mode) return; if (newline_on_exit || msg_didout) { if (msg_use_printf()) { if (info_message) mch_msg("\n"); else mch_errmsg("\r\n"); } else out_char('\n'); } else { restore_cterm_colors(); /* get original colors back */ msg_clr_eos_force(); /* clear the rest of the display */ windgoto((int)Rows - 1, 0); /* may have moved the cursor */ } } void mch_exit(int r) { exiting = TRUE; { mch_restore_title(3); /* restore xterm title and icon name */ /* * When t_ti is not empty but it doesn't cause swapping terminal * pages, need to output a newline when msg_didout is set. But when * t_ti does swap pages it should not go to the shell page. Do this * before stoptermcap(). */ if (swapping_screen() && !newline_on_exit) exit_scroll(); ui_builtin_stop(); /* * A newline is only required after a message in the alternate screen. * This is set to TRUE by wait_return(). */ if (!swapping_screen() || newline_on_exit) exit_scroll(); /* Cursor may have been switched off without calling starttermcap() * when doing "vim -u vimrc" and vimrc contains ":q". */ if (full_screen) cursor_on(); } out_flush(); ml_close_all(TRUE); /* remove all memfiles */ event_teardown(); #ifdef EXITFREE free_all_mem(); #endif exit(r); } /* * Set mouse clicks on or off. */ void mch_setmouse(int on) { static int ison = FALSE; int xterm_mouse_vers; if (on == ison) /* return quickly if nothing to do */ return; xterm_mouse_vers = use_xterm_mouse(); if (ttym_flags == TTYM_URXVT) { out_str_nf((char_u *) (on ? "\033[?1015h" : "\033[?1015l")); ison = on; } if (ttym_flags == TTYM_SGR) { out_str_nf((char_u *) (on ? "\033[?1006h" : "\033[?1006l")); ison = on; } if (xterm_mouse_vers > 0) { if (on) /* enable mouse events, use mouse tracking if available */ out_str_nf((char_u *) (xterm_mouse_vers > 1 ? "\033[?1002h" : "\033[?1000h")); else /* disable mouse events, could probably always send the same */ out_str_nf((char_u *) (xterm_mouse_vers > 1 ? "\033[?1002l" : "\033[?1000l")); ison = on; } else if (ttym_flags == TTYM_DEC) { if (on) /* enable mouse events */ out_str_nf((char_u *)"\033[1;2'z\033[1;3'{"); else /* disable mouse events */ out_str_nf((char_u *)"\033['z"); ison = on; } } /* * mch_expand_wildcards() - this code does wild-card pattern matching using * the shell * * return OK for success, FAIL for error (you may lose some memory) and put * an error message in *file. * * num_pat is number of input patterns * pat is array of pointers to input patterns * num_file is pointer to number of matched file names * file is pointer to array of pointers to matched file names */ #ifndef SEEK_SET # define SEEK_SET 0 #endif #ifndef SEEK_END # define SEEK_END 2 #endif #define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, int flags /* EW_* flags */ ) { int i; size_t len; char_u *p; bool dir; char_u *extra_shell_arg = NULL; ShellOpts shellopts = kShellOptExpand | kShellOptSilent; int j; char_u *tempname; char_u *command; FILE *fd; char_u *buffer; #define STYLE_ECHO 0 /* use "echo", the default */ #define STYLE_GLOB 1 /* use "glob", for csh */ #define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ #define STYLE_PRINT 3 /* use "print -N", for zsh */ #define STYLE_BT 4 /* `cmd` expansion, execute the pattern * directly */ int shell_style = STYLE_ECHO; int check_spaces; static int did_find_nul = FALSE; int ampersent = FALSE; /* vimglob() function to define for Posix shell */ static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; *num_file = 0; /* default: no files found */ *file = NULL; /* * If there are no wildcards, just copy the names to allocated memory. * Saves a lot of time, because we don't have to start a new shell. */ if (!have_wildcard(num_pat, pat)) { save_patterns(num_pat, pat, num_file, file); return OK; } # ifdef HAVE_SANDBOX /* Don't allow any shell command in the sandbox. */ if (sandbox != 0 && check_secure()) return FAIL; # endif /* * Don't allow the use of backticks in secure and restricted mode. */ if (secure || restricted) for (i = 0; i < num_pat; ++i) if (vim_strchr(pat[i], '`') != NULL && (check_restricted() || check_secure())) return FAIL; /* * get a name for the temp file */ if ((tempname = vim_tempname()) == NULL) { EMSG(_(e_notmp)); return FAIL; } /* * Let the shell expand the patterns and write the result into the temp * file. * STYLE_BT: NL separated * If expanding `cmd` execute it directly. * STYLE_GLOB: NUL separated * If we use *csh, "glob" will work better than "echo". * STYLE_PRINT: NL or NUL separated * If we use *zsh, "print -N" will work better than "glob". * STYLE_VIMGLOB: NL separated * If we use *sh*, we define "vimglob()". * STYLE_ECHO: space separated. * A shell we don't know, stay safe and use "echo". */ if (num_pat == 1 && *pat[0] == '`' && (len = STRLEN(pat[0])) > 2 && *(pat[0] + len - 1) == '`') shell_style = STYLE_BT; else if ((len = STRLEN(p_sh)) >= 3) { if (STRCMP(p_sh + len - 3, "csh") == 0) shell_style = STYLE_GLOB; else if (STRCMP(p_sh + len - 3, "zsh") == 0) shell_style = STYLE_PRINT; } if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), "sh") != NULL) shell_style = STYLE_VIMGLOB; /* Compute the length of the command. We need 2 extra bytes: for the * optional '&' and for the NUL. * Worst case: "unset nonomatch; print -N >" plus two is 29 */ len = STRLEN(tempname) + 29; if (shell_style == STYLE_VIMGLOB) len += STRLEN(sh_vimglob_func); for (i = 0; i < num_pat; ++i) { /* Count the length of the patterns in the same way as they are put in * "command" below. */ ++len; /* add space */ for (j = 0; pat[i][j] != NUL; ++j) { if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) ++len; /* may add a backslash */ ++len; } } command = xmalloc(len); /* * Build the shell command: * - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell * recognizes this). * - Add the shell command to print the expanded names. * - Add the temp file name. * - Add the file name patterns. */ if (shell_style == STYLE_BT) { /* change `command; command& ` to (command; command ) */ STRCPY(command, "("); STRCAT(command, pat[0] + 1); /* exclude first backtick */ p = command + STRLEN(command) - 1; *p-- = ')'; /* remove last backtick */ while (p > command && vim_iswhite(*p)) --p; if (*p == '&') { /* remove trailing '&' */ ampersent = TRUE; *p = ' '; } STRCAT(command, ">"); } else { if (flags & EW_NOTFOUND) STRCPY(command, "set nonomatch; "); else STRCPY(command, "unset nonomatch; "); if (shell_style == STYLE_GLOB) STRCAT(command, "glob >"); else if (shell_style == STYLE_PRINT) STRCAT(command, "print -N >"); else if (shell_style == STYLE_VIMGLOB) STRCAT(command, sh_vimglob_func); else STRCAT(command, "echo >"); } STRCAT(command, tempname); if (shell_style != STYLE_BT) for (i = 0; i < num_pat; ++i) { /* Put a backslash before special * characters, except inside ``. */ int intick = FALSE; p = command + STRLEN(command); *p++ = ' '; for (j = 0; pat[i][j] != NUL; ++j) { if (pat[i][j] == '`') intick = !intick; else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { /* Remove a backslash, take char literally. But keep * backslash inside backticks, before a special character * and before a backtick. */ if (intick || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL || pat[i][j + 1] == '`') *p++ = '\\'; ++j; } else if (!intick && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) /* Put a backslash before a special character, but not * when inside ``. And not for $var when EW_KEEPDOLLAR is * set. */ *p++ = '\\'; /* Copy one character. */ *p++ = pat[i][j]; } *p = NUL; } if (flags & EW_SILENT) { shellopts |= kShellOptHideMess; } if (ampersent) STRCAT(command, "&"); /* put the '&' after the redirection */ /* * Using zsh -G: If a pattern has no matches, it is just deleted from * the argument list, otherwise zsh gives an error message and doesn't * expand any other pattern. */ if (shell_style == STYLE_PRINT) extra_shell_arg = (char_u *)"-G"; /* Use zsh NULL_GLOB option */ /* * If we use -f then shell variables set in .cshrc won't get expanded. * vi can do it, so we will too, but it is only necessary if there is a "$" * in one of the patterns, otherwise we can still use the fast option. */ else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) extra_shell_arg = (char_u *)"-f"; /* Use csh fast option */ /* * execute the shell command */ i = call_shell( command, shellopts, extra_shell_arg ); /* When running in the background, give it some time to create the temp * file, but don't wait for it to finish. */ if (ampersent) os_delay(10L, true); free(command); if (i) { /* os_call_shell() failed */ os_remove((char *)tempname); free(tempname); /* * With interactive completion, the error message is not printed. */ if (!(flags & EW_SILENT)) { redraw_later_clear(); /* probably messed up screen */ msg_putchar('\n'); /* clear bottom line quickly */ #if SIZEOF_LONG > SIZEOF_INT assert(Rows <= (long)INT_MAX + 1); #endif cmdline_row = (int)(Rows - 1); /* continue on last line */ MSG(_(e_wildexpand)); msg_start(); /* don't overwrite this message */ } /* If a `cmd` expansion failed, don't list `cmd` as a match, even when * EW_NOTFOUND is given */ if (shell_style == STYLE_BT) return FAIL; goto notfound; } /* * read the names from the file into memory */ fd = fopen((char *)tempname, READBIN); if (fd == NULL) { /* Something went wrong, perhaps a file name with a special char. */ if (!(flags & EW_SILENT)) { MSG(_(e_wildexpand)); msg_start(); /* don't overwrite this message */ } free(tempname); goto notfound; } int fseek_res = fseek(fd, 0L, SEEK_END); if (fseek_res < 0) { free(tempname); fclose(fd); return FAIL; } long long templen = ftell(fd); /* get size of temp file */ if (templen < 0) { free(tempname); fclose(fd); return FAIL; } #if SIZEOF_LONG_LONG > SIZEOF_SIZE_T assert(templen <= (long long)SIZE_MAX); #endif len = (size_t)templen; fseek(fd, 0L, SEEK_SET); buffer = xmalloc(len + 1); // fread() doesn't terminate buffer with NUL; // appropiate termination (not always NUL) is done below. size_t readlen = fread((char *)buffer, 1, len, fd); fclose(fd); os_remove((char *)tempname); if (readlen != len) { /* unexpected read error */ EMSG2(_(e_notread), tempname); free(tempname); free(buffer); return FAIL; } free(tempname); /* file names are separated with Space */ if (shell_style == STYLE_ECHO) { buffer[len] = '\n'; /* make sure the buffer ends in NL */ p = buffer; for (i = 0; *p != '\n'; ++i) { /* count number of entries */ while (*p != ' ' && *p != '\n') ++p; p = skipwhite(p); /* skip to next entry */ } } /* file names are separated with NL */ else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { buffer[len] = NUL; /* make sure the buffer ends in NUL */ p = buffer; for (i = 0; *p != NUL; ++i) { /* count number of entries */ while (*p != '\n' && *p != NUL) ++p; if (*p != NUL) ++p; p = skipwhite(p); /* skip leading white space */ } } /* file names are separated with NUL */ else { /* * Some versions of zsh use spaces instead of NULs to separate * results. Only do this when there is no NUL before the end of the * buffer, otherwise we would never be able to use file names with * embedded spaces when zsh does use NULs. * When we found a NUL once, we know zsh is OK, set did_find_nul and * don't check for spaces again. */ check_spaces = FALSE; if (shell_style == STYLE_PRINT && !did_find_nul) { /* If there is a NUL, set did_find_nul, else set check_spaces */ buffer[len] = NUL; if (len && (int)STRLEN(buffer) < (int)len) did_find_nul = TRUE; else check_spaces = TRUE; } /* * Make sure the buffer ends with a NUL. For STYLE_PRINT there * already is one, for STYLE_GLOB it needs to be added. */ if (len && buffer[len - 1] == NUL) --len; else buffer[len] = NUL; i = 0; for (p = buffer; p < buffer + len; ++p) if (*p == NUL || (*p == ' ' && check_spaces)) { /* count entry */ ++i; *p = NUL; } if (len) ++i; /* count last entry */ } assert(buffer[len] == NUL || buffer[len] == '\n'); if (i == 0) { /* * Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". * /bin/sh will happily expand it to nothing rather than returning an * error; and hey, it's good to check anyway -- webb. */ free(buffer); goto notfound; } *num_file = i; *file = xmalloc(sizeof(char_u *) * (size_t)i); /* * Isolate the individual file names. */ p = buffer; for (i = 0; i < *num_file; ++i) { (*file)[i] = p; /* Space or NL separates */ if (shell_style == STYLE_ECHO || shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { while (!(shell_style == STYLE_ECHO && *p == ' ') && *p != '\n' && *p != NUL) ++p; if (p == buffer + len) /* last entry */ *p = NUL; else { *p++ = NUL; p = skipwhite(p); /* skip to next entry */ } } else { /* NUL separates */ while (*p && p < buffer + len) /* skip entry */ ++p; ++p; /* skip NUL */ } } /* * Move the file names to allocated memory. */ for (j = 0, i = 0; i < *num_file; ++i) { /* Require the files to exist. Helps when using /bin/sh */ if (!(flags & EW_NOTFOUND) && !os_file_exists((*file)[i])) continue; /* check if this entry should be included */ dir = (os_isdir((*file)[i])); if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) continue; /* Skip files that are not executable if we check for that. */ if (!dir && (flags & EW_EXEC) && !os_can_exe((*file)[i], NULL)) continue; p = xmalloc(STRLEN((*file)[i]) + 1 + dir); STRCPY(p, (*file)[i]); if (dir) add_pathsep(p); /* add '/' to a directory name */ (*file)[j++] = p; } free(buffer); *num_file = j; if (*num_file == 0) { /* rejected all entries */ free(*file); *file = NULL; goto notfound; } return OK; notfound: if (flags & EW_NOTFOUND) { save_patterns(num_pat, pat, num_file, file); return OK; } return FAIL; } static void save_patterns(int num_pat, char_u **pat, int *num_file, char_u ***file) { int i; char_u *s; *file = xmalloc((size_t)num_pat * sizeof(char_u *)); for (i = 0; i < num_pat; i++) { s = vim_strsave(pat[i]); /* Be compatible with expand_filename(): halve the number of * backslashes. */ backslash_halve(s); (*file)[i] = s; } *num_file = num_pat; } /* * Return TRUE if the string "p" contains a wildcard that mch_expandpath() can * expand. */ int mch_has_exp_wildcard(char_u *p) { for (; *p; mb_ptr_adv(p)) { if (*p == '\\' && p[1] != NUL) ++p; else if (vim_strchr((char_u *) "*?[{'" , *p) != NULL) return TRUE; } return FALSE; } /* * Return TRUE if the string "p" contains a wildcard. * Don't recognize '~' at the end as a wildcard. */ int mch_has_wildcard(char_u *p) { for (; *p; mb_ptr_adv(p)) { if (*p == '\\' && p[1] != NUL) ++p; else if (vim_strchr((char_u *) "*?[{`'$" , *p) != NULL || (*p == '~' && p[1] != NUL)) return TRUE; } return FALSE; } static int have_wildcard(int num, char_u **file) { int i; for (i = 0; i < num; i++) if (mch_has_wildcard(file[i])) return 1; return 0; } static int have_dollars(int num, char_u **file) { int i; for (i = 0; i < num; i++) if (vim_strchr(file[i], '$') != NULL) return TRUE; return FALSE; }