aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/options.txt26
-rw-r--r--runtime/lua/vim/_meta/options.lua4
-rw-r--r--src/nvim/cmdexpand.c29
-rw-r--r--src/nvim/cmdexpand_defs.h7
-rw-r--r--src/nvim/option.c11
-rw-r--r--src/nvim/options.lua4
-rw-r--r--test/old/testdir/test_cmdline.vim66
-rw-r--r--test/old/testdir/test_options.vim52
8 files changed, 171 insertions, 28 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 07f4eb80c5..e2af4d5bc1 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -136,10 +136,26 @@ To include white space in a string option value it has to be preceded with a
backslash. To include a backslash you have to use two. Effectively this
means that the number of backslashes in an option value is halved (rounded
down).
+In options 'path', 'cdpath', and 'tags', spaces have to be preceded with three
+backslashes instead becuase they can be separated by either commas or spaces.
+Comma-separated options like 'backupdir' and 'tags' will also require commas
+to be escaped with two backslashes, whereas this is not needed for
+non-comma-separated ones like 'makeprg'.
+When setting options using |:let| and |literal-string|, you need to use one
+fewer layer of backslash.
A few examples: >
- :set tags=tags\ /usr/tags results in "tags /usr/tags"
- :set tags=tags\\,file results in "tags\,file"
- :set tags=tags\\\ file results in "tags\ file"
+ :set makeprg=make\ file results in "make file"
+ :let &makeprg='make file' (same as above)
+ :set makeprg=make\\\ file results in "make\ file"
+ :set tags=tags\ /usr/tags results in "tags" and "/usr/tags"
+ :set tags=tags\\\ file results in "tags file"
+ :let &tags='tags\ file' (same as above)
+
+ :set makeprg=make,file results in "make,file"
+ :set makeprg=make\\,file results in "make\,file"
+ :set tags=tags,file results in "tags" and "file"
+ :set tags=tags\\,file results in "tags,file"
+ :let &tags='tags\,file' (same as above)
The "|" character separates a ":set" command from a following command. To
include the "|" in the option value, use "\|" instead. This example sets the
@@ -6426,8 +6442,8 @@ A jump table for the options with a short description can be found at |Q_op|.
'tags' 'tag' string (default "./tags;,tags")
global or local to buffer |global-local|
Filenames for the tag command, separated by spaces or commas. To
- include a space or comma in a file name, precede it with a backslash
- (see |option-backslash| about including spaces and backslashes).
+ include a space or comma in a file name, precede it with backslashes
+ (see |option-backslash| about including spaces/commas and backslashes).
When a file name starts with "./", the '.' is replaced with the path
of the current file. But only when the 'd' flag is not included in
'cpoptions'. Environment variables are expanded |:set_env|. Also see
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 4f6408b136..a4e0e61248 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -6877,8 +6877,8 @@ vim.go.tagrelative = vim.o.tagrelative
vim.go.tr = vim.go.tagrelative
--- Filenames for the tag command, separated by spaces or commas. To
---- include a space or comma in a file name, precede it with a backslash
---- (see `option-backslash` about including spaces and backslashes).
+--- include a space or comma in a file name, precede it with backslashes
+--- (see `option-backslash` about including spaces/commas and backslashes).
--- When a file name starts with "./", the '.' is replaced with the path
--- of the current file. But only when the 'd' flag is not included in
--- 'cpoptions'. Environment variables are expanded `:set_env`. Also see
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index c2469b6574..38be6573c8 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -156,8 +156,9 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
// and wildmatch characters, except '~'.
for (int 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], " ");
+ if (xp->xp_backslash & XP_BS_THREE) {
+ char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " ";
+ p = vim_strsave_escaped(files[i], pat);
xfree(files[i]);
files[i] = p;
#if defined(BACKSLASH_IN_FILENAME)
@@ -165,6 +166,14 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
xfree(files[i]);
files[i] = p;
#endif
+ } else if (xp->xp_backslash & XP_BS_COMMA) {
+ if (vim_strchr(files[i], ',') != NULL) {
+ p = vim_strsave_escaped(files[i], ",");
+ if (p != NULL) {
+ xfree(files[i]);
+ files[i] = p;
+ }
+ }
}
#ifdef BACKSLASH_IN_FILENAME
p = vim_strsave_fnameescape(files[i], vse_what);
@@ -2440,15 +2449,23 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
pat = xstrdup(pat);
for (int i = 0; pat[i]; i++) {
if (pat[i] == '\\') {
- if (xp->xp_backslash == XP_BS_THREE
+ 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] == ' ') {
+ } else if (xp->xp_backslash & XP_BS_ONE
+ && pat[i + 1] == ' ') {
STRMOVE(pat + i, pat + i + 1);
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == '\\'
+ && pat[i + 2] == ',') {
+ STRMOVE(pat + i, pat + i + 2);
+#ifdef BACKSLASH_IN_FILENAME
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == ',') {
+ STRMOVE(pat + i, pat + i + 1);
+#endif
}
}
}
diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h
index a302a32852..7c422aca18 100644
--- a/src/nvim/cmdexpand_defs.h
+++ b/src/nvim/cmdexpand_defs.h
@@ -40,9 +40,10 @@ typedef struct expand {
/// values for xp_backslash
enum {
- XP_BS_NONE = 0, ///< nothing special for backslashes
- XP_BS_ONE = 1, ///< uses one backslash before a space
- XP_BS_THREE = 2, ///< uses three backslashes before a space
+ XP_BS_NONE = 0, ///< nothing special for backslashes
+ XP_BS_ONE = 0x1, ///< uses one backslash before a space
+ XP_BS_THREE = 0x2, ///< uses three backslashes before a space
+ XP_BS_COMMA = 0x4, ///< commas need to be escaped with a backslash
};
/// values for xp_context when doing command line completion
diff --git a/src/nvim/option.c b/src/nvim/option.c
index c0353e52be..0700d0e87e 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -5496,6 +5496,9 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_backslash = XP_BS_ONE;
}
}
+ if (flags & P_COMMA) {
+ xp->xp_backslash |= XP_BS_COMMA;
+ }
}
// For an option that is a list of file names, or comma/colon-separated
@@ -5511,8 +5514,12 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
while (s > xp->xp_pattern && *(s - 1) == '\\') {
s--;
}
- if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3))
- || (*p == ',' && (flags & P_COMMA) && ((p - s) % 1) == 0)
+ if ((*p == ' ' && ((xp->xp_backslash & XP_BS_THREE) && (p - s) < 3))
+#if defined(BACKSLASH_IN_FILENAME)
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 1)
+#else
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 2)
+#endif
|| (*p == ':' && (flags & P_COLON))) {
xp->xp_pattern = p + 1;
break;
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 68d88ea0aa..d5f41c5a89 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -8679,8 +8679,8 @@ return {
deny_duplicates = true,
desc = [=[
Filenames for the tag command, separated by spaces or commas. To
- include a space or comma in a file name, precede it with a backslash
- (see |option-backslash| about including spaces and backslashes).
+ include a space or comma in a file name, precede it with backslashes
+ (see |option-backslash| about including spaces/commas and backslashes).
When a file name starts with "./", the '.' is replaced with the path
of the current file. But only when the 'd' flag is not included in
'cpoptions'. Environment variables are expanded |:set_env|. Also see
diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
index 54f5dd500b..90748a6076 100644
--- a/test/old/testdir/test_cmdline.vim
+++ b/test/old/testdir/test_cmdline.vim
@@ -1176,13 +1176,71 @@ func Test_cmdline_complete_various()
mapclear
delcom MyCmd
+ " Prepare for path completion
+ call mkdir('Xa b c', 'D')
+ defer delete('Xcomma,foobar.txt')
+ call writefile([], 'Xcomma,foobar.txt')
+
" completion for :set path= with multiple backslashes
- call feedkeys(":set path=a\\\\\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set path=a\\\ b', @:)
+ call feedkeys(':set path=Xa\\\ b' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set path=Xa\\\ b\\\ c/', @:)
+ set path&
" completion for :set dir= with a backslash
- call feedkeys(":set dir=a\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set dir=a\ b', @:)
+ call feedkeys(':set dir=Xa\ b' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dir=Xa\ b\ c/', @:)
+ set dir&
+
+ " completion for :set tags= / set dictionary= with escaped commas
+ if has('win32')
+ " In Windows backslashes are rounded up, so both '\,' and '\\,' escape to
+ " '\,'
+ call feedkeys(':set dictionary=Xcomma\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\,foobar.txt', @:)
+
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foobar.txt', @:)
+
+ call feedkeys(':set tags=Xcomma\\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\\\,foo', @:) " Didn't find a match
+
+ " completion for :set dictionary= with escaped commas (same behavior, but
+ " different internal code path from 'set tags=' for escaping the output)
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foobar.txt', @:)
+ else
+ " In other platforms, backslashes are rounded down (since '\,' itself will
+ " be escaped into ','). As a result '\\,' and '\\\,' escape to '\,'.
+ call feedkeys(':set tags=Xcomma\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foo', @:) " Didn't find a match
+
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\\,foobar.txt', @:)
+
+ call feedkeys(':set dictionary=Xcomma\\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\\,foobar.txt', @:)
+
+ " completion for :set dictionary= with escaped commas (same behavior, but
+ " different internal code path from 'set tags=' for escaping the output)
+ call feedkeys(':set dictionary=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\\,foobar.txt', @:)
+ endif
+ set tags&
+ set dictionary&
+
+ " completion for :set makeprg= with no escaped commas
+ call feedkeys(':set makeprg=Xcomma,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set makeprg=Xcomma,foobar.txt', @:)
+
+ if !has('win32')
+ " Cannot create file with backslash in file name in Windows, so only test
+ " this elsewhere.
+ defer delete('Xcomma\,fooslash.txt')
+ call writefile([], 'Xcomma\,fooslash.txt')
+ call feedkeys(':set makeprg=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set makeprg=Xcomma\\,fooslash.txt', @:)
+ endif
+ set makeprg&
" completion for the :py3 commands
call feedkeys(":py3\<C-A>\<C-B>\"\<CR>", 'xt')
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
index 76b7cc920b..cc3f21b86b 100644
--- a/test/old/testdir/test_options.vim
+++ b/test/old/testdir/test_options.vim
@@ -316,16 +316,60 @@ func Test_set_completion()
" Expand directories.
call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ', @:)
- call assert_notmatch('./small.vim ', @:)
+ call assert_match(' ./samples/ ', @:)
+ call assert_notmatch(' ./summarize.vim ', @:)
+ set cdpath&
" Expand files and directories.
call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:)
+ call assert_match(' ./samples/.* ./summarize.vim', @:)
call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
- set tags&
+
+ " Expand files with spaces/commas in them. Make sure we delimit correctly.
+ "
+ " 'tags' allow for for spaces/commas to both act as delimiters, with actual
+ " spaces requiring double escape, and commas need a single escape.
+ " 'dictionary' is a normal comma-separated option where only commas act as
+ " delimiters, and both space/comma need one single escape.
+ " 'makeprg' is a non-comma-separated option. Commas don't need escape.
+ defer delete('Xfoo Xspace.txt')
+ defer delete('Xsp_dummy')
+ defer delete('Xbar,Xcomma.txt')
+ defer delete('Xcom_dummy')
+ call writefile([], 'Xfoo Xspace.txt')
+ call writefile([], 'Xsp_dummy')
+ call writefile([], 'Xbar,Xcomma.txt')
+ call writefile([], 'Xcom_dummy')
+
+ call feedkeys(':set tags=./Xfoo\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./Xfoo\ Xsp_dummy', @:)
+ call feedkeys(':set tags=./Xfoo\\\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./Xfoo\\\ Xspace.txt', @:)
+ call feedkeys(':set dictionary=./Xfoo\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=./Xfoo\ Xspace.txt', @:)
+
+ call feedkeys(':set dictionary=./Xbar,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=./Xbar,Xcom_dummy', @:)
+ if has('win32')
+ " In Windows, '\,' is literal, see `:help filename-backslash`, so this
+ " means we treat it as one file name.
+ call feedkeys(':set dictionary=Xbar\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\,Xcomma.txt', @:)
+ else
+ " In other platforms, '\,' simply escape to ',', and indicate a delimiter
+ " to split into a separate file name. You need '\\,' to escape the comma
+ " as part of the file name.
+ call feedkeys(':set dictionary=Xbar\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\,Xcom_dummy', @:)
+
+ call feedkeys(':set dictionary=Xbar\\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\\,Xcomma.txt', @:)
+ endif
+ call feedkeys(":set makeprg=./Xbar,Xcom\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set makeprg=./Xbar,Xcomma.txt', @:)
+ set tags& dictionary& makeprg&
" Expanding the option names
call feedkeys(":set \<Tab>\<C-B>\"\<CR>", 'xt')