Merge branch 'master' into documentation-update

Conflicts (FIXED):
	doc_src/command.txt
	doc_src/index.hdr.in
	doc_src/read.txt
	doc_src/type.txt
This commit is contained in:
Mark Griffiths 2014-08-26 20:05:46 +01:00
commit d6c5a1e0c4
41 changed files with 970 additions and 317 deletions

View File

@ -290,7 +290,7 @@ doc/refman.pdf: doc
test: $(PROGRAMS) fish_tests test: $(PROGRAMS) fish_tests
./fish_tests ./fish_tests
cd tests; ../fish <test.fish; cd tests; ../fish -c 'set -x fish_function_path "$$PWD"/../share/functions dummy; source' <test.fish;
.PHONY: test .PHONY: test

View File

@ -1053,6 +1053,87 @@ static int builtin_emit(parser_t &parser, wchar_t **argv)
} }
/**
Implementation of the builtin 'command'. Actual command running is handled by
the parser, this just processes the flags.
*/
static int builtin_command(parser_t &parser, wchar_t **argv)
{
int argc=builtin_count_args(argv);
int print_path=0;
woptind=0;
static const struct woption
long_options[] =
{
{ L"search", no_argument, 0, 's' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
while (1)
{
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"svh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, argv[0], stderr_buffer);
return STATUS_BUILTIN_ERROR;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK;
case 's':
case 'v':
print_path=1;
break;
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return STATUS_BUILTIN_ERROR;
}
}
if (!print_path)
{
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_ERROR;
}
int found=0;
for (int idx = woptind; argv[idx]; ++idx)
{
const wchar_t *command_name = argv[idx];
wcstring path;
if (path_get_path(command_name, &path))
{
append_format(stdout_buffer, L"%ls\n", path.c_str());
++found;
}
}
return found ? STATUS_BUILTIN_OK : STATUS_BUILTIN_ERROR;
}
/** /**
A generic bultin that only supports showing a help message. This is A generic bultin that only supports showing a help message. This is
only a placeholder that prints the help message. Useful for only a placeholder that prints the help message. Useful for
@ -2233,6 +2314,7 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
int exit_res=STATUS_BUILTIN_OK; int exit_res=STATUS_BUILTIN_OK;
const wchar_t *mode_name = READ_MODE_NAME; const wchar_t *mode_name = READ_MODE_NAME;
int shell = 0; int shell = 0;
int array = 0;
woptind=0; woptind=0;
@ -2277,6 +2359,10 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
L"shell", no_argument, 0, 's' L"shell", no_argument, 0, 's'
} }
, ,
{
L"array", no_argument, 0, 'a'
}
,
{ {
L"help", no_argument, 0, 'h' L"help", no_argument, 0, 'h'
} }
@ -2291,7 +2377,7 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
int opt = wgetopt_long(argc, int opt = wgetopt_long(argc,
argv, argv,
L"xglUup:c:hm:s", L"xglUup:c:hm:sa",
long_options, long_options,
&opt_index); &opt_index);
if (opt == -1) if (opt == -1)
@ -2346,6 +2432,10 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
shell = 1; shell = 1;
break; break;
case 'a':
array = 1;
break;
case 'h': case 'h':
builtin_print_help(parser, argv[0], stdout_buffer); builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK; return STATUS_BUILTIN_OK;
@ -2378,6 +2468,14 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
return STATUS_BUILTIN_ERROR; return STATUS_BUILTIN_ERROR;
} }
if (array && woptind+1 != argc)
{
append_format(stderr_buffer, _(L"%ls: --array option requires a single variable name.\n"), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return STATUS_BUILTIN_ERROR;
}
/* /*
Verify all variable names Verify all variable names
*/ */
@ -2512,18 +2610,70 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
wchar_t *state; wchar_t *state;
env_var_t ifs = env_get_string(L"IFS"); env_var_t ifs = env_get_string(L"IFS");
if (ifs.missing())
ifs = L"";
nxt = wcstok(buff, (i<argc-1)?ifs.c_str():L"", &state); if (ifs.missing_or_empty())
while (i<argc)
{ {
env_set(argv[i], nxt != 0 ? nxt: L"", place); /* Every character is a separate token */
size_t bufflen = wcslen(buff);
if (array)
{
if (bufflen > 0)
{
wcstring chars(bufflen+(bufflen-1), ARRAY_SEP);
for (size_t j=0; j<bufflen; ++j)
{
chars[j*2] = buff[j];
}
env_set(argv[i], chars.c_str(), place);
}
else
{
env_set(argv[i], NULL, place);
}
}
else
{
size_t j = 0;
for (; i+1 < argc; ++i)
{
if (j < bufflen) {
wchar_t buffer[2] = {buff[j], 0};
env_set(argv[i], buffer, place);
}
else {
env_set(argv[i], L"", place);
}
if (j < bufflen) ++j;
}
if (i < argc) env_set(argv[i], &buff[j], place);
}
}
else if (array)
{
wcstring tokens;
tokens.reserve(wcslen(buff));
bool empty = true;
i++; for (nxt = wcstok(buff, ifs.c_str(), &state); nxt != 0; nxt = wcstok(0, ifs.c_str(), &state))
if (nxt != 0) {
nxt = wcstok(0, (i<argc-1)?ifs.c_str():L"", &state); if (! tokens.empty()) tokens.push_back(ARRAY_SEP);
tokens.append(nxt);
empty = false;
}
env_set(argv[i], empty ? NULL : tokens.c_str(), place);
}
else
{
nxt = wcstok(buff, (i<argc-1)?ifs.c_str():L"", &state);
while (i<argc)
{
env_set(argv[i], nxt != 0 ? nxt: L"", place);
i++;
if (nxt != 0)
nxt = wcstok(0, (i<argc-1)?ifs.c_str():L"", &state);
}
} }
} }
@ -3726,7 +3876,7 @@ static const builtin_data_t builtin_datas[]=
{ L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function") }, { L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function") },
{ L"case", &builtin_generic, N_(L"Conditionally execute a block of commands") }, { L"case", &builtin_generic, N_(L"Conditionally execute a block of commands") },
{ L"cd", &builtin_cd, N_(L"Change working directory") }, { L"cd", &builtin_cd, N_(L"Change working directory") },
{ L"command", &builtin_generic, N_(L"Run a program instead of a function or builtin") }, { L"command", &builtin_command, N_(L"Run a program instead of a function or builtin") },
{ L"commandline", &builtin_commandline, N_(L"Set or get the commandline") }, { L"commandline", &builtin_commandline, N_(L"Set or get the commandline") },
{ L"complete", &builtin_complete, N_(L"Edit command specific completions") }, { L"complete", &builtin_complete, N_(L"Edit command specific completions") },
{ L"contains", &builtin_contains, N_(L"Search for a specified string in a list") }, { L"contains", &builtin_contains, N_(L"Search for a specified string in a list") },

View File

@ -326,7 +326,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
int opt = wgetopt_long(argc, int opt = wgetopt_long(argc,
argv, argv,
L"abijpctwforhI:CLSs", L"abijpctwforhI:CLSsP",
long_options, long_options,
&opt_index); &opt_index);
if (opt == -1) if (opt == -1)

View File

@ -166,7 +166,7 @@ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope)
case ENV_INVALID: case ENV_INVALID:
{ {
append_format(stderr_buffer, _(L"%ls: Unknown error"), L"set"); append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key);
retcode=1; retcode=1;
break; break;
} }

View File

@ -1884,7 +1884,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
//const wcstring prev_token(prev_begin, prev_token_len); //const wcstring prev_token(prev_begin, prev_token_len);
parse_node_tree_t tree; parse_node_tree_t tree;
parse_tree_from_string(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &tree, NULL); parse_tree_from_string(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens | parse_flag_include_comments, &tree, NULL);
/* Find any plain statement that contains the position. We have to backtrack past spaces (#1261). So this will be at either the last space character, or after the end of the string */ /* Find any plain statement that contains the position. We have to backtrack past spaces (#1261). So this will be at either the last space character, or after the end of the string */
size_t adjusted_pos = pos; size_t adjusted_pos = pos;
@ -1896,9 +1896,31 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
const parse_node_t *plain_statement = tree.find_node_matching_source_location(symbol_plain_statement, adjusted_pos, NULL); const parse_node_t *plain_statement = tree.find_node_matching_source_location(symbol_plain_statement, adjusted_pos, NULL);
if (plain_statement == NULL) if (plain_statement == NULL)
{ {
/* Not part of a plain statement. This could be e.g. a for loop header, case expression, etc. Do generic file completions (#1309). If we had to backtrack, it means there was whitespace; don't do an autosuggestion in that case. */ /* Not part of a plain statement. This could be e.g. a for loop header, case expression, etc. Do generic file completions (#1309). If we had to backtrack, it means there was whitespace; don't do an autosuggestion in that case. Also don't do it if we are just after a pipe, semicolon, or & (#1631), or in a comment.
bool no_file = (flags & COMPLETION_REQUEST_AUTOSUGGESTION) && (adjusted_pos < pos);
completer.complete_param_expand(current_token, ! no_file); Overall this logic is a total mess. A better approach would be to return the "possible next token" from the parse tree directly (this data is available as the first of the sequence of nodes without source locations at the very end of the parse tree). */
bool do_file = true;
if (flags & COMPLETION_REQUEST_AUTOSUGGESTION)
{
if (adjusted_pos < pos)
{
do_file = false;
}
else if (pos > 0)
{
// If the previous character is in one of these types, we don't do file suggestions
parse_token_type_t bad_types[] = {parse_token_type_pipe, parse_token_type_end, parse_token_type_background, parse_special_type_comment};
for (size_t i=0; i < sizeof bad_types / sizeof *bad_types; i++)
{
if (tree.find_node_matching_source_location(bad_types[i], pos - 1, NULL))
{
do_file = false;
break;
}
}
}
}
completer.complete_param_expand(current_token, do_file);
} }
else else
{ {

View File

@ -2,14 +2,23 @@
\subsection command-synopsis Synopsis \subsection command-synopsis Synopsis
\fish{synopsis} \fish{synopsis}
command COMMANDNAME [OPTIONS...] command [OPTIONS] COMMANDNAME [ARGS...]
\endfish \endfish
\subsection command-description Description \subsection command-description Description
`command` forces the shell to execute the program `COMMANDNAME` and ignore any functions or builtins with the same name. `command` forces the shell to execute the program `COMMANDNAME` and ignore any functions or builtins with the same name.
The following options are available:
- `-h` or `--help` prints help and then exits.
- `-s` or `--search` returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the `$PATH`.
\subsection command-example Example With the `-s` option, `command` treats every argument as a separate command to look up and sets the exit status to 0 if any of the specified commands were found, or 1 if no commands could be found.
`command ls` causes fish to execute the `ls` program, even if an 'ls' function exists. For basic compatibility with POSIX `command`, the `-v` flag is recognized as an alias for `-s`.
\subsection command-example Examples
`command ls` causes fish to execute the `ls` program, even if an `ls` function exists.
`command -s ls` returns the path to the `ls` program.

View File

@ -416,7 +416,7 @@ Note that if no matches are found for a specific wildcard, it will expand into z
\subsection expand-command-substitution Command substitution \subsection expand-command-substitution Command substitution
The output of a series of commands can be used as the parameters to another command. If a parameter contains a set of parenthesis, the text enclosed by the parenthesis will be interpreted as a list of commands. On expansion, this list is executed, and substituted by the output. If the output is more than one line long, each line will be expanded to a new parameter. The output of a series of commands can be used as the parameters to another command. If a parameter contains a set of parenthesis, the text enclosed by the parenthesis will be interpreted as a list of commands. On expansion, this list is executed, and substituted by the output. If the output is more than one line long, each line will be expanded to a new parameter. Setting `IFS` to the empty string will disable line splitting.
The exit status of the last run command substitution is available in the <a href='#variables-status'>status</a> variable. The exit status of the last run command substitution is available in the <a href='#variables-status'>status</a> variable.
@ -432,6 +432,9 @@ for i in *.jpg; convert $i (basename $i .jpg).png; end
# PNG format using the 'convert' program. # PNG format using the 'convert' program.
\endfish \endfish
The command `begin; set -l IFS; set data (cat data.txt); end`
will set the `data` variable to the contents of 'data.txt' without
splitting it into an array.
\subsection expand-brace Brace expansion \subsection expand-brace Brace expansion
@ -718,40 +721,51 @@ certain environment variables.
- `CDPATH`, an array of directories in which to search for the new directory for the `cd` builtin. By default, the fish configuration defines `CDPATH` to be a universal variable with the values `.` and `~`. - `CDPATH`, an array of directories in which to search for the new directory for the `cd` builtin. By default, the fish configuration defines `CDPATH` to be a universal variable with the values `.` and `~`.
- A large number of variable starting with the prefixes `fish_color` and `fish_pager_color`. See <a href='#variables-color'>Variables for changing highlighting colors</a> for more information. - A large number of variable starting with the prefixes `fish_color` and `fish_pager_color.` See <a href='#variables-color'>Variables for changing highlighting colors</a> for more information.
- `fish_greeting`, the greeting message printed on startup. - `fish_greeting`, the greeting message printed on startup.
- `LANG`, `LC_ALL`, `LC_COLLATE`, `LC_CTYPE`, `LC_MESSAGES`, `LC_MONETARY`, `LC_NUMERIC` and `LC_TIME` set the language option for the shell and subprograms. See the section <a href='#variables-locale'>Locale variables</a> for more information. - `LANG`, `LC_ALL`, `LC_COLLATE`, `LC_CTYPE`, `LC_MESSAGES`, `LC_MONETARY`, `LC_NUMERIC` and `LC_TIME` set the language option for the shell and subprograms. See the section <a href='#variables-locale'>Locale variables</a> for more information.
- `fish_user_paths`, an array of directories that are prepended to PATH. This can be a universal variable. - `fish_user_paths`, an array of directories that are prepended to `PATH`. This can be a universal variable.
- `PATH`, an array of directories in which to search for commands - `PATH`, an array of directories in which to search for commands
- `umask`, the current file creation mask. The preferred way to change the umask variable is through the <a href="commands.html#umask">umask function</a>. An attempt to set umask to an invalid value will always fail. - `umask`, the current file creation mask. The preferred way to change the umask variable is through the <a href="commands.html#umask">umask function</a>. An attempt to set umask to an invalid value will always fail.
`fish` also sends additional information to the user through the values of certain environment variables. The user cannot change the values of most of these variables. `fish` also sends additional information to the user through the
values of certain environment variables. The user cannot change the
values of most of these variables.
- `_`, the name of the currently running command. - `_`, the name of the currently running command.
- `argv`, an array of arguments to the shell or function. `argv` is only defined when inside a function call, or if fish was invoked with a list of arguments, like 'fish myscript.fish foo bar'. This variable can be changed by the user. - `argv`, an array of arguments to the shell or function. `argv` is only defined when inside a function call, or if fish was invoked with a list of arguments, like `fish myscript.fish foo bar`. This variable can be changed by the user.
- `history`, an array containing the last commands that were entered. - `history`, an array containing the last commands that were entered.
- `HOME`, the user's home directory. This variable can only be changed by the root user. - `HOME`, the user's home directory. This variable can be changed by the user.
- `IFS`, the internal field separator that is used for word splitting with the <a href="commands.html#read">read builtin</a>. Setting this to the empty string will also disable line splitting in <a href="#expand-command-substitution">command substitution</a>. This variable can be changed by the user.
- `PWD`, the current working directory. - `PWD`, the current working directory.
- `status`, the <a href="#variables-status">exit status</a> of the last foreground job to exit. If the job was terminated through a signal, the exit status will be 128 plus the signal number. - `status`, the <a href="#variables-status">exit status</a> of the last foreground job to exit. If the job was terminated through a signal, the exit status will be 128 plus the signal number.
- `USER`, the current username. This variable can only be changed by the root user. - `USER`, the current username. This variable can be changed by the user.
- `CMD_DURATION`, the runtime of the last command in milliseconds. - `CMD_DURATION`, the runtime of the last command in milliseconds.
The names of these variables are mostly derived from the csh family of shells and differ from the ones used by Bourne style shells such as bash. The names of these variables are mostly derived from the csh family of
shells and differ from the ones used by Bourne style shells such as
Variables whose name are in uppercase are exported to the commands started by fish, while those in lowercase are not exported. This rule is not enforced by fish, but it is good coding practice to use casing to distinguish between exported and unexported variables. `fish` also uses several variables internally. Such variables are prefixed with the string `__FISH` or `__fish`. These should never be used by the user. Changing their value may break fish. bash.
Variables whose name are in uppercase are exported to the commands
started by fish, while those in lowercase are not exported. This rule is not
enforced by fish, but it is good coding practice to use casing to
distinguish between exported and unexported variables. `fish` also
uses several variables internally. Such variables are prefixed with
the string `__FISH` or `__fish.` These should never be used by the
user. Changing their value may break fish.
\subsection variables-status The status variable \subsection variables-status The status variable
@ -1091,9 +1105,9 @@ If you install fish in your home directory, fish will not work correctly for any
If you have a question not answered by this documentation, there are several avenues for help: If you have a question not answered by this documentation, there are several avenues for help:
-# The official mailing list at <a href='fish-users@lists.sf.net'>fish-users@lists.sf.net</a> -# The official mailing list at <a href='https://lists.sf.net/lists/listinfo/fish-users'>fish-users@lists.sf.net</a>
-# The Internet Relay Chat channel, \#fish on `irc.oftc.net` -# The Internet Relay Chat channel, `#fish` on `irc.oftc.net`
-# The <a href="http://github.com/fish-shell/fish-shell/">project GitHub page</a> -# The <a href="https://github.com/fish-shell/fish-shell/">project GitHub page</a>
If you have an improvement for fish, you can submit it via the mailing list or the GitHub page. If you have an improvement for fish, you can submit it via the mailing list or the GitHub page.

View File

@ -29,15 +29,16 @@ The following options are available:
- `-x` or `--export` exports the variables to child processes. - `-x` or `--export` exports the variables to child processes.
`read` reads a single line of input from stdin, breaks it into tokens based on the `IFS` shell variable, and then assigns one token to each variable specified in `VARIABLES`. If there are more tokens than variables, the complete remainder is assigned to the last variable. - `-a` or `--array` stores the result as an array.
See the documentation for `set` for more details on the scoping rules for variables. `read` reads a single line of input from stdin, breaks it into tokens based on the `IFS` shell variable, and then assigns one token to each variable specified in `VARIABLES`. If there are more tokens than variables, the complete remainder is assigned to the last variable. As a special case, if `IFS` is set to the empty string, each character of the input is considered a separate token.
If `-a` or `--array` is provided, only one variable name is allowed and the tokens are stored as an array in this variable.
\subsection read-example Example \subsection read-example Example
The following code stores the value 'hello' in the shell variable The following code stores the value 'hello' in the shell variable `$foo`.
`$foo`.
\fish \fish
echo hello|read foo echo hello|read foo

View File

@ -17,13 +17,13 @@ The following options are available:
- `-f` or `--no-functions` suppresses function and builtin lookup. - `-f` or `--no-functions` suppresses function and builtin lookup.
- `-t` or `--type` prints `keyword`, `function`, `builtin`, or `file` if `NAME` is a shell reserved word, function, builtin, or disk file, respectively. - `-t` or `--type` prints `function`, `builtin`, or `file` if `NAME` is a shell function, builtin, or disk file, respectively.
- `-p` or `--path` returns the name of the disk file that would be executed, or nothing if 'type -t name' would not return 'file'. - `-p` or `--path` returns the name of the disk file that would be executed, or nothing if `type -t name` would not return `file`.
- `-P` or `--force-path` returns the name of the disk file that would be executed, or nothing no file with the specified name could be found in the `$PATH`. - `-P` or `--force-path` returns the name of the disk file that would be executed, or nothing if no file with the specified name could be found in the <tt>$PATH</tt>.
`type` sets the exit status to 0 if the specified command was found, and 1 if it could not be found. - `-q` or `--quiet` suppresses all output; this is useful when testing the exit status.
\subsection type-example Example \subsection type-example Example
@ -31,4 +31,4 @@ The following options are available:
\fish \fish
type fg type fg
# Outputs the string 'fg is a shell builtin'. # Outputs the string 'fg is a shell builtin'.
\endfish \endfish

View File

@ -655,10 +655,12 @@ int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t var_mode)
if (!errno && (!*end) && (mask <= 0777) && (mask >= 0)) if (!errno && (!*end) && (mask <= 0777) && (mask >= 0))
{ {
umask(mask); umask(mask);
/* Do not actually create a umask variable, on env_get, it will be calculated dynamically */
return 0;
} }
} }
/* Do not actually create a umask variable, on env_get, it will be calculated dynamically */
return 0; return ENV_INVALID;
} }
/* /*

2
env.h
View File

@ -80,7 +80,7 @@ void env_init(const struct config_paths_t *paths = NULL);
* ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that the user tried to change a read-only variable. * ENV_PERM, can only be returned when setting as a user, e.g. ENV_USER is set. This means that the user tried to change a read-only variable.
* ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric variables set from the local or universal scopes, or set as exported. * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric variables set from the local or universal scopes, or set as exported.
* ENV_INVALID, the variable name or mode was invalid * ENV_INVALID, the variable value was invalid. This applies only to special variables.
*/ */
int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t mode); int env_set(const wcstring &key, const wchar_t *val, env_mode_flags_t mode);

View File

@ -1548,16 +1548,7 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, boo
if (! ifs.missing_or_empty()) if (! ifs.missing_or_empty())
{ {
if (ifs.at(0) < 128) sep = '\n';
{
sep = '\n';//ifs[0];
}
else
{
sep = 0;
debug(0, L"Warning - invalid command substitution separator '%lc'. Please change the first character of IFS", ifs[0]);
}
} }
is_subshell=1; is_subshell=1;

View File

@ -953,8 +953,11 @@ void expand_variable_error(parser_t &parser, const wcstring &token, size_t token
/** /**
Parse an array slicing specification Parse an array slicing specification
Returns 0 on success.
If a parse error occurs, returns the index of the bad token.
Note that 0 can never be a bad index because the string always starts with [.
*/ */
static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx, std::vector<size_t> &source_positions, size_t array_size) static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx, std::vector<size_t> &source_positions, size_t array_size)
{ {
wchar_t *end; wchar_t *end;
@ -981,7 +984,7 @@ static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &
tmp = wcstol(&in[pos], &end, 10); tmp = wcstol(&in[pos], &end, 10);
if ((errno) || (end == &in[pos])) if ((errno) || (end == &in[pos]))
{ {
return 1; return pos;
} }
// debug( 0, L"Push idx %d", tmp ); // debug( 0, L"Push idx %d", tmp );
@ -999,7 +1002,7 @@ static int parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &
long tmp1 = wcstol(&in[pos], &end, 10); long tmp1 = wcstol(&in[pos], &end, 10);
if ((errno) || (end == &in[pos])) if ((errno) || (end == &in[pos]))
{ {
return 1; return pos;
} }
pos = end-in; pos = end-in;
@ -1046,21 +1049,24 @@ static int 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; // Make this explicit
} if (instr.empty())
{
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) append_completion(out, instr);
{ return true;
int is_ok= 1; }
int empty=0;
bool is_ok = true;
bool empty = false;
const size_t insize = instr.size();
wcstring var_tmp; wcstring var_tmp;
@ -1074,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;
@ -1084,12 +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)
{ {
if (!(in[stop_pos ])) const wchar_t nc = instr.at(stop_pos);
if (nc == VARIABLE_EXPAND_EMPTY)
{
stop_pos++;
break; break;
if (!(iswalnum(in[stop_pos]) || }
(wcschr(L"_", in[stop_pos])!= 0))) if (!wcsvarchr(nc))
break; break;
stop_pos++; stop_pos++;
@ -1101,14 +1110,22 @@ 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 = expand_var(var_tmp.c_str()); env_var_t var_val;
if (var_len == 1 && var_tmp[0] == VARIABLE_EXPAND_EMPTY)
{
var_val = env_var_t::missing_var();
}
else
{
var_val = expand_var(var_tmp.c_str());
}
if (! var_val.missing()) if (! var_val.missing())
{ {
@ -1117,20 +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;
all_vars=0; all_vars=0;
const wchar_t *in = instr.c_str();
if (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)
{ {
append_syntax_error(errors, append_syntax_error(errors,
stop_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);
@ -1142,15 +1161,15 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
for (size_t j=0; j<var_idx_list.size(); j++) for (size_t j=0; j<var_idx_list.size(); j++)
{ {
long tmp = var_idx_list.at(j); long tmp = var_idx_list.at(j);
size_t var_src_pos = var_pos_list.at(j);
/* Check that we are within array bounds. If not, truncate the list to exit. */ /* Check that we are within array bounds. If not, truncate the list to exit. */
if (tmp < 1 || (size_t)tmp > var_item_list.size()) if (tmp < 1 || (size_t)tmp > var_item_list.size())
{ {
size_t var_src_pos = var_pos_list.at(j);
/* The slice was parsed starting at stop_pos, so we have to add that to the error position */ /* The slice was parsed starting at stop_pos, so we have to add that to the error position */
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;
} }
@ -1169,12 +1188,21 @@ 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)
res.push_back(INTERNAL_SEPARATOR); {
if (instr.at(i-1) != VARIABLE_EXPAND_SINGLE)
{
res.push_back(INTERNAL_SEPARATOR);
}
else if (var_item_list.empty() || var_item_list.front().empty())
{
// first expansion is empty, but we need to recursively expand
res.push_back(VARIABLE_EXPAND_EMPTY);
}
}
for (size_t j=0; j<var_item_list.size(); j++) for (size_t j=0; j<var_item_list.size(); j++)
{ {
@ -1186,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);
} }
@ -1204,18 +1233,23 @@ 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(instr, 0, i);
if (start_pos > 0) if (i > 0)
new_in.append(in, start_pos - 1);
// at this point new_in.size() is start_pos - 1
if (start_pos>1 && new_in[start_pos-2]!=VARIABLE_EXPAND)
{ {
new_in.push_back(INTERNAL_SEPARATOR); if (instr.at(i-1) != VARIABLE_EXPAND)
{
new_in.push_back(INTERNAL_SEPARATOR);
}
else if (next.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);
} }
} }
@ -1227,38 +1261,70 @@ static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::
} }
else else
{ {
/* // even with no value, we still need to parse out slice syntax
Expand a non-existing variable // Behave as though we had 1 value, so $foo[1] always works.
*/ const size_t slice_start = stop_pos;
if (slice_start < insize && instr.at(slice_start) == L'[')
{
const wchar_t *in = instr.c_str();
wchar_t *slice_end;
size_t bad_pos;
bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list, 1);
if (bad_pos != 0)
{
append_syntax_error(errors,
stop_pos + bad_pos,
L"Invalid index value");
is_ok = 0;
return is_ok;
}
stop_pos = (slice_end-in);
// validate that the parsed indexes are valid
for (size_t j=0; j<var_idx_list.size(); j++)
{
long tmp = var_idx_list.at(j);
if (tmp != 1)
{
size_t var_src_pos = var_pos_list.at(j);
append_syntax_error(errors,
slice_start + var_src_pos,
ARRAY_BOUNDS_ERR);
is_ok = 0;
return is_ok;
}
}
}
/* 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;
in[i] = 0; res.append(instr, 0, i);
res.append(in); if (i > 0 && instr.at(i-1) == VARIABLE_EXPAND_SINGLE)
res.append(in + stop_pos); {
res.push_back(VARIABLE_EXPAND_EMPTY);
}
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;
@ -1435,11 +1501,14 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
{ {
std::vector<long> slice_idx; std::vector<long> slice_idx;
std::vector<size_t> slice_source_positions; std::vector<size_t> slice_source_positions;
const wchar_t * const slice_begin = tail_begin;
wchar_t *slice_end; wchar_t *slice_end;
size_t bad_pos;
if (parse_slice(tail_begin, &slice_end, slice_idx, slice_source_positions, sub_res.size())) bad_pos = parse_slice(slice_begin, &slice_end, slice_idx, slice_source_positions, sub_res.size());
if (bad_pos != 0)
{ {
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Invalid index value"); append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
return 0; return 0;
} }
else else
@ -1451,8 +1520,9 @@ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<
long idx = slice_idx.at(i); long idx = slice_idx.at(i);
if (idx < 1 || (size_t)idx > sub_res.size()) if (idx < 1 || (size_t)idx > sub_res.size())
{ {
size_t pos = slice_source_positions.at(i);
append_syntax_error(errors, append_syntax_error(errors,
SOURCE_LOCATION_UNKNOWN, slice_begin - in + pos,
ARRAY_BOUNDS_ERR); ARRAY_BOUNDS_ERR);
return 0; return 0;
} }
@ -1739,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;
} }
@ -1803,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))
@ -1818,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
{ {
@ -1868,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);
} }
} }
} }

View File

@ -102,6 +102,11 @@ enum
*/ */
INTERNAL_SEPARATOR, INTERNAL_SEPARATOR,
/**
Character representing an empty variable expansion.
Only used transitively while expanding variables.
*/
VARIABLE_EXPAND_EMPTY,
} }
; ;

View File

@ -1341,6 +1341,11 @@ static int expand_test(const wchar_t *in, int flags, ...)
i++; i++;
} }
va_end(va); va_end(va);
if (output.size() != i)
{
res = false;
}
return res; return res;
@ -1367,6 +1372,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");
@ -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);
@ -1937,7 +1948,47 @@ static void test_complete(void)
completions.clear(); completions.clear();
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");
@ -3404,6 +3479,8 @@ static void test_highlighting(void)
{L"ls", highlight_spec_command}, {L"ls", highlight_spec_command},
{L"param2", highlight_spec_param}, {L"param2", highlight_spec_param},
{L")", highlight_spec_operator}, {L")", highlight_spec_operator},
{L"|", highlight_spec_statement_terminator},
{L"cat", highlight_spec_command},
{NULL, -1} {NULL, -1}
}; };
@ -3629,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();

View File

@ -617,12 +617,26 @@ static size_t color_variable(const wchar_t *in, size_t in_len, std::vector<highl
if (in[idx] == L'[') if (in[idx] == L'[')
{ {
wchar_t *slice_begin = NULL, *slice_end = NULL; wchar_t *slice_begin = NULL, *slice_end = NULL;
if (1 == parse_util_locate_slice(in, &slice_begin, &slice_end, false)) switch (parse_util_locate_slice(in, &slice_begin, &slice_end, false))
{ {
size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in; case 1:
assert(slice_end_idx > slice_begin_idx); {
colors[slice_begin_idx] = highlight_spec_operator; size_t slice_begin_idx = slice_begin - in, slice_end_idx = slice_end - in;
colors[slice_end_idx] = highlight_spec_operator; assert(slice_end_idx > slice_begin_idx);
colors[slice_begin_idx] = highlight_spec_operator;
colors[slice_end_idx] = highlight_spec_operator;
break;
}
case -1:
{
// syntax error
// Normally the entire token is colored red for us, but inside a double-quoted string
// that doesn't happen. As such, color the variable + the slice start red. Coloring any
// more than that looks bad, unless we're willing to try and detect where the double-quoted
// string ends, and I'd rather not do that.
std::fill(colors, colors + idx + 1, (highlight_spec_t)highlight_spec_error);
break;
}
} }
} }
return idx; return idx;
@ -861,7 +875,11 @@ static void color_argument_internal(const wcstring &buffstr, std::vector<highlig
*/ */
case e_double_quoted: case e_double_quoted:
{ {
colors[in_pos] = highlight_spec_quote; // slices are colored in advance, past `in_pos`, and we don't want to overwrite that
if (colors[in_pos] == highlight_spec_param)
{
colors[in_pos] = highlight_spec_quote;
}
switch (c) switch (c)
{ {
case L'"': case L'"':
@ -876,7 +894,7 @@ static void color_argument_internal(const wcstring &buffstr, std::vector<highlig
if (in_pos + 1 < buff_len) if (in_pos + 1 < buff_len)
{ {
const wchar_t escaped_char = buffstr.at(in_pos + 1); const wchar_t escaped_char = buffstr.at(in_pos + 1);
if (escaped_char == L'\\' || escaped_char == L'\'' || escaped_char == L'$') if (wcschr(L"\\\"\n$", escaped_char))
{ {
colors[in_pos] = highlight_spec_escape; //backslash colors[in_pos] = highlight_spec_escape; //backslash
colors[in_pos + 1] = highlight_spec_escape; //escaped char colors[in_pos + 1] = highlight_spec_escape; //escaped char
@ -1358,6 +1376,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
} }
break; break;
case parse_token_type_pipe:
case parse_token_type_background: case parse_token_type_background:
case parse_token_type_end: case parse_token_type_end:
case symbol_optional_background: case symbol_optional_background:

View File

@ -47,7 +47,7 @@ if not set -q fish_function_path
end end
if not contains $__fish_datadir/functions $fish_function_path if not contains $__fish_datadir/functions $fish_function_path
set fish_function_path[-1] $__fish_datadir/functions set fish_function_path $fish_function_path $__fish_datadir/functions
end end
if not set -q fish_complete_path if not set -q fish_complete_path
@ -55,7 +55,7 @@ if not set -q fish_complete_path
end end
if not contains $__fish_datadir/completions $fish_complete_path if not contains $__fish_datadir/completions $fish_complete_path
set fish_complete_path[-1] $__fish_datadir/completions set fish_complete_path $fish_complete_path $__fish_datadir/completions
end end
# #

View File

@ -1,5 +1,5 @@
function __fish_complete_cabal function __fish_complete_cabal
if type -f cabal >/dev/null if type -q -f cabal
set cmd (commandline -poc) set cmd (commandline -poc)
if test (count $cmd) -gt 1 if test (count $cmd) -gt 1
cabal $cmd[2..-1] --list-options cabal $cmd[2..-1] --list-options

View File

@ -2,7 +2,7 @@
function __fish_complete_vi -d "Compleletions for vi and its aliases" --argument-names cmd function __fish_complete_vi -d "Compleletions for vi and its aliases" --argument-names cmd
set -l is_vim set -l is_vim
if type $cmd > /dev/null if type -q $cmd
eval command $cmd --version >/dev/null ^/dev/null; and set -l is_vim vim eval command $cmd --version >/dev/null ^/dev/null; and set -l is_vim vim
end end

View File

@ -248,7 +248,7 @@ function __fish_config_interactive -d "Initializations that should be performed
# First check if we are on OpenSUSE since SUSE's handler has no options # First check if we are on OpenSUSE since SUSE's handler has no options
# and expects first argument to be a command and second database # and expects first argument to be a command and second database
# also check if there is command-not-found command. # also check if there is command-not-found command.
if begin; test -f /etc/SuSE-release; and type -p command-not-found > /dev/null 2> /dev/null; end if begin; test -f /etc/SuSE-release; and type -q -p command-not-found; end
function __fish_command_not_found_handler --on-event fish_command_not_found function __fish_command_not_found_handler --on-event fish_command_not_found
/usr/bin/command-not-found $argv /usr/bin/command-not-found $argv
end end
@ -263,7 +263,7 @@ function __fish_config_interactive -d "Initializations that should be performed
/usr/lib/command-not-found -- $argv /usr/lib/command-not-found -- $argv
end end
# Ubuntu Feisty places this command in the regular path instead # Ubuntu Feisty places this command in the regular path instead
else if type -p command-not-found > /dev/null 2> /dev/null else if type -q -p command-not-found
function __fish_command_not_found_handler --on-event fish_command_not_found function __fish_command_not_found_handler --on-event fish_command_not_found
command-not-found -- $argv command-not-found -- $argv
end end

View File

@ -18,7 +18,7 @@ function __fish_print_packages
end end
mkdir -m 700 -p $XDG_CACHE_HOME mkdir -m 700 -p $XDG_CACHE_HOME
if type -f apt-cache >/dev/null if type -q -f apt-cache
# Do not generate the cache as apparently sometimes this is slow. # Do not generate the cache as apparently sometimes this is slow.
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=547550 # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=547550
apt-cache --no-generate pkgnames (commandline -tc) ^/dev/null | sed -e 's/$/'\t$package'/' apt-cache --no-generate pkgnames (commandline -tc) ^/dev/null | sed -e 's/$/'\t$package'/'
@ -28,7 +28,7 @@ function __fish_print_packages
# Pkg is fast on FreeBSD and provides versioning info which we want for # Pkg is fast on FreeBSD and provides versioning info which we want for
# installed packages # installed packages
if begin if begin
type -f pkg > /dev/null type -q -f pkg
and test (uname) = "FreeBSD" and test (uname) = "FreeBSD"
end end
pkg query "%n-%v" pkg query "%n-%v"
@ -36,7 +36,7 @@ function __fish_print_packages
end end
# Caches for 5 minutes # Caches for 5 minutes
if type -f pacman >/dev/null if type -q -f pacman
set cache_file $XDG_CACHE_HOME/.pac-cache.$USER set cache_file $XDG_CACHE_HOME/.pac-cache.$USER
if test -f $cache_file if test -f $cache_file
cat $cache_file cat $cache_file
@ -53,7 +53,7 @@ function __fish_print_packages
end end
# yum is slow, just like rpm, so go to the background # yum is slow, just like rpm, so go to the background
if type -f /usr/share/yum-cli/completion-helper.py >/dev/null if type -q -f /usr/share/yum-cli/completion-helper.py
# If the cache is less than six hours old, we do not recalculate it # If the cache is less than six hours old, we do not recalculate it
@ -75,7 +75,7 @@ function __fish_print_packages
# Rpm is too slow for this job, so we set it up to do completions # Rpm is too slow for this job, so we set it up to do completions
# as a background job and cache the results. # as a background job and cache the results.
if type -f rpm >/dev/null if type -q -f rpm
# If the cache is less than five minutes old, we do not recalculate it # If the cache is less than five minutes old, we do not recalculate it
@ -99,12 +99,12 @@ function __fish_print_packages
# installed on the system packages is in completions/emerge.fish # installed on the system packages is in completions/emerge.fish
# eix is MUCH faster than emerge so use it if it is available # eix is MUCH faster than emerge so use it if it is available
if type -f eix > /dev/null if type -q -f eix
eix --only-names "^"(commandline -tc) | cut -d/ -f2 eix --only-names "^"(commandline -tc) | cut -d/ -f2
return return
else else
# FIXME? Seems to be broken # FIXME? Seems to be broken
if type -f emerge >/dev/null if type -q -f emerge
emerge -s \^(commandline -tc) |sgrep "^*" |cut -d\ -f3 |cut -d/ -f2 emerge -s \^(commandline -tc) |sgrep "^*" |cut -d\ -f3 |cut -d/ -f2
return return
end end

View File

@ -12,6 +12,7 @@ function alias --description "Legacy function for creating shellscript functions
set -l name set -l name
set -l body set -l body
set -l prefix set -l prefix
set -l first_word
switch (count $argv) switch (count $argv)
case 0 case 0
@ -21,8 +22,9 @@ function alias --description "Legacy function for creating shellscript functions
# Some seds (e.g. on Mac OS X), don't support \n in the RHS # Some seds (e.g. on Mac OS X), don't support \n in the RHS
# Use a literal newline instead # Use a literal newline instead
# http://sed.sourceforge.net/sedfaq4.html#s4.1 # http://sed.sourceforge.net/sedfaq4.html#s4.1
set -l tmp (echo $argv|sed -e "s/\([^=]\)=/\1\\ # The extra '' at the end is so $tmp[2] is guaranteed to work
/") set -l tmp (echo $argv|sed -e 's/\([^=]\{0,1\}\)=/\1\
/') ''
set name $tmp[1] set name $tmp[1]
set body $tmp[2] set body $tmp[2]
@ -35,18 +37,34 @@ function alias --description "Legacy function for creating shellscript functions
return 1 return 1
end end
# sanity check
if test -z "$name"
printf ( _ "%s: Name cannot be empty\n") alias
return 1
else if test -z "$body"
printf ( _ "%s: Body cannot be empty\n") alias
return 1
end
# Extract the first command from the body
switch $body
case \*\ \* \*\t\*
# note: strip leading spaces if present
set first_word (echo $body|sed -e 's/^[[:space:]]\{1,\}//;s/[[:space:]].*//;q')
case '*'
set first_word $body
end
# Prevent the alias from immediately running into an infinite recursion if # Prevent the alias from immediately running into an infinite recursion if
# $body starts with the same command as $name. # $body starts with the same command as $name.
switch $body if test $first_word = $name
case $name $name\ \* $name\t\* if contains $name (builtin --names)
if contains $name (builtin --names) set prefix builtin
set prefix builtin else
else set prefix command
set prefix command end
end
end end
eval "function $name --wraps $body; $prefix $body \$argv; end" eval "function $name --wraps $first_word; $prefix $body \$argv; end"
end end

View File

@ -51,7 +51,7 @@ function funced --description 'Edit function definition'
if test -n "$editor" if test -n "$editor"
set -l editor_cmd set -l editor_cmd
eval set editor_cmd $editor eval set editor_cmd $editor
if not type -f "$editor_cmd[1]" >/dev/null if not type -q -f "$editor_cmd[1]"
_ "funced: The value for \$EDITOR '$editor' could not be used because the command '$editor_cmd[1]' could not be found _ "funced: The value for \$EDITOR '$editor' could not be used because the command '$editor_cmd[1]' could not be found
" "
set editor fish set editor fish

View File

@ -25,7 +25,7 @@ function help --description 'Show help for the fish shell'
set -l graphical_browsers htmlview x-www-browser firefox galeon mozilla konqueror epiphany opera netscape rekonq google-chrome chromium-browser set -l graphical_browsers htmlview x-www-browser firefox galeon mozilla konqueror epiphany opera netscape rekonq google-chrome chromium-browser
set -l text_browsers htmlview www-browser links elinks lynx w3m set -l text_browsers htmlview www-browser links elinks lynx w3m
if type "$BROWSER" >/dev/null if type -q "$BROWSER"
# User has manually set a preferred browser, so we respect that # User has manually set a preferred browser, so we respect that
set fish_browser $BROWSER set fish_browser $BROWSER
@ -36,7 +36,7 @@ function help --description 'Show help for the fish shell'
else else
# Check for a text-based browser. # Check for a text-based browser.
for i in $text_browsers for i in $text_browsers
if type -f $i >/dev/null if type -q -f $i
set fish_browser $i set fish_browser $i
break break
end end
@ -46,7 +46,7 @@ function help --description 'Show help for the fish shell'
# browser to use instead. # browser to use instead.
if test "$DISPLAY" -a \( "$XAUTHORITY" = "$HOME/.Xauthority" -o "$XAUTHORITY" = "" \) if test "$DISPLAY" -a \( "$XAUTHORITY" = "$HOME/.Xauthority" -o "$XAUTHORITY" = "" \)
for i in $graphical_browsers for i in $graphical_browsers
if type -f $i >/dev/null if type -q -f $i
set fish_browser $i set fish_browser $i
set fish_browser_bg 1 set fish_browser_bg 1
break break
@ -55,17 +55,17 @@ function help --description 'Show help for the fish shell'
end end
# If the OS appears to be Windows (graphical), try to use cygstart # If the OS appears to be Windows (graphical), try to use cygstart
if type cygstart > /dev/null if type -q cygstart
set fish_browser cygstart set fish_browser cygstart
# If xdg-open is available, just use that # If xdg-open is available, just use that
else if type xdg-open > /dev/null else if type -q xdg-open
set fish_browser xdg-open set fish_browser xdg-open
end end
# On OS X, we go through osascript by default # On OS X, we go through osascript by default
if test (uname) = Darwin if test (uname) = Darwin
if type osascript >/dev/null if type -q osascript
set fish_browser osascript set fish_browser osascript
end end
end end
@ -92,7 +92,7 @@ function help --description 'Show help for the fish shell'
case $help_topics case $help_topics
set fish_help_page "index.html\#$fish_help_item" set fish_help_page "index.html\#$fish_help_item"
case "*" case "*"
if type -f $fish_help_item >/dev/null if type -q -f $fish_help_item
# Prefer to use fish's man pages, to avoid # Prefer to use fish's man pages, to avoid
# the annoying useless "builtin" man page bash # the annoying useless "builtin" man page bash
# installs on OS X # installs on OS X

View File

@ -13,7 +13,7 @@ if command ls --version 1>/dev/null 2>/dev/null
end end
if not set -q LS_COLORS if not set -q LS_COLORS
if type -f dircolors >/dev/null if type -q -f dircolors
eval (dircolors -c | sed 's/>&\/dev\/null$//') eval (dircolors -c | sed 's/>&\/dev\/null$//')
end end
end end

View File

@ -14,11 +14,11 @@ if not test (uname) = Darwin
end end
end end
if type -f cygstart >/dev/null if type -q -f cygstart
for i in $argv for i in $argv
cygstart $i cygstart $i
end end
else if type -f xdg-open >/dev/null else if type -q -f xdg-open
for i in $argv for i in $argv
xdg-open $i xdg-open $i
end end

View File

@ -2,7 +2,7 @@
# test -x in /usr/bin/seq because that's where it usually is and # test -x in /usr/bin/seq because that's where it usually is and
# that's substantially cheaper than the type function # that's substantially cheaper than the type function
if begin ; not test -x /usr/bin/seq ; and not type -f seq > /dev/null; end if begin ; not test -x /usr/bin/seq ; and not type -q -f seq; end
# No seq command # No seq command
function seq --description "Print sequences of numbers" function seq --description "Print sequences of numbers"
__fish_fallback_seq $argv __fish_fallback_seq $argv

View File

@ -4,79 +4,84 @@ function type --description "Print the type of a command"
# Initialize # Initialize
set -l res 1 set -l res 1
set -l mode normal set -l mode normal
set -l multi no
set -l selection all set -l selection all
#
# Get options
#
set -l options
set -l shortopt tpPafh
if not getopt -T > /dev/null
# GNU getopt
set -l longopt type,path,force-path,all,no-functions,help
set options -o $shortopt -l $longopt --
# Verify options
if not getopt -n type $options $argv >/dev/null
return 1
end
else
# Old getopt, used on OS X
set options $shortopt
# Verify options
if not getopt $options $argv >/dev/null
return 1
end
end
# Do the real getopt invocation # Parse options
set -l tmp (getopt $options $argv) set -l names
if test (count $argv) -gt 0
for i in (seq (count $argv))
set -l arg $argv[$i]
set -l needbreak 0
while test -n $arg
set -l flag $arg
set arg ''
switch $flag
case '--*'
# do nothing; this just prevents it matching the next case
case '-??*'
# combined flags
set -l IFS
echo -n $flag | read _ flag arg
set flag -$flag
set arg -$arg
end
switch $flag
case -t --type
if test $mode != quiet
set mode type
end
# Break tmp up into an array case -p --path
set -l opt if test $mode != quiet
eval set opt $tmp set mode path
end
for i in $opt
switch $i
case -t --type
set mode type
case -p --path case -P --force-path
set mode path if test $mode != quiet
set mode path
end
set selection files
case -P --force-path case -a --all
set mode path set multi yes
set selection files
case -a --all case -f --no-functions
set selection multi set selection files
case -f --no-functions case -q --quiet
set selection files set mode quiet
case -h --help case -h --help
__fish_print_help type __fish_print_help type
return 0 return 0
case -- case --
break set names $argv[$i..-1]
set -e names[1]
set needbreak 1
break
case '*'
set names $argv[$i..-1]
set needbreak 1
break
end
end
if test $needbreak -eq 1
break
end
end end
end end
# Check all possible types for the remaining arguments # Check all possible types for the remaining arguments
for i in $argv for i in $names
switch $i
case '-*'
continue
end
# Found will be set to 1 if a match is found # Found will be set to 1 if a match is found
set found 0 set -l found 0
if test $selection != files if test $selection != files
if contains -- $i (functions -na) if functions -q -- $i
set res 0 set res 0
set found 1 set found 1
switch $mode switch $mode
@ -86,12 +91,8 @@ function type --description "Print the type of a command"
case type case type
echo (_ 'function') echo (_ 'function')
case path
echo
end end
if test $selection != multi if test $multi != yes
continue continue
end end
end end
@ -106,11 +107,8 @@ function type --description "Print the type of a command"
case type case type
echo (_ 'builtin') echo (_ 'builtin')
case path
echo
end end
if test $selection != multi if test $multi != yes
continue continue
end end
end end
@ -118,33 +116,31 @@ function type --description "Print the type of a command"
end end
set -l paths set -l paths
if test $selection != multi if test $multi != yes
set paths (which $i ^/dev/null) set paths (command -s -- $i)
else else
set paths (which -a $i ^/dev/null) set paths (which -a -- $i ^/dev/null)
end end
for path in $paths for path in $paths
if test -x (echo $path) set res 0
set res 0 set found 1
set found 1 switch $mode
switch $mode case normal
case normal printf (_ '%s is %s\n') $i $path
printf (_ '%s is %s\n') $i $path
case type case type
echo (_ 'file') echo (_ 'file')
case path case path
echo $path echo $path
end end
if test $selection != multi if test $multi != yes
continue continue
end
end end
end end
if test $found = 0 if begin; test $found = 0; and test $mode != quiet; end
printf (_ "%s: Could not find '%s'\n") type $i printf (_ "%s: Could not find '%s'\n") type $i >&2
end end
end end

View File

@ -26,7 +26,7 @@ if term:
os.environ['TERM'] = term os.environ['TERM'] = term
import subprocess import subprocess
import re, socket, cgi, select, time, glob, random, string import re, socket, cgi, select, time, glob, random, string, binascii
try: try:
import json import json
except ImportError: except ImportError:
@ -617,14 +617,16 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_get_current_prompt(self): def do_get_current_prompt(self):
# Return the current prompt # Return the current prompt
# We run 'false' to demonstrate how the prompt shows the command status (#1624)
prompt_func, err = run_fish_cmd('functions fish_prompt') prompt_func, err = run_fish_cmd('functions fish_prompt')
result = self.do_get_prompt('builtin cd "' + initial_wd + '" ; fish_prompt', prompt_func.strip(), {'name': 'Current'}) result = self.do_get_prompt('builtin cd "' + initial_wd + '" ; false ; fish_prompt', prompt_func.strip(), {'name': 'Current'})
return result return result
def do_get_sample_prompt(self, text, extras_dict): def do_get_sample_prompt(self, text, extras_dict):
# Return the prompt you get from the given text # Return the prompt you get from the given text
# extras_dict is a dictionary whose values get merged in # extras_dict is a dictionary whose values get merged in
cmd = text + "\n cd \"" + initial_wd + "\" \n fish_prompt\n" # We run 'false' to demonstrate how the prompt shows the command status (#1624)
cmd = text + "\n builtin cd \"" + initial_wd + "\" \n false \n fish_prompt\n"
return self.do_get_prompt(cmd, text.strip(), extras_dict) return self.do_get_prompt(cmd, text.strip(), extras_dict)
def parse_one_sample_prompt_hash(self, line, result_dict): def parse_one_sample_prompt_hash(self, line, result_dict):
@ -680,6 +682,14 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
result.extend([r for r in sample_results if r]) result.extend([r for r in sample_results if r])
return result return result
def secure_startswith(self, haystack, needle):
if len(haystack) < len(needle):
return False
bits = 0
for x,y in zip(haystack, needle):
bits |= ord(x) ^ ord(y)
return bits == 0
def font_size_for_ansi_prompt(self, prompt_demo_ansi): def font_size_for_ansi_prompt(self, prompt_demo_ansi):
width = ansi_prompt_line_width(prompt_demo_ansi) width = ansi_prompt_line_width(prompt_demo_ansi)
# Pick a font size # Pick a font size
@ -697,7 +707,7 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
p = self.path p = self.path
authpath = '/' + authkey authpath = '/' + authkey
if p.startswith(authpath): if self.secure_startswith(p, authpath):
p = p[len(authpath):] p = p[len(authpath):]
else: else:
return self.send_error(403) return self.send_error(403)
@ -736,7 +746,7 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
p = self.path p = self.path
authpath = '/' + authkey authpath = '/' + authkey
if p.startswith(authpath): if self.secure_startswith(p, authpath):
p = p[len(authpath):] p = p[len(authpath):]
else: else:
return self.send_error(403) return self.send_error(403)
@ -851,7 +861,7 @@ where = os.path.dirname(sys.argv[0])
os.chdir(where) os.chdir(where)
# Generate a 16-byte random key as a hexadecimal string # Generate a 16-byte random key as a hexadecimal string
authkey = hex(random.getrandbits(16*4))[2:] authkey = binascii.b2a_hex(os.urandom(16))
# Try to find a suitable port # Try to find a suitable port
PORT = 8000 PORT = 8000

30
tests/expansion.err Normal file
View File

@ -0,0 +1,30 @@
Array index out of bounds
fish: show "$foo[2]"
^
Array index out of bounds
fish: show $foo[2]
^
Array index out of bounds
fish: show "$foo[1 2]"
^
Array index out of bounds
fish: show $foo[1 2]
^
Array index out of bounds
fish: show "$foo[2 1]"
^
Array index out of bounds
fish: show $foo[2 1]
^
Invalid index value
fish: echo "$foo[d]"
^
Invalid index value
fish: echo $foo[d]
^
Array index out of bounds
fish: echo ()[1]
^
Invalid index value
fish: echo ()[d]
^

82
tests/expansion.in Normal file
View File

@ -0,0 +1,82 @@
# Test expansion of variables
function show --description 'Prints argument count followed by arguments'
echo (count $argv) $argv
end
set -l foo
show "$foo"
show $foo
show "prefix$foo"
show prefix$foo
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo ''
show "$foo"
show $foo
show "prefix$foo"
show prefix$foo
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo bar
set -l bar
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l bar baz
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l bar baz quux
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo bar fooer fooest
set -l fooer
set -l fooest
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l fooer ''
show $$foo
show prefix$$foo
set -l foo bar '' fooest
show "$$foo"
show $$foo
show "prefix$$foo"
show prefix$$foo
set -l foo
show "$foo[1]"
show $foo[1]
show "$foo[-1]"
show $foo[-1]
show "$foo[2]"
show $foo[2]
show "$foo[1 2]"
show $foo[1 2]
show "$foo[2 1]"
show $foo[2 1]
echo "$foo[d]"
echo $foo[d]
echo ()[1]
echo ()[d]

42
tests/expansion.out Normal file
View File

@ -0,0 +1,42 @@
1
0
1 prefix
0
1
0
1 prefix
0
1
1
1 prefix
1 prefix
1
0
1 prefix
0
1
0
1 prefix
0
1 baz
1 baz
1 prefixbaz
1 prefixbaz
1 baz quux
2 baz quux
1 prefixbaz quux
2 prefixbaz prefixquux
1 baz quux fooer fooest
2 baz quux
1 prefixbaz quux fooer fooest
2 prefixbaz prefixquux
3 baz quux
3 prefixbaz prefixquux prefix
1 baz quux fooest
2 baz quux
1 prefixbaz quux fooest
2 prefixbaz prefixquux
1
0
1
0

1
tests/expansion.status Normal file
View File

@ -0,0 +1 @@
0

0
tests/read.err Normal file
View File

72
tests/read.in Normal file
View File

@ -0,0 +1,72 @@
#
# Test read builtin and IFS
#
count (echo one\ntwo)
set -l IFS \t
count (echo one\ntwo)
set -l IFS
count (echo one\ntwo)
set -le IFS
function print_vars --no-scope-shadowing
set -l space
set -l IFS \n # ensure our command substitution works right
for var in $argv
echo -n $space (count $$var) \'$$var\'
set space ''
end
echo
end
echo
echo 'hello there' | read -l one two
print_vars one two
echo 'hello there' | read -l one
print_vars one
echo '' | read -l one
print_vars one
echo '' | read -l one two
print_vars one two
echo 'test' | read -l one two three
print_vars one two three
echo
set -l IFS
echo 'hello' | read -l one
print_vars one
echo 'hello' | read -l one two
print_vars one two
echo 'hello' | read -l one two three
print_vars one two three
echo '' | read -l one
print_vars one
echo 't' | read -l one two
print_vars one two
echo 't' | read -l one two three
print_vars one two three
echo ' t' | read -l one two
print_vars one two
set -le IFS
echo
echo 'hello there' | read -la ary
print_vars ary
echo 'hello' | read -la ary
print_vars ary
echo 'this is a bunch of words' | read -la ary
print_vars ary
echo ' one two three' | read -la ary
print_vars ary
echo '' | read -la ary
print_vars ary
echo
set -l IFS
echo 'hello' | read -la ary
print_vars ary
echo 'h' | read -la ary
print_vars ary
echo '' | read -la ary
print_vars ary
set -le IFS

27
tests/read.out Normal file
View File

@ -0,0 +1,27 @@
2
2
1
1 'hello' 1 'there'
1 'hello there'
1 ''
1 '' 1 ''
1 'test' 1 '' 1 ''
1 'hello'
1 'h' 1 'ello'
1 'h' 1 'e' 1 'llo'
1 ''
1 't' 1 ''
1 't' 1 '' 1 ''
1 ' ' 1 't'
2 'hello' 'there'
1 'hello'
6 'this' 'is' 'a' 'bunch' 'of' 'words'
3 'one' 'two' 'three'
0
5 'h' 'e' 'l' 'l' 'o'
1 'h'
0

1
tests/read.status Normal file
View File

@ -0,0 +1 @@
0

View File

@ -110,6 +110,6 @@ function fish_test_type_zzz
true true
end end
# Should succeed # Should succeed
type fish_test_type_zzz >/dev/null ; echo $status type -q fish_test_type_zzz ; echo $status
# Should fail # Should fail
type -f fish_test_type_zzz >/dev/null ; echo $status type -q -f fish_test_type_zzz ; echo $status

View File

@ -1,5 +1,7 @@
Testing high level script functionality Testing high level script functionality
File expansion.in tested ok
File printf.in tested ok File printf.in tested ok
File read.in tested ok
File test1.in tested ok File test1.in tested ok
File test2.in tested ok File test2.in tested ok
File test3.in tested ok File test3.in tested ok

View File

@ -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);

View File

@ -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