aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/provider/clipboard.vim5
-rw-r--r--runtime/doc/nvim_clipboard.txt2
-rw-r--r--src/nvim/eval.c15
-rw-r--r--src/nvim/ex_docmd.c8
-rw-r--r--src/nvim/os/env.c117
-rw-r--r--src/nvim/testdir/Makefile1
-rw-r--r--src/nvim/testdir/test68.in131
-rw-r--r--src/nvim/testdir/test68.ok77
-rw-r--r--test/functional/legacy/068_text_formatting_spec.lua207
-rw-r--r--test/functional/terminal/buffer_spec.lua13
-rw-r--r--test/functional/terminal/ex_terminal_spec.lua23
11 files changed, 325 insertions, 274 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index 9f1737639b..c7cb14ded7 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -52,6 +52,11 @@ elseif executable('lemonade')
let s:paste['+'] = 'lemonade paste'
let s:copy['*'] = 'lemonade copy'
let s:paste['*'] = 'lemonade paste'
+elseif executable('doitclient')
+ let s:copy['+'] = 'doitclient wclip'
+ let s:paste['+'] = 'doitclient wclip -r'
+ let s:copy['*'] = s:copy['+']
+ let s:paste['*'] = s:paste['+']
else
echom 'clipboard: No clipboard tool available. See :help nvim-clipboard'
finish
diff --git a/runtime/doc/nvim_clipboard.txt b/runtime/doc/nvim_clipboard.txt
index 258fc550f8..078382c7a7 100644
--- a/runtime/doc/nvim_clipboard.txt
+++ b/runtime/doc/nvim_clipboard.txt
@@ -24,6 +24,8 @@ is found in your `$PATH`.
- pbcopy/pbpaste (only for Mac OS X)
- lemonade (useful for SSH machine)
https://github.com/pocke/lemonade
+- doitclient (another option for SSH setups from the maintainer of PuTTY)
+ http://www.chiark.greenend.org.uk/~sgtatham/doit/
The presence of a suitable clipboard tool implicitly enables the '+' and '*'
registers.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 8c8881b398..712ee06b85 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -21701,6 +21701,18 @@ static void term_resize(uint16_t width, uint16_t height, void *d)
pty_process_resize(&data->proc.pty, width, height);
}
+static inline void term_delayed_free(void **argv)
+{
+ TerminalJobData *j = argv[0];
+ if (j->in.pending_reqs || j->out.pending_reqs || j->err.pending_reqs) {
+ queue_put(j->events, term_delayed_free, 1, j);
+ return;
+ }
+
+ terminal_destroy(j->term);
+ term_job_data_decref(j);
+}
+
static void term_close(void *d)
{
TerminalJobData *data = d;
@@ -21708,8 +21720,7 @@ static void term_close(void *d)
data->exited = true;
process_stop((Process *)&data->proc);
}
- terminal_destroy(data->term);
- term_job_data_decref(d);
+ queue_put(data->events, term_delayed_free, 1, data);
}
static void term_job_data_decref(TerminalJobData *data)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 5c6943f05b..870284a0f7 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -9499,12 +9499,14 @@ static void ex_folddo(exarg_T *eap)
static void ex_terminal(exarg_T *eap)
{
- // We will call termopen() with ['shell'] if not given a {cmd}.
- char *name = (char *)p_sh;
+ char *name = (char *)p_sh; // Default to 'shell' if {cmd} is not given.
+ bool mustfree = false;
char *lquote = "['";
char *rquote = "']";
+
if (*eap->arg != NUL) {
name = (char *)vim_strsave_escaped(eap->arg, (char_u *)"\"\\");
+ mustfree = true;
lquote = rquote = "\"";
}
@@ -9514,7 +9516,7 @@ static void ex_terminal(exarg_T *eap)
eap->forceit==TRUE ? "!" : "", lquote, name, rquote);
do_cmdline_cmd(ex_cmd);
- if (name != (char *)p_sh) {
+ if (mustfree) {
xfree(name);
}
}
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 384a17004e..edc430410c 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -147,7 +147,7 @@ static char_u *homedir = NULL;
void init_homedir(void)
{
- /* In case we are called a second time (when 'encoding' changes). */
+ // In case we are called a second time (when 'encoding' changes).
xfree(homedir);
homedir = NULL;
@@ -176,16 +176,16 @@ void init_homedir(void)
if (var != NULL) {
#ifdef UNIX
- /*
- * Change to the directory and get the actual path. This resolves
- * links. Don't do it when we can't return.
- */
+ // Change to the directory and get the actual path. This resolves
+ // links. Don't do it when we can't return.
if (os_dirname(NameBuff, MAXPATHL) == OK
&& os_chdir((char *)NameBuff) == 0) {
- if (!os_chdir((char *)var) && os_dirname(IObuff, IOSIZE) == OK)
+ if (!os_chdir((char *)var) && os_dirname(IObuff, IOSIZE) == OK) {
var = IObuff;
- if (os_chdir((char *)NameBuff) != 0)
+ }
+ if (os_chdir((char *)NameBuff) != 0) {
EMSG(_(e_prev_dir));
+ }
}
#endif
homedir = vim_strsave(var);
@@ -239,29 +239,29 @@ void expand_env(char_u *src, char_u *dst, int dstlen)
/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
/// If anything fails no expansion is done and dst equals src.
-/// startstr recognize the start of a new name, for '~' expansion.
+/// prefix recognize the start of a new name, for '~' expansion.
/// @param srcp Input string e.g. "$HOME/vim.hlp"
/// @param dst Where to put the result
/// @param dstlen Maximum length of the result
/// @param esc Should we escape spaces in expanded variables?
/// @param one Should we expand more than one '~'?
-/// @param startstr Common prefix for paths, can be NULL
-void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
- char_u *startstr)
+/// @param prefix Common prefix for paths, can be NULL
+void expand_env_esc(char_u *restrict srcp,
+ char_u *restrict dst,
+ int dstlen,
+ bool esc,
+ bool one,
+ char_u *prefix)
{
- char_u *src;
char_u *tail;
- int c;
char_u *var;
bool copy_char;
bool mustfree; // var was allocated, need to free it later
bool at_start = true; // at start of a name
- int startstr_len = 0;
- if (startstr != NULL)
- startstr_len = (int)STRLEN(startstr);
+ int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix);
- src = skipwhite(srcp);
+ char_u *src = skipwhite(srcp);
dstlen--; // leave one char space for "\,"
while (*src && dstlen > 0) {
// Skip over `=expr`.
@@ -281,6 +281,7 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
dstlen -= (int)len;
continue;
}
+
copy_char = true;
if ((*src == '$') || (*src == '~' && at_start)) {
mustfree = false;
@@ -290,14 +291,15 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
if (*src != '~') { // environment var
tail = src + 1;
var = dst;
- c = dstlen - 1;
+ int c = dstlen - 1;
#ifdef UNIX
// Unix has ${var-name} type environment vars
if (*tail == '{' && !vim_isIDc('{')) {
- tail++; /* ignore '{' */
- while (c-- > 0 && *tail && *tail != '}')
+ tail++; // ignore '{'
+ while (c-- > 0 && *tail != NUL && *tail != '}') {
*var++ = *tail++;
+ }
} else // NOLINT
#endif
{
@@ -321,7 +323,7 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
#if defined(UNIX)
}
#endif
- } else if ( src[1] == NUL /* home directory */
+ } else if (src[1] == NUL // home directory
|| vim_ispathsep(src[1])
|| vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) {
var = homedir;
@@ -331,12 +333,13 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
// Copy ~user to dst[], so we can put a NUL after it.
tail = src;
var = dst;
- c = dstlen - 1;
- while ( c-- > 0
- && *tail
- && vim_isfilec(*tail)
- && !vim_ispathsep(*tail))
+ int c = dstlen - 1;
+ while (c-- > 0
+ && *tail
+ && vim_isfilec(*tail)
+ && !vim_ispathsep(*tail)) {
*var++ = *tail++;
+ }
*var = NUL;
// Use os_get_user_directory() to get the user directory.
// If this function fails, the shell is used to
@@ -344,8 +347,7 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
// does not support ~user (old versions of /bin/sh).
var = (char_u *)os_get_user_directory((char *)dst + 1);
mustfree = true;
- if (var == NULL)
- {
+ if (var == NULL) {
expand_T xpc;
ExpandInit(&xpc);
@@ -381,8 +383,9 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) {
char_u *p = vim_strsave_escaped(var, (char_u *)" \t");
- if (mustfree)
+ if (mustfree) {
xfree(var);
+ }
var = p;
mustfree = true;
}
@@ -391,7 +394,7 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
&& (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) {
STRCPY(dst, var);
dstlen -= (int)STRLEN(var);
- c = (int)STRLEN(var);
+ int c = (int)STRLEN(var);
// if var[] ends in a path separator and tail[] starts
// with it, skip a character
if (*var != NUL && after_pathsep((char *)dst, (char *)dst + c)
@@ -404,8 +407,9 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
src = tail;
copy_char = false;
}
- if (mustfree)
+ if (mustfree) {
xfree(var);
+ }
}
if (copy_char) { // copy at least one char
@@ -422,9 +426,10 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
*dst++ = *src++;
--dstlen;
- if (startstr != NULL && src - startstr_len >= srcp
- && STRNCMP(src - startstr_len, startstr, startstr_len) == 0)
+ if (prefix != NULL && src - prefix_len >= srcp
+ && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) {
at_start = true;
+ }
}
}
*dst = NUL;
@@ -451,17 +456,37 @@ static char *vim_version_dir(const char *vimdir)
return NULL;
}
-/// If the string between "p" and "pend" ends in "name/", return "pend" minus
-/// the length of "name/". Otherwise return "pend".
-static char *remove_tail(char *p, char *pend, char *name)
+/// If `dirname + "/"` precedes `pend` in the path, return the pointer to
+/// `dirname + "/" + pend`. Otherwise return `pend`.
+///
+/// Examples (path = /usr/local/share/nvim/runtime/doc/help.txt):
+///
+/// pend = help.txt
+/// dirname = doc
+/// -> doc/help.txt
+///
+/// pend = doc/help.txt
+/// dirname = runtime
+/// -> runtime/doc/help.txt
+///
+/// pend = runtime/doc/help.txt
+/// dirname = vim74
+/// -> runtime/doc/help.txt
+///
+/// @param path Path to a file
+/// @param pend A suffix of the path
+/// @param dirname The immediate path fragment before the pend
+/// @return The new pend including dirname or just pend
+static char *remove_tail(char *path, char *pend, char *dirname)
{
- size_t len = STRLEN(name) + 1;
- char *newend = pend - len;
+ size_t len = STRLEN(dirname);
+ char *new_tail = pend - len - 1;
- if (newend >= p
- && fnamencmp((char_u *)newend, (char_u *)name, len - 1) == 0
- && (newend == p || after_pathsep(p, newend)))
- return newend;
+ if (new_tail >= path
+ && fnamencmp((char_u *)new_tail, (char_u *)dirname, len) == 0
+ && (new_tail == path || after_pathsep(path, new_tail))) {
+ return new_tail;
+ }
return pend;
}
@@ -745,9 +770,10 @@ void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
/// @param src Input file name
char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
{
- size_t len = 3; /* space for "~/" and trailing NUL */
- if (src != NULL) /* just in case */
+ size_t len = 3; // space for "~/" and trailing NUL
+ if (src != NULL) { // just in case
len += STRLEN(src);
+ }
char_u *dst = xmalloc(len);
home_replace(buf, src, dst, (int)len, true);
return dst;
@@ -783,8 +809,7 @@ char_u *get_env_name(expand_T *xp, int idx)
STRLCPY(name, envname, ENVNAMELEN);
xfree(envname);
return name;
- } else {
- return NULL;
}
+ return NULL;
}
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 82c7cd4de9..b763a67347 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -28,7 +28,6 @@ SCRIPTS := \
test53.out \
test55.out \
test64.out \
- test68.out \
test69.out \
test73.out \
test79.out \
diff --git a/src/nvim/testdir/test68.in b/src/nvim/testdir/test68.in
deleted file mode 100644
index ca54e942b5..0000000000
--- a/src/nvim/testdir/test68.in
+++ /dev/null
@@ -1,131 +0,0 @@
-Test for text formatting.
-
-Results of test68:
-
-STARTTEST
-:so small.vim
-/^{/+1
-:set noai tw=2 fo=t
-gRa b
-ENDTEST
-
-{
-
-
-}
-
-STARTTEST
-/^{/+1
-:set ai tw=2 fo=tw
-gqgqjjllab
-ENDTEST
-
-{
-a b
-
-a
-}
-
-STARTTEST
-/^{/+1
-:set tw=3 fo=t
-gqgqo
-a 
-ENDTEST
-
-{
-a 
-}
-
-STARTTEST
-/^{/+1
-:set tw=2 fo=tcq1 comments=:#
-gqgqjgqgqo
-a b
-#a b
-ENDTEST
-
-{
-a b
-#a b
-}
-
-STARTTEST
-/^{/+1
-:set tw=5 fo=tcn comments=:#
-A bjA b
-ENDTEST
-
-{
- 1 a
-# 1 a
-}
-
-STARTTEST
-/^{/+3
-:set tw=5 fo=t2a si
-i A_
-ENDTEST
-
-{
-
- x a
- b
- c
-
-}
-
-STARTTEST
-/^{/+1
-:set tw=5 fo=qn comments=:#
-gwap
-ENDTEST
-
-{
-# 1 a b
-}
-
-STARTTEST
-/^{/+1
-:set tw=5 fo=q2 comments=:#
-gwap
-ENDTEST
-
-{
-# x
-# a b
-}
-
-STARTTEST
-/^{/+2
-:set tw& fo=a
-I^^
-ENDTEST
-
-{
- 1aa
- 2bb
-}
-
-STARTTEST
-/mno pqr/
-:setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/
-A vwx yz
-ENDTEST
-
-/* abc def ghi jkl
- * mno pqr stu
- */
-
-STARTTEST
-/^#/
-:setl tw=12 fo=tqnc comments=:#
-A foobar
-ENDTEST
-
-# 1 xxxxx
-
-STARTTEST
-:g/^STARTTEST/.,/^ENDTEST/d
-:1;/^Results/,$wq! test.out
-ENDTEST
diff --git a/src/nvim/testdir/test68.ok b/src/nvim/testdir/test68.ok
deleted file mode 100644
index b3726a0a27..0000000000
--- a/src/nvim/testdir/test68.ok
+++ /dev/null
@@ -1,77 +0,0 @@
-Results of test68:
-
-
-{
-a
-b
-}
-
-
-{
-a
-b
-
-a
-b
-}
-
-
-{
-a
-
-
-a
-
-}
-
-
-{
-a b
-#a b
-
-a b
-#a b
-}
-
-
-{
- 1 a
- b
-# 1 a
-# b
-}
-
-
-{
-
- x a
- b_
- c
-
-}
-
-
-{
-# 1 a
-# b
-}
-
-
-{
-# x a
-# b
-}
-
-
-{ 1aa ^^2bb }
-
-
-/* abc def ghi jkl
- * mno pqr stu
- * vwx yz
- */
-
-
-# 1 xxxxx
-# foobar
-
diff --git a/test/functional/legacy/068_text_formatting_spec.lua b/test/functional/legacy/068_text_formatting_spec.lua
new file mode 100644
index 0000000000..cac8be77f3
--- /dev/null
+++ b/test/functional/legacy/068_text_formatting_spec.lua
@@ -0,0 +1,207 @@
+local helpers = require('test.functional.helpers')
+local feed, insert = helpers.feed, helpers.insert
+local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect
+
+describe('text formatting', function()
+ setup(clear)
+
+ it('is working', function()
+ -- The control character <C-A> (byte \x01) needs to be put in the buffer
+ -- directly. But the insert function sends the text to nvim in insert
+ -- mode so it has to be escaped with <C-V>.
+ insert([[
+ Results of test68:
+
+
+ {
+
+
+ }
+
+
+ {
+ a b
+
+ a
+ }
+
+
+ {
+ a 
+ }
+
+
+ {
+ a b
+ #a b
+ }
+
+
+ {
+ 1 a
+ # 1 a
+ }
+
+
+ {
+
+ x a
+ b
+ c
+
+ }
+
+
+ {
+ # 1 a b
+ }
+
+
+ {
+ # x
+ # a b
+ }
+
+
+ {
+ 1aa
+ 2bb
+ }
+
+
+ /* abc def ghi jkl
+ * mno pqr stu
+ */
+
+
+ # 1 xxxxx
+ ]])
+
+ execute('/^{/+1')
+ execute('set noai tw=2 fo=t')
+ feed('gRa b<esc>')
+
+ execute('/^{/+1')
+ execute('set ai tw=2 fo=tw')
+ feed('gqgqjjllab<esc>')
+
+ execute('/^{/+1')
+ execute('set tw=3 fo=t')
+ feed('gqgqo<cr>')
+ feed('a <C-V><C-A><esc><esc>')
+
+ execute('/^{/+1')
+ execute('set tw=2 fo=tcq1 comments=:#')
+ feed('gqgqjgqgqo<cr>')
+ feed('a b<cr>')
+ feed('#a b<esc>')
+
+ execute('/^{/+1')
+ execute('set tw=5 fo=tcn comments=:#')
+ feed('A b<esc>jA b<esc>')
+
+ execute('/^{/+3')
+ execute('set tw=5 fo=t2a si')
+ feed('i <esc>A_<esc>')
+
+ execute('/^{/+1')
+ execute('set tw=5 fo=qn comments=:#')
+ feed('gwap<cr>')
+
+ execute('/^{/+1')
+ execute('set tw=5 fo=q2 comments=:#')
+ feed('gwap<cr>')
+
+ execute('/^{/+2')
+ execute('set tw& fo=a')
+ feed('I^^<esc><esc>')
+
+ execute('/mno pqr/')
+ execute('setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/')
+ feed('A vwx yz<esc>')
+
+ execute('/^#/')
+ execute('setl tw=12 fo=tqnc comments=:#')
+ feed('A foobar<esc>')
+
+ -- Assert buffer contents.
+ expect([[
+ Results of test68:
+
+
+ {
+ a
+ b
+ }
+
+
+ {
+ a
+ b
+
+ a
+ b
+ }
+
+
+ {
+ a
+ 
+
+ a
+ 
+ }
+
+
+ {
+ a b
+ #a b
+
+ a b
+ #a b
+ }
+
+
+ {
+ 1 a
+ b
+ # 1 a
+ # b
+ }
+
+
+ {
+
+ x a
+ b_
+ c
+
+ }
+
+
+ {
+ # 1 a
+ # b
+ }
+
+
+ {
+ # x a
+ # b
+ }
+
+
+ { 1aa ^^2bb }
+
+
+ /* abc def ghi jkl
+ * mno pqr stu
+ * vwx yz
+ */
+
+
+ # 1 xxxxx
+ # foobar
+ ]])
+ end)
+end)
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 55ef254a63..cefb603a7e 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -158,8 +158,7 @@ describe('terminal buffer', function()
end)
it('handles loss of focus gracefully', function()
- -- Temporarily change the statusline to avoid printing the file name, which
- -- varies be where the test is run.
+ -- Change the statusline to avoid printing the file name, which varies.
nvim('set_option', 'statusline', '==========')
execute('set laststatus=0')
@@ -195,5 +194,15 @@ describe('terminal buffer', function()
execute('set laststatus=1') -- Restore laststatus to the default.
end)
+
+ it('term_close() use-after-free #4393', function()
+ if eval("executable('yes')") == 0 then
+ pending('missing "yes" command')
+ return
+ end
+ execute('terminal yes')
+ feed([[<C-\><C-n>]])
+ execute('bdelete!')
+ end)
end)
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index 493539b4d3..d89092ff27 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -1,15 +1,15 @@
local helpers = require('test.functional.helpers')
local Screen = require('test.functional.ui.screen')
local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim
-local nvim_dir = helpers.nvim_dir
-local execute = helpers.execute
+local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq
+local execute, eval = helpers.execute, helpers.eval
describe(':terminal', function()
local screen
before_each(function()
clear()
- screen = Screen.new(50, 7)
+ screen = Screen.new(50, 4)
screen:attach(false)
nvim('set_option', 'shell', nvim_dir..'/shell-test')
nvim('set_option', 'shellcmdflag', 'EXE')
@@ -23,9 +23,6 @@ describe(':terminal', function()
ready $ |
[Process exited 0] |
|
- |
- |
- |
-- TERMINAL -- |
]])
end)
@@ -37,9 +34,6 @@ describe(':terminal', function()
ready $ echo hi |
|
[Process exited 0] |
- |
- |
- |
-- TERMINAL -- |
]])
end)
@@ -51,10 +45,15 @@ describe(':terminal', function()
ready $ echo 'hello' \ "world" |
|
[Process exited 0] |
- |
- |
- |
-- TERMINAL -- |
]])
end)
+
+ it('ex_terminal() double-free #4554', function()
+ source([[
+ autocmd BufNew * set shell=foo
+ terminal]])
+ -- Verify that BufNew actually fired (else the test is invalid).
+ eq('foo', eval('&shell'))
+ end)
end)