diff options
-rw-r--r-- | src/nvim/path.c | 29 | ||||
-rw-r--r-- | test/unit/path_spec.moon | 59 |
2 files changed, 88 insertions, 0 deletions
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/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) |