mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Clean up variable expansion, and properly handle embedded NULs
This commit is contained in:
parent
f71b10df8c
commit
9419191aa6
4 changed files with 199 additions and 117 deletions
119
expand.cpp
119
expand.cpp
|
@ -1049,21 +1049,24 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
|
||||||
fewer string scans and overall just less work. But until that
|
fewer string scans and overall just less work. But until that
|
||||||
happens, don't edit it unless you know exactly what you are doing,
|
happens, don't edit it unless you know exactly what you are doing,
|
||||||
and do proper testing afterwards.
|
and do proper testing afterwards.
|
||||||
|
|
||||||
|
This function operates on strings backwards, starting at last_idx.
|
||||||
*/
|
*/
|
||||||
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors);
|
static int expand_variables(parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
|
||||||
|
|
||||||
static int expand_variables2(parser_t &parser, const wcstring &instr, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
|
|
||||||
{
|
{
|
||||||
wchar_t *in = wcsdup(instr.c_str());
|
// We permit last_idx to be beyond the end of the string if and only if the string is empty
|
||||||
int result = expand_variables_internal(parser, in, out, last_idx, errors);
|
assert(instr.empty() || (last_idx >= 0 && (size_t)last_idx < instr.size()));
|
||||||
free(in);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx, parse_error_list_t *errors)
|
// Make this explicit
|
||||||
{
|
if (instr.empty())
|
||||||
int is_ok= 1;
|
{
|
||||||
int empty=0;
|
append_completion(out, instr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_ok = true;
|
||||||
|
bool empty = false;
|
||||||
|
const size_t insize = instr.size();
|
||||||
|
|
||||||
wcstring var_tmp;
|
wcstring var_tmp;
|
||||||
|
|
||||||
|
@ -1077,7 +1080,7 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
|
|
||||||
for (long i=last_idx; (i>=0) && is_ok && !empty; i--)
|
for (long i=last_idx; (i>=0) && is_ok && !empty; i--)
|
||||||
{
|
{
|
||||||
const wchar_t c = in[i];
|
const wchar_t c = instr.at(i);
|
||||||
if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE))
|
if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE))
|
||||||
{
|
{
|
||||||
long start_pos = i+1;
|
long start_pos = i+1;
|
||||||
|
@ -1087,17 +1090,15 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
|
|
||||||
stop_pos = start_pos;
|
stop_pos = start_pos;
|
||||||
|
|
||||||
while (1)
|
while (stop_pos < insize)
|
||||||
{
|
{
|
||||||
const wchar_t nc = in[stop_pos];
|
const wchar_t nc = instr.at(stop_pos);
|
||||||
if (!(nc))
|
|
||||||
break;
|
|
||||||
if (nc == VARIABLE_EXPAND_EMPTY)
|
if (nc == VARIABLE_EXPAND_EMPTY)
|
||||||
{
|
{
|
||||||
stop_pos++;
|
stop_pos++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!(wcsvarchr(nc)))
|
if (!wcsvarchr(nc))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
stop_pos++;
|
stop_pos++;
|
||||||
|
@ -1109,13 +1110,13 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
|
|
||||||
if (var_len == 0)
|
if (var_len == 0)
|
||||||
{
|
{
|
||||||
expand_variable_error(parser, in, stop_pos-1, -1, errors);
|
expand_variable_error(parser, instr, stop_pos-1, -1, errors);
|
||||||
|
|
||||||
is_ok = 0;
|
is_ok = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var_tmp.append(in + start_pos, var_len);
|
var_tmp.append(instr, start_pos, var_len);
|
||||||
env_var_t var_val;
|
env_var_t var_val;
|
||||||
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY)
|
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY)
|
||||||
{
|
{
|
||||||
|
@ -1133,22 +1134,22 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
|
|
||||||
if (is_ok)
|
if (is_ok)
|
||||||
{
|
{
|
||||||
tokenize_variable_array(var_val.c_str(), var_item_list);
|
tokenize_variable_array(var_val, var_item_list);
|
||||||
|
|
||||||
const size_t slice_start = stop_pos;
|
const size_t slice_start = stop_pos;
|
||||||
if (in[slice_start] == L'[')
|
if (slice_start < insize && instr.at(slice_start) == L'[')
|
||||||
{
|
{
|
||||||
wchar_t *slice_end;
|
wchar_t *slice_end;
|
||||||
size_t bad_pos;
|
size_t bad_pos;
|
||||||
all_vars=0;
|
all_vars=0;
|
||||||
|
const wchar_t *in = instr.c_str();
|
||||||
bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size());
|
bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, var_item_list.size());
|
||||||
if (bad_pos != 0)
|
if (bad_pos != 0)
|
||||||
{
|
{
|
||||||
append_syntax_error(errors,
|
append_syntax_error(errors,
|
||||||
stop_pos + bad_pos,
|
stop_pos + bad_pos,
|
||||||
L"Invalid index value");
|
L"Invalid index value");
|
||||||
is_ok = 0;
|
is_ok = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
stop_pos = (slice_end-in);
|
stop_pos = (slice_end-in);
|
||||||
|
@ -1168,7 +1169,7 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
append_syntax_error(errors,
|
append_syntax_error(errors,
|
||||||
slice_start + var_src_pos,
|
slice_start + var_src_pos,
|
||||||
ARRAY_BOUNDS_ERR);
|
ARRAY_BOUNDS_ERR);
|
||||||
is_ok=0;
|
is_ok = false;
|
||||||
var_idx_list.resize(j);
|
var_idx_list.resize(j);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1187,14 +1188,12 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
|
|
||||||
if (is_ok)
|
if (is_ok)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (is_single)
|
if (is_single)
|
||||||
{
|
{
|
||||||
in[i]=0;
|
wcstring res(instr, 0, i);
|
||||||
wcstring res = in;
|
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
if (in[i-1] != VARIABLE_EXPAND_SINGLE)
|
if (instr.at(i-1) != VARIABLE_EXPAND_SINGLE)
|
||||||
{
|
{
|
||||||
res.push_back(INTERNAL_SEPARATOR);
|
res.push_back(INTERNAL_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
@ -1215,15 +1214,16 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
res.append(next);
|
res.append(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.append(in + stop_pos);
|
assert(stop_pos <= insize);
|
||||||
is_ok &= expand_variables2(parser, res, out, i, errors);
|
res.append(instr, stop_pos, insize - stop_pos);
|
||||||
|
is_ok &= expand_variables(parser, res, out, i, errors);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (size_t j=0; j<var_item_list.size(); j++)
|
for (size_t j=0; j<var_item_list.size(); j++)
|
||||||
{
|
{
|
||||||
const wcstring &next = var_item_list.at(j);
|
const wcstring &next = var_item_list.at(j);
|
||||||
if (is_ok && (i == 0) && (!in[stop_pos]))
|
if (is_ok && (i == 0) && stop_pos == insize)
|
||||||
{
|
{
|
||||||
append_completion(out, next);
|
append_completion(out, next);
|
||||||
}
|
}
|
||||||
|
@ -1233,11 +1233,11 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
if (is_ok)
|
if (is_ok)
|
||||||
{
|
{
|
||||||
wcstring new_in;
|
wcstring new_in;
|
||||||
new_in.append(in, i);
|
new_in.append(instr, 0, i);
|
||||||
|
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
if (in[i-1] != VARIABLE_EXPAND)
|
if (instr.at(i-1) != VARIABLE_EXPAND)
|
||||||
{
|
{
|
||||||
new_in.push_back(INTERNAL_SEPARATOR);
|
new_in.push_back(INTERNAL_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
@ -1246,9 +1246,10 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
new_in.push_back(VARIABLE_EXPAND_EMPTY);
|
new_in.push_back(VARIABLE_EXPAND_EMPTY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert(stop_pos <= insize);
|
||||||
new_in.append(next);
|
new_in.append(next);
|
||||||
new_in.append(in + stop_pos);
|
new_in.append(instr, stop_pos, insize - stop_pos);
|
||||||
is_ok &= expand_variables2(parser, new_in, out, i, errors);
|
is_ok &= expand_variables(parser, new_in, out, i, errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1263,8 +1264,9 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
// even with no value, we still need to parse out slice syntax
|
// even with no value, we still need to parse out slice syntax
|
||||||
// Behave as though we had 1 value, so $foo[1] always works.
|
// Behave as though we had 1 value, so $foo[1] always works.
|
||||||
const size_t slice_start = stop_pos;
|
const size_t slice_start = stop_pos;
|
||||||
if (in[slice_start] == L'[')
|
if (slice_start < insize && instr.at(slice_start) == L'[')
|
||||||
{
|
{
|
||||||
|
const wchar_t *in = instr.c_str();
|
||||||
wchar_t *slice_end;
|
wchar_t *slice_end;
|
||||||
size_t bad_pos;
|
size_t bad_pos;
|
||||||
|
|
||||||
|
@ -1295,41 +1297,34 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Expand a non-existing variable */
|
||||||
Expand a non-existing variable
|
|
||||||
*/
|
|
||||||
if (c == VARIABLE_EXPAND)
|
if (c == VARIABLE_EXPAND)
|
||||||
{
|
{
|
||||||
/*
|
/* Regular expansion, i.e. expand this argument to nothing */
|
||||||
Regular expansion, i.e. expand this argument to nothing
|
empty = true;
|
||||||
*/
|
|
||||||
empty = 1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/* Expansion to single argument. */
|
||||||
Expansion to single argument.
|
|
||||||
*/
|
|
||||||
wcstring res;
|
wcstring res;
|
||||||
res.append(in, i);
|
res.append(instr, 0, i);
|
||||||
if (i > 0 && in[i-1] == VARIABLE_EXPAND_SINGLE)
|
if (i > 0 && instr.at(i-1) == VARIABLE_EXPAND_SINGLE)
|
||||||
{
|
{
|
||||||
res.push_back(VARIABLE_EXPAND_EMPTY);
|
res.push_back(VARIABLE_EXPAND_EMPTY);
|
||||||
}
|
}
|
||||||
res.append(in + stop_pos);
|
assert(stop_pos <= insize);
|
||||||
|
res.append(instr, stop_pos, insize - stop_pos);
|
||||||
|
|
||||||
is_ok &= expand_variables2(parser, res, out, i, errors);
|
is_ok &= expand_variables(parser, res, out, i, errors);
|
||||||
return is_ok;
|
return is_ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty)
|
if (!empty)
|
||||||
{
|
{
|
||||||
append_completion(out, in);
|
append_completion(out, instr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_ok;
|
return is_ok;
|
||||||
|
@ -1814,7 +1809,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!expand_variables2(parser, next, *out, next.size() - 1, errors))
|
if (!expand_variables(parser, next, *out, next.size() - 1, errors))
|
||||||
{
|
{
|
||||||
return EXPAND_ERROR;
|
return EXPAND_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -1878,12 +1873,10 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
|
||||||
|
|
||||||
for (i=0; i < in->size(); i++)
|
for (i=0; i < in->size(); i++)
|
||||||
{
|
{
|
||||||
wcstring next_str = in->at(i).completion;
|
wcstring next = in->at(i).completion;
|
||||||
int wc_res;
|
int wc_res;
|
||||||
|
|
||||||
remove_internal_separator(next_str, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
|
remove_internal_separator(next, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
|
||||||
const wchar_t *next = next_str.c_str();
|
|
||||||
|
|
||||||
const bool has_wildcard = wildcard_has(next, 1);
|
const bool has_wildcard = wildcard_has(next, 1);
|
||||||
|
|
||||||
if (has_wildcard && (flags & EXECUTABLES_ONLY))
|
if (has_wildcard && (flags & EXECUTABLES_ONLY))
|
||||||
|
@ -1893,12 +1886,12 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
|
||||||
else if (((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
|
else if (((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
|
||||||
has_wildcard)
|
has_wildcard)
|
||||||
{
|
{
|
||||||
const wchar_t *start, *rest;
|
wcstring start, rest;
|
||||||
|
|
||||||
if (next[0] == '/')
|
if (next[0] == '/')
|
||||||
{
|
{
|
||||||
start = L"/";
|
start = L"/";
|
||||||
rest = &next[1];
|
rest = next.substr(1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1943,7 +1936,7 @@ int expand_string(const wcstring &input, std::vector<completion_t> &output, expa
|
||||||
{
|
{
|
||||||
if (!(flags & ACCEPT_INCOMPLETE))
|
if (!(flags & ACCEPT_INCOMPLETE))
|
||||||
{
|
{
|
||||||
append_completion(*out, next_str);
|
append_completion(*out, next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
154
fish_tests.cpp
154
fish_tests.cpp
|
@ -1342,6 +1342,11 @@ static int expand_test(const wchar_t *in, int flags, ...)
|
||||||
}
|
}
|
||||||
va_end(va);
|
va_end(va);
|
||||||
|
|
||||||
|
if (output.size() != i)
|
||||||
|
{
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1368,6 +1373,11 @@ static void test_expand()
|
||||||
err(L"Cannot skip wildcard expansion");
|
err(L"Cannot skip wildcard expansion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!expand_test(L"/bin/l\\0", ACCEPT_INCOMPLETE, 0))
|
||||||
|
{
|
||||||
|
err(L"Failed to handle null escape in expansion");
|
||||||
|
}
|
||||||
|
|
||||||
if (system("mkdir -p /tmp/fish_expand_test/")) err(L"mkdir failed");
|
if (system("mkdir -p /tmp/fish_expand_test/")) err(L"mkdir failed");
|
||||||
if (system("touch /tmp/fish_expand_test/.foo")) err(L"touch failed");
|
if (system("touch /tmp/fish_expand_test/.foo")) err(L"touch failed");
|
||||||
if (system("touch /tmp/fish_expand_test/bar")) err(L"touch failed");
|
if (system("touch /tmp/fish_expand_test/bar")) err(L"touch failed");
|
||||||
|
@ -1858,6 +1868,7 @@ static void test_colors()
|
||||||
static void test_complete(void)
|
static void test_complete(void)
|
||||||
{
|
{
|
||||||
say(L"Testing complete");
|
say(L"Testing complete");
|
||||||
|
|
||||||
const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"};
|
const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"};
|
||||||
size_t count = sizeof name_strs / sizeof *name_strs;
|
size_t count = sizeof name_strs / sizeof *name_strs;
|
||||||
const wcstring_list_t names(name_strs, name_strs + count);
|
const wcstring_list_t names(name_strs, name_strs + count);
|
||||||
|
@ -1938,6 +1949,46 @@ static void test_complete(void)
|
||||||
complete(L"echo \\$Foo", completions, COMPLETION_REQUEST_DEFAULT);
|
complete(L"echo \\$Foo", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
do_test(completions.empty());
|
do_test(completions.empty());
|
||||||
|
|
||||||
|
/* File completions */
|
||||||
|
char saved_wd[PATH_MAX + 1] = {};
|
||||||
|
getcwd(saved_wd, sizeof saved_wd);
|
||||||
|
if (system("mkdir -p '/tmp/complete_test/'")) err(L"mkdir failed");
|
||||||
|
if (system("touch '/tmp/complete_test/testfile'")) err(L"touch failed");
|
||||||
|
if (chdir("/tmp/complete_test/")) err(L"chdir failed");
|
||||||
|
complete(L"cat te", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.size() == 1);
|
||||||
|
do_test(completions.at(0).completion == L"stfile");
|
||||||
|
completions.clear();
|
||||||
|
complete(L"cat /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.size() == 1);
|
||||||
|
do_test(completions.at(0).completion == L"stfile");
|
||||||
|
completions.clear();
|
||||||
|
complete(L"echo sup > /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.size() == 1);
|
||||||
|
do_test(completions.at(0).completion == L"stfile");
|
||||||
|
completions.clear();
|
||||||
|
complete(L"echo sup > /tmp/complete_test/te", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.size() == 1);
|
||||||
|
do_test(completions.at(0).completion == L"stfile");
|
||||||
|
completions.clear();
|
||||||
|
|
||||||
|
// Zero escapes can cause problems. See #1631
|
||||||
|
complete(L"cat foo\\0", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.empty());
|
||||||
|
completions.clear();
|
||||||
|
complete(L"cat foo\\0bar", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.empty());
|
||||||
|
completions.clear();
|
||||||
|
complete(L"cat \\0", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.empty());
|
||||||
|
completions.clear();
|
||||||
|
complete(L"cat te\\0", completions, COMPLETION_REQUEST_DEFAULT);
|
||||||
|
do_test(completions.empty());
|
||||||
|
completions.clear();
|
||||||
|
|
||||||
|
if (chdir(saved_wd)) err(L"chdir failed");
|
||||||
|
if (system("rm -Rf '/tmp/complete_test/'")) err(L"rm failed");
|
||||||
|
|
||||||
complete_set_variable_names(NULL);
|
complete_set_variable_names(NULL);
|
||||||
|
|
||||||
/* Test wraps */
|
/* Test wraps */
|
||||||
|
@ -2004,7 +2055,7 @@ static void test_completion_insertions()
|
||||||
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_REPLACES_TOKEN, false, L"bar ^");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void perform_one_autosuggestion_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line)
|
static void perform_one_autosuggestion_special_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line)
|
||||||
{
|
{
|
||||||
wcstring suggestion;
|
wcstring suggestion;
|
||||||
bool success = autosuggest_suggest_special(command, wd, suggestion);
|
bool success = autosuggest_suggest_special(command, wd, suggestion);
|
||||||
|
@ -2034,57 +2085,81 @@ static void test_autosuggest_suggest_special()
|
||||||
if (system("mkdir -p ~/test_autosuggest_suggest_special/")) err(L"mkdir failed"); //make sure tilde is handled
|
if (system("mkdir -p ~/test_autosuggest_suggest_special/")) err(L"mkdir failed"); //make sure tilde is handled
|
||||||
|
|
||||||
const wcstring wd = L"/tmp/autosuggest_test/";
|
const wcstring wd = L"/tmp/autosuggest_test/";
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/0", wd, L"cd /tmp/autosuggest_test/0foobar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/0", wd, L"cd /tmp/autosuggest_test/0foobar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/0", wd, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/0", wd, L"cd \"/tmp/autosuggest_test/0foobar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/0", wd, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/0", wd, L"cd '/tmp/autosuggest_test/0foobar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 0", wd, L"cd 0foobar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 0", wd, L"cd 0foobar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"0", wd, L"cd \"0foobar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"0", wd, L"cd \"0foobar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '0", wd, L"cd '0foobar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '0", wd, L"cd '0foobar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/1", wd, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/1", wd, L"cd /tmp/autosuggest_test/1foo\\ bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/1", wd, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/1", wd, L"cd \"/tmp/autosuggest_test/1foo bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/1", wd, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/1", wd, L"cd '/tmp/autosuggest_test/1foo bar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 1", wd, L"cd 1foo\\ bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 1", wd, L"cd 1foo\\ bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"1", wd, L"cd \"1foo bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"1", wd, L"cd \"1foo bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '1", wd, L"cd '1foo bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '1", wd, L"cd '1foo bar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/2", wd, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/2", wd, L"cd /tmp/autosuggest_test/2foo\\ \\ bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/2", wd, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/2", wd, L"cd \"/tmp/autosuggest_test/2foo bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/2", wd, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/2", wd, L"cd '/tmp/autosuggest_test/2foo bar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 2", wd, L"cd 2foo\\ \\ bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 2", wd, L"cd 2foo\\ \\ bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"2", wd, L"cd \"2foo bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"2", wd, L"cd \"2foo bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '2", wd, L"cd '2foo bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '2", wd, L"cd '2foo bar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/3", wd, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/3", wd, L"cd /tmp/autosuggest_test/3foo\\\\bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/3", wd, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/3", wd, L"cd \"/tmp/autosuggest_test/3foo\\bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/3", wd, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/3", wd, L"cd '/tmp/autosuggest_test/3foo\\bar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 3", wd, L"cd 3foo\\\\bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 3", wd, L"cd 3foo\\\\bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"3", wd, L"cd \"3foo\\bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"3", wd, L"cd \"3foo\\bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '3", wd, L"cd '3foo\\bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '3", wd, L"cd '3foo\\bar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/4", wd, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/4", wd, L"cd /tmp/autosuggest_test/4foo\\'bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/4", wd, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/4", wd, L"cd \"/tmp/autosuggest_test/4foo'bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/4", wd, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/4", wd, L"cd '/tmp/autosuggest_test/4foo\\'bar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 4", wd, L"cd 4foo\\'bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 4", wd, L"cd 4foo\\'bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"4", wd, L"cd \"4foo'bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"4", wd, L"cd \"4foo'bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '4", wd, L"cd '4foo\\'bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '4", wd, L"cd '4foo\\'bar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd /tmp/autosuggest_test/5", wd, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd /tmp/autosuggest_test/5", wd, L"cd /tmp/autosuggest_test/5foo\\\"bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"/tmp/autosuggest_test/5", wd, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"/tmp/autosuggest_test/5", wd, L"cd \"/tmp/autosuggest_test/5foo\\\"bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '/tmp/autosuggest_test/5", wd, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '/tmp/autosuggest_test/5", wd, L"cd '/tmp/autosuggest_test/5foo\"bar/'", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd 5", wd, L"cd 5foo\\\"bar/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd 5", wd, L"cd 5foo\\\"bar/", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd \"5", wd, L"cd \"5foo\\\"bar/\"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd \"5", wd, L"cd \"5foo\\\"bar/\"", __LINE__);
|
||||||
perform_one_autosuggestion_test(L"cd '5", wd, L"cd '5foo\"bar/'", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '5", wd, L"cd '5foo\"bar/'", __LINE__);
|
||||||
|
|
||||||
perform_one_autosuggestion_test(L"cd ~/test_autosuggest_suggest_specia", wd, L"cd ~/test_autosuggest_suggest_special/", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd ~/test_autosuggest_suggest_specia", wd, L"cd ~/test_autosuggest_suggest_special/", __LINE__);
|
||||||
|
|
||||||
// A single quote should defeat tilde expansion
|
// A single quote should defeat tilde expansion
|
||||||
perform_one_autosuggestion_test(L"cd '~/test_autosuggest_suggest_specia'", wd, L"", __LINE__);
|
perform_one_autosuggestion_special_test(L"cd '~/test_autosuggest_suggest_specia'", wd, L"", __LINE__);
|
||||||
|
|
||||||
if (system("rm -Rf '/tmp/autosuggest_test/'")) err(L"rm failed");
|
if (system("rm -Rf '/tmp/autosuggest_test/'")) err(L"rm failed");
|
||||||
if (system("rm -Rf ~/test_autosuggest_suggest_special/")) err(L"rm failed");
|
if (system("rm -Rf ~/test_autosuggest_suggest_special/")) err(L"rm failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, const wcstring &wd, long line)
|
||||||
|
{
|
||||||
|
completion_list_t comps;
|
||||||
|
complete(command, comps, COMPLETION_REQUEST_AUTOSUGGESTION);
|
||||||
|
do_test(comps.empty());
|
||||||
|
if (! comps.empty())
|
||||||
|
{
|
||||||
|
const wcstring &suggestion = comps.front().completion;
|
||||||
|
printf("line %ld: complete() expected to return nothing for %ls\n", line, command.c_str());
|
||||||
|
printf(" instead got: %ls\n", suggestion.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_autosuggestion_ignores()
|
||||||
|
{
|
||||||
|
say(L"Testing scenarios that should produce no autosuggestions");
|
||||||
|
const wcstring wd = L"/tmp/autosuggest_test/";
|
||||||
|
// Do not do file autosuggestions immediately after certain statement terminators - see #1631
|
||||||
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST|", wd, __LINE__);
|
||||||
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST&", wd, __LINE__);
|
||||||
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST#comment", wd, __LINE__);
|
||||||
|
perform_one_autosuggestion_should_ignore_test(L"echo PIPE_TEST;", wd, __LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_autosuggestion_combining()
|
static void test_autosuggestion_combining()
|
||||||
{
|
{
|
||||||
say(L"Testing autosuggestion combining");
|
say(L"Testing autosuggestion combining");
|
||||||
|
@ -3631,6 +3706,7 @@ int main(int argc, char **argv)
|
||||||
if (should_test_function("universal")) test_universal_callbacks();
|
if (should_test_function("universal")) test_universal_callbacks();
|
||||||
if (should_test_function("notifiers")) test_universal_notifiers();
|
if (should_test_function("notifiers")) test_universal_notifiers();
|
||||||
if (should_test_function("completion_insertions")) test_completion_insertions();
|
if (should_test_function("completion_insertions")) test_completion_insertions();
|
||||||
|
if (should_test_function("autosuggestion_ignores")) test_autosuggestion_ignores();
|
||||||
if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining();
|
if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining();
|
||||||
if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
|
if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
|
||||||
if (should_test_function("history")) history_tests_t::test_history();
|
if (should_test_function("history")) history_tests_t::test_history();
|
||||||
|
|
33
wildcard.cpp
33
wildcard.cpp
|
@ -105,18 +105,14 @@ wildcards using **.
|
||||||
/** Hashtable containing all descriptions that describe an executable */
|
/** Hashtable containing all descriptions that describe an executable */
|
||||||
static std::map<wcstring, wcstring> suffix_map;
|
static std::map<wcstring, wcstring> suffix_map;
|
||||||
|
|
||||||
|
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
|
||||||
bool wildcard_has(const wchar_t *str, bool internal)
|
static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal)
|
||||||
{
|
{
|
||||||
if (!str)
|
assert(str != NULL);
|
||||||
{
|
const wchar_t *end = str + len;
|
||||||
debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internal)
|
if (internal)
|
||||||
{
|
{
|
||||||
for (; *str; str++)
|
for (; str < end; str++)
|
||||||
{
|
{
|
||||||
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
|
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
|
||||||
return true;
|
return true;
|
||||||
|
@ -125,7 +121,7 @@ bool wildcard_has(const wchar_t *str, bool internal)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
wchar_t prev=0;
|
wchar_t prev=0;
|
||||||
for (; *str; str++)
|
for (; str < end; str++)
|
||||||
{
|
{
|
||||||
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\'))
|
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\'))
|
||||||
return true;
|
return true;
|
||||||
|
@ -136,6 +132,18 @@ bool wildcard_has(const wchar_t *str, bool internal)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool wildcard_has(const wchar_t *str, bool internal)
|
||||||
|
{
|
||||||
|
assert(str != NULL);
|
||||||
|
return wildcard_has_impl(str, wcslen(str), internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wildcard_has(const wcstring &str, bool internal)
|
||||||
|
{
|
||||||
|
return wildcard_has_impl(str.data(), str.size(), internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Check whether the string str matches the wildcard string wc.
|
Check whether the string str matches the wildcard string wc.
|
||||||
|
|
||||||
|
@ -1084,6 +1092,11 @@ int wildcard_expand(const wchar_t *wc,
|
||||||
|
|
||||||
int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector<completion_t> &outputs)
|
int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector<completion_t> &outputs)
|
||||||
{
|
{
|
||||||
|
/* Hackish fix for 1631. We are about to call c_str(), which will produce a string truncated at any embedded nulls. We could fix this by passing around the size, etc. However embedded nulls are never allowed in a filename, so we just check for them and return 0 (no matches) if there is an embedded null. This isn't quite right, e.g. it will fail for \0?, but that is an edge case. */
|
||||||
|
if (wc.find(L'\0') != wcstring::npos)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
// PCA: not convinced this temporary variable is really necessary
|
// PCA: not convinced this temporary variable is really necessary
|
||||||
std::vector<completion_t> lst;
|
std::vector<completion_t> lst;
|
||||||
int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, lst);
|
int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, lst);
|
||||||
|
|
|
@ -79,9 +79,9 @@ int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_
|
||||||
*/
|
*/
|
||||||
bool wildcard_match(const wcstring &str, const wcstring &wc, bool leading_dots_fail_to_match = false);
|
bool wildcard_match(const wcstring &str, const wcstring &wc, bool leading_dots_fail_to_match = false);
|
||||||
|
|
||||||
|
|
||||||
/** Check if the specified string contains wildcards */
|
/** Check if the specified string contains wildcards */
|
||||||
bool wildcard_has(const wchar_t *str, bool internal);
|
bool wildcard_has(const wcstring &, bool internal);
|
||||||
|
bool wildcard_has(const wchar_t *, bool internal);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Test wildcard completion
|
Test wildcard completion
|
||||||
|
|
Loading…
Reference in a new issue