diff options
-rw-r--r-- | src/nvim/ex_cmds.c | 68 | ||||
-rw-r--r-- | src/nvim/option.c | 26 | ||||
-rw-r--r-- | src/nvim/path.c | 29 | ||||
-rw-r--r-- | src/nvim/version.c | 2 | ||||
-rw-r--r-- | test/unit/path_spec.moon | 59 |
5 files changed, 130 insertions, 54 deletions
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0a26026d7b..48e75190aa 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1307,52 +1307,58 @@ do_shell ( apply_autocmds(EVENT_SHELLCMDPOST, NULL, NULL, FALSE, curbuf); } -/* - * Create a shell command from a command string, input redirection file and - * output redirection file. - * Returns an allocated string with the shell command. - */ -char_u * -make_filter_cmd ( - char_u *cmd, /* command */ - char_u *itmp, /* NULL or name of input file */ - char_u *otmp /* NULL or name of output file */ -) +/// Create a shell command from a command string, input redirection file and +/// output redirection file. +/// +/// @param cmd Command to execute. +/// @param itmp NULL or the input file. +/// @param otmp NULL or the output file. +/// @returns an allocated string with the shell command. +char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp) { - size_t len = STRLEN(cmd) + 3; /* "()" + NUL */ + bool is_fish_shell = +#if defined(UNIX) + STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; +#else + false; +#endif + + size_t len = STRLEN(cmd) + 1; // At least enough space for cmd + NULL. + + len += is_fish_shell ? sizeof("begin; ""; end") - 1 + : sizeof("("")") - 1; + if (itmp != NULL) - len += STRLEN(itmp) + 9; /* " { < " + " } " */ + len += STRLEN(itmp) + sizeof(" { "" < "" } ") - 1; if (otmp != NULL) - len += STRLEN(otmp) + STRLEN(p_srr) + 2; /* " " */ + len += STRLEN(otmp) + STRLEN(p_srr) + 2; // two extra spaces (" "), char_u *buf = xmalloc(len); #if defined(UNIX) - /* - * Put braces around the command (for concatenated commands) when - * redirecting input and/or output. - */ - if (itmp != NULL || otmp != NULL) - vim_snprintf((char *)buf, len, "(%s)", (char *)cmd); - else + // Put delimiters around the command (for concatenated commands) when + // redirecting input and/or output. + if (itmp != NULL || otmp != NULL) { + char *fmt = is_fish_shell ? "begin; %s; end" + : "(%s)"; + vim_snprintf((char *)buf, len, fmt, (char *)cmd); + } else { STRCPY(buf, cmd); + } + if (itmp != NULL) { STRCAT(buf, " < "); STRCAT(buf, itmp); } #else - /* - * for shells that don't understand braces around commands, at least allow - * the use of commands in a pipe. - */ + // For shells that don't understand braces around commands, at least allow + // the use of commands in a pipe. STRCPY(buf, cmd); if (itmp != NULL) { char_u *p; - /* - * If there is a pipe, we have to put the '<' in front of it. - * Don't do this when 'shellquote' is not empty, otherwise the - * redirection would be inside the quotes. - */ + // If there is a pipe, we have to put the '<' in front of it. + // Don't do this when 'shellquote' is not empty, otherwise the + // redirection would be inside the quotes. if (*p_shq == NUL) { p = vim_strchr(buf, '|'); if (p != NULL) @@ -1363,7 +1369,7 @@ make_filter_cmd ( if (*p_shq == NUL) { p = vim_strchr(cmd, '|'); if (p != NULL) { - STRCAT(buf, " "); /* insert a space before the '|' for DOS */ + STRCAT(buf, " "); // Insert a space before the '|' for DOS STRCAT(buf, p); } } diff --git a/src/nvim/option.c b/src/nvim/option.c index ebbba28b9d..6dc3234dea 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2359,7 +2359,6 @@ void set_init_3(void) * This is done after other initializations, where 'shell' might have been * set, but only if they have not been set before. */ - char_u *p; int idx_srr; int do_srr; int idx_sp; @@ -2376,28 +2375,10 @@ void set_init_3(void) else do_sp = !(options[idx_sp].flags & P_WAS_SET); - /* - * Isolate the name of the shell: - * - Skip beyond any path. E.g., "/usr/bin/csh -f" -> "csh -f". - * - Remove any argument. E.g., "csh -f" -> "csh". - * But don't allow a space in the path, so that this works: - * "/usr/bin/csh --rcfile ~/.cshrc" - * But don't do that for Windows, it's common to have a space in the path. - */ - p = skiptowhite(p_sh); - if (*p == NUL) { - /* No white space, use the tail. */ - p = vim_strsave(path_tail(p_sh)); - } else { - char_u *p1, *p2; + size_t len = 0; + char_u *p = (char_u *)invocation_path_tail(p_sh, &len); + p = vim_strnsave(p, len); - /* Find the last path separator before the space. */ - p1 = p_sh; - for (p2 = p_sh; p2 < p; mb_ptr_adv(p2)) - if (vim_ispathsep(*p2)) - p1 = p2 + 1; - p = vim_strnsave(p1, (int)(p - p1)); - } { /* * Default for p_sp is "| tee", for p_srr is ">". @@ -2421,6 +2402,7 @@ void set_init_3(void) || fnamecmp(p, "zsh") == 0 || fnamecmp(p, "zsh-beta") == 0 || fnamecmp(p, "bash") == 0 + || fnamecmp(p, "fish") == 0 ) { if (do_sp) { p_sp = (char_u *)"2>&1| tee"; diff --git a/src/nvim/path.c b/src/nvim/path.c index dbbb77a8e9..2d819b39a4 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -131,6 +131,35 @@ char_u *path_tail_with_sep(char_u *fname) return tail; } +/// Finds the path tail (or executable) in an invocation. +/// +/// @param[in] invocation A program invocation in the form: +/// "path/to/exe [args]". +/// @param[out] len Stores the length of the executable name. +/// +/// @post if `len` is not null, stores the length of the executable name. +/// +/// @return The position of the last path separator + 1. +const char_u *invocation_path_tail(const char_u *invocation, size_t *len) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) +{ + const char_u *tail = get_past_head((char_u *) invocation); + const char_u *p = tail; + while (*p != NUL && *p != ' ') { + bool was_sep = vim_ispathsep_nocolon(*p); + mb_ptr_adv(p); + if (was_sep) { + tail = p; // Now tail points one past the separator. + } + } + + if (len != NULL) { + *len = (size_t)(p - tail); + } + + return tail; +} + /// Get the next path component of a path name. /// /// @param fname A file path. (Must be != NULL.) diff --git a/src/nvim/version.c b/src/nvim/version.c index a92a5e9f82..052248a719 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -319,7 +319,7 @@ static int included_patches[] = { //279, //278, 277, - //276, + 276, 275, 274, //273 NA diff --git a/test/unit/path_spec.moon b/test/unit/path_spec.moon index 0786fe6c01..f7e5ed00bc 100644 --- a/test/unit/path_spec.moon +++ b/test/unit/path_spec.moon @@ -114,6 +114,65 @@ describe 'path function', -> it 'returns the whole file name if there is no separator', -> eq 'file.txt', path_tail_with_sep 'file.txt' + describe 'invocation_path_tail', -> + -- Returns the path tail and length (out param) of the tail. + -- Does not convert the tail from C-pointer to lua string for use with + -- strcmp. + invocation_path_tail = (invk) -> + plen = ffi.new 'size_t[?]', 1 + ptail = path.invocation_path_tail (to_cstr invk), plen + neq NULL, ptail + + -- it does not change the output if len==NULL + tail2 = path.invocation_path_tail (to_cstr invk), NULL + neq NULL, tail2 + eq (ffi.string ptail), (ffi.string tail2) + + ptail, plen[0] + + -- This test mimics the intended use in C. + compare = (base, pinvk, len) -> + eq 0, (ffi.C.strncmp (to_cstr base), pinvk, len) + + it 'returns the executable name of an invocation given a relative invocation', -> + invk, len = invocation_path_tail 'directory/exe a b c' + compare "exe a b c", invk, len + eq 3, len + + it 'returns the executable name of an invocation given an absolute invocation', -> + if ffi.os == 'Windows' + invk, len = invocation_path_tail 'C:\\Users\\anyone\\Program Files\\z a b' + compare 'z a b', invk, len + eq 1, len + else + invk, len = invocation_path_tail '/usr/bin/z a b' + compare 'z a b', invk, len + eq 1, len + + it 'does not count arguments to the executable as part of its path', -> + invk, len = invocation_path_tail 'exe a/b\\c' + compare "exe a/b\\c", invk, len + eq 3, len + + it 'only accepts whitespace as a terminator for the executable name', -> + invk, len = invocation_path_tail 'exe-a+b_c[]()|#!@$%^&*' + eq 'exe-a+b_c[]()|#!@$%^&*', (ffi.string invk) + + it 'is equivalent to path_tail when args do not contain a path separator', -> + ptail = path.path_tail to_cstr "a/b/c x y z" + neq NULL, ptail + tail = ffi.string ptail + + invk, len = invocation_path_tail "a/b/c x y z" + eq tail, ffi.string invk + + it 'is not equivalent to path_tail when args contain a path separator', -> + ptail = path.path_tail to_cstr "a/b/c x y/z" + neq NULL, ptail + + invk, len = invocation_path_tail "a/b/c x y/z" + neq (ffi.string ptail), (ffi.string invk) + describe 'path_next_component', -> path_next_component = (file) -> res = path.path_next_component (to_cstr file) |