mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 04:43:10 +00:00
parent
4b079e16e5
commit
6e56637cf0
15 changed files with 74 additions and 117 deletions
|
@ -51,6 +51,7 @@ This section is for changes merged to the `major` branch that are not also merge
|
||||||
- The machine hostname, where available, is now exposed as `$hostname` which is now a reserved variable. This drops the dependency on the `hostname` executable (#4422).
|
- The machine hostname, where available, is now exposed as `$hostname` which is now a reserved variable. This drops the dependency on the `hostname` executable (#4422).
|
||||||
- `functions --handlers` can be used to show event handlers (#4694).
|
- `functions --handlers` can be used to show event handlers (#4694).
|
||||||
- Variables set in `if` and `while` conditions are available outside the block (#4820).
|
- Variables set in `if` and `while` conditions are available outside the block (#4820).
|
||||||
|
- The `?` wildcard has been removed (#4520).
|
||||||
|
|
||||||
## Other significant changes
|
## Other significant changes
|
||||||
- Command substitution output is now limited to 10 MB by default (#3822).
|
- Command substitution output is now limited to 10 MB by default (#3822).
|
||||||
|
|
|
@ -92,7 +92,6 @@ Some characters can not be written directly on the command line. For these chara
|
||||||
- '<code>\\$</code>' escapes the dollar character
|
- '<code>\\$</code>' escapes the dollar character
|
||||||
- '<code>\\\\</code>' escapes the backslash character
|
- '<code>\\\\</code>' escapes the backslash character
|
||||||
- '<code>\\*</code>' escapes the star character
|
- '<code>\\*</code>' escapes the star character
|
||||||
- '<code>\\?</code>' escapes the question mark character
|
|
||||||
- '<code>\\~</code>' escapes the tilde character
|
- '<code>\\~</code>' escapes the tilde character
|
||||||
- '<code>\\#</code>' escapes the hash character
|
- '<code>\\#</code>' escapes the hash character
|
||||||
- '<code>\\(</code>' escapes the left parenthesis character
|
- '<code>\\(</code>' escapes the left parenthesis character
|
||||||
|
@ -330,7 +329,7 @@ These are the general purpose tab completions that `fish` provides:
|
||||||
|
|
||||||
- Completion of usernames for tilde expansion.
|
- Completion of usernames for tilde expansion.
|
||||||
|
|
||||||
- Completion of filenames, even on strings with wildcards such as '`*`', '`**`' and '`?`'.
|
- Completion of filenames, even on strings with wildcards such as '`*`' and '`**`'.
|
||||||
|
|
||||||
`fish` provides a large number of program specific completions. Most of these completions are simple options like the `-l` option for `ls`, but some are more advanced. The latter include:
|
`fish` provides a large number of program specific completions. Most of these completions are simple options like the `-l` option for `ls`, but some are more advanced. The latter include:
|
||||||
|
|
||||||
|
@ -418,9 +417,7 @@ When an argument for a program is given on the commandline, it undergoes the pro
|
||||||
|
|
||||||
\subsection expand-wildcard Wildcards
|
\subsection expand-wildcard Wildcards
|
||||||
|
|
||||||
If a star (`*`) or a question mark (`?`) is present in the parameter, `fish` attempts to match the given parameter to any files in such a way that:
|
If a star (`*`) is present in the parameter, `fish` attempts to match the given parameter to any files in such a way that:
|
||||||
|
|
||||||
- `?` can match any single character except '/'.
|
|
||||||
|
|
||||||
- `*` can match any string of characters not containing '/'. This includes matching an empty string.
|
- `*` can match any string of characters not containing '/'. This includes matching an empty string.
|
||||||
|
|
||||||
|
@ -446,8 +443,6 @@ Examples:
|
||||||
|
|
||||||
- `a*` matches any files beginning with an 'a' in the current directory.
|
- `a*` matches any files beginning with an 'a' in the current directory.
|
||||||
|
|
||||||
- `???` matches any file in the current directory whose name is exactly three characters long.
|
|
||||||
|
|
||||||
- `**` matches any files and directories in the current directory and all of its subdirectories.
|
- `**` matches any files and directories in the current directory and all of its subdirectories.
|
||||||
|
|
||||||
Note that for most commands, if any wildcard fails to expand, the command is not executed, <a href='#variables-status'>`$status`</a> is set to nonzero, and a warning is printed. This behavior is consistent with setting `shopt -s failglob` in bash. There are exactly 3 exceptions, namely <a href="commands.html#set">`set`</a>, <a href="commands.html#count">`count`</a> and <a href="commands.html#for">`for`</a>. Their globs are permitted to expand to zero arguments, as with `shopt -s nullglob` in bash.
|
Note that for most commands, if any wildcard fails to expand, the command is not executed, <a href='#variables-status'>`$status`</a> is set to nonzero, and a warning is printed. This behavior is consistent with setting `shopt -s failglob` in bash. There are exactly 3 exceptions, namely <a href="commands.html#set">`set`</a>, <a href="commands.html#count">`count`</a> and <a href="commands.html#for">`for`</a>. Their globs are permitted to expand to zero arguments, as with `shopt -s nullglob` in bash.
|
||||||
|
|
|
@ -6,25 +6,18 @@ function __fish_git_commits
|
||||||
# This allows filtering by subject with the new pager!
|
# This allows filtering by subject with the new pager!
|
||||||
# Because even subject lines can be quite long,
|
# Because even subject lines can be quite long,
|
||||||
# trim them (abbrev'd hash+tab+subject) to 73 characters
|
# trim them (abbrev'd hash+tab+subject) to 73 characters
|
||||||
command git log --pretty=tformat:"%h"\t"%s" --all --max-count=1000 ^/dev/null \
|
command git log --pretty=tformat:"%h"\t"%s" --all --max-count=1000 ^/dev/null | string replace -r '(.{73}).+' '$1…'
|
||||||
| string replace -r '(.{73}).+' '$1…'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_recent_commits
|
function __fish_git_recent_commits
|
||||||
# Like __fish_git_commits, but not on all branches and limited to
|
# Like __fish_git_commits, but not on all branches and limited to
|
||||||
# the last 50 commits. Used for fixup, where only the current branch
|
# the last 50 commits. Used for fixup, where only the current branch
|
||||||
# and the latest commits make sense.
|
# and the latest commits make sense.
|
||||||
command git log --pretty=tformat:"%h"\t"%s" --max-count=50 ^/dev/null \
|
command git log --pretty=tformat:"%h"\t"%s" --max-count=50 ^/dev/null | string replace -r '(.{73}).+' '$1…'
|
||||||
| string replace -r '(.{73}).+' '$1…'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_branches
|
function __fish_git_branches
|
||||||
command git branch --no-color -a $argv ^/dev/null \
|
command git branch --no-color -a $argv ^/dev/null # Filter out detached heads and such ("(HEAD detached at SOMESHA)", localized). | string match -v '\* (*)' | string match -r -v ' -> ' | string trim -c "* " # We assume anything that's not remote is a local branch. | string replace -r '^(?!remotes/)(.*)' '$1\tLocal Branch' | string replace -r "^remotes/(.*)" '$1\tRemote Branch'
|
||||||
# Filter out detached heads and such ("(HEAD detached at SOMESHA)", localized).
|
|
||||||
| string match -v '\* (*)' | string match -r -v ' -> ' | string trim -c "* " \
|
|
||||||
# We assume anything that's not remote is a local branch.
|
|
||||||
| string replace -r '^(?!remotes/)(.*)' '$1\tLocal Branch' \
|
|
||||||
| string replace -r "^remotes/(.*)" '$1\tRemote Branch'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_git_tags
|
function __fish_git_tags
|
||||||
|
@ -96,8 +89,7 @@ function __fish_git_files
|
||||||
# E.g. `git reset $submodule` won't do anything (not even print an error).
|
# E.g. `git reset $submodule` won't do anything (not even print an error).
|
||||||
# --ignore-submodules=all was added in git 1.7.2, released July 2010.
|
# --ignore-submodules=all was added in git 1.7.2, released July 2010.
|
||||||
set -l use_next
|
set -l use_next
|
||||||
command git status --porcelain -z --ignore-submodules=all \
|
command git status --porcelain -z --ignore-submodules=all | while read -lz -d '' line
|
||||||
| while read -lz -d '' line
|
|
||||||
# The entire line is the "from" from a rename.
|
# The entire line is the "from" from a rename.
|
||||||
if set -q use_next[1]
|
if set -q use_next[1]
|
||||||
if contains -- $use_next $argv
|
if contains -- $use_next $argv
|
||||||
|
@ -144,28 +136,31 @@ function __fish_git_files
|
||||||
case 'A ' AM AD
|
case 'A ' AM AD
|
||||||
# Additions are only shown here if they are staged.
|
# Additions are only shown here if they are staged.
|
||||||
# Otherwise it's an untracked file.
|
# Otherwise it's an untracked file.
|
||||||
contains -- added $argv; or contains -- all-staged $argv
|
contains -- added $argv
|
||||||
|
or contains -- all-staged $argv
|
||||||
and printf '%s\t%s\n' "$file" $added_desc
|
and printf '%s\t%s\n' "$file" $added_desc
|
||||||
case '?M'
|
case '*M'
|
||||||
# Modified
|
# Modified
|
||||||
contains -- modified $argv
|
contains -- modified $argv
|
||||||
and printf '%s\t%s\n' "$file" $modified_desc
|
and printf '%s\t%s\n' "$file" $modified_desc
|
||||||
case 'M?'
|
case 'M*'
|
||||||
# If the character is first ("M "), then that means it's "our" change,
|
# If the character is first ("M "), then that means it's "our" change,
|
||||||
# which means it is staged.
|
# which means it is staged.
|
||||||
# This is useless for many commands - e.g. `checkout` won't do anything with this.
|
# This is useless for many commands - e.g. `checkout` won't do anything with this.
|
||||||
# So it needs to be requested explicitly.
|
# So it needs to be requested explicitly.
|
||||||
contains -- modified-staged $argv; or contains -- all-staged $argv
|
contains -- modified-staged $argv
|
||||||
|
or contains -- all-staged $argv
|
||||||
and printf '%s\t%s\n' "$file" $staged_modified_desc
|
and printf '%s\t%s\n' "$file" $staged_modified_desc
|
||||||
case '?D'
|
case '*D'
|
||||||
contains -- deleted $argv
|
contains -- deleted $argv
|
||||||
and printf '%s\t%s\n' "$file" $deleted_desc
|
and printf '%s\t%s\n' "$file" $deleted_desc
|
||||||
case 'D?'
|
case 'D*'
|
||||||
# TODO: The docs are unclear on this.
|
# TODO: The docs are unclear on this.
|
||||||
# There is both X unmodified and Y either M or D ("not updated")
|
# There is both X unmodified and Y either M or D ("not updated")
|
||||||
# and Y is D and X is unmodified or [MARC] ("deleted in work tree").
|
# and Y is D and X is unmodified or [MARC] ("deleted in work tree").
|
||||||
# For our purposes, we assume this is a staged deletion.
|
# For our purposes, we assume this is a staged deletion.
|
||||||
contains -- deleted-staged $argv; or contains -- all-staged $argv
|
contains -- deleted-staged $argv
|
||||||
|
or contains -- all-staged $argv
|
||||||
and printf '%s\t%s\n' "$file" $staged_deleted_desc
|
and printf '%s\t%s\n' "$file" $staged_deleted_desc
|
||||||
case '\?\?'
|
case '\?\?'
|
||||||
# Untracked
|
# Untracked
|
||||||
|
@ -201,7 +196,8 @@ end
|
||||||
function __fish_git_needs_command
|
function __fish_git_needs_command
|
||||||
set cmd (commandline -opc)
|
set cmd (commandline -opc)
|
||||||
set -l skip_next 1
|
set -l skip_next 1
|
||||||
set -q cmd[2]; or return 0
|
set -q cmd[2]
|
||||||
|
or return 0
|
||||||
# Skip first word because it's "git" or a wrapper
|
# Skip first word because it's "git" or a wrapper
|
||||||
for c in $cmd[2..-1]
|
for c in $cmd[2..-1]
|
||||||
test $skip_next -eq 0
|
test $skip_next -eq 0
|
||||||
|
|
|
@ -995,12 +995,6 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
||||||
out += *in;
|
out += *in;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ANY_CHAR: {
|
|
||||||
// Experimental fix for #1614. The hope is that any time these appear in a
|
|
||||||
// string, they came from wildcard expansion.
|
|
||||||
out += L'?';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ANY_STRING: {
|
case ANY_STRING: {
|
||||||
out += L'*';
|
out += L'*';
|
||||||
break;
|
break;
|
||||||
|
@ -1022,7 +1016,6 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
||||||
case L']':
|
case L']':
|
||||||
case L'{':
|
case L'{':
|
||||||
case L'}':
|
case L'}':
|
||||||
case L'?':
|
|
||||||
case L'*':
|
case L'*':
|
||||||
case L'|':
|
case L'|':
|
||||||
case L';':
|
case L';':
|
||||||
|
@ -1356,12 +1349,6 @@ static bool unescape_string_internal(const wchar_t *const input, const size_t in
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case L'?': {
|
|
||||||
if (unescape_special) {
|
|
||||||
to_append_or_none = ANY_CHAR;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case L'$': {
|
case L'$': {
|
||||||
if (unescape_special) {
|
if (unescape_special) {
|
||||||
to_append_or_none = VARIABLE_EXPAND;
|
to_append_or_none = VARIABLE_EXPAND;
|
||||||
|
|
|
@ -823,15 +823,11 @@ static void remove_internal_separator(wcstring *str, bool conv) {
|
||||||
// Remove all instances of INTERNAL_SEPARATOR.
|
// Remove all instances of INTERNAL_SEPARATOR.
|
||||||
str->erase(std::remove(str->begin(), str->end(), (wchar_t)INTERNAL_SEPARATOR), str->end());
|
str->erase(std::remove(str->begin(), str->end(), (wchar_t)INTERNAL_SEPARATOR), str->end());
|
||||||
|
|
||||||
// If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*',
|
// If conv is true, replace all instances of ANY_STRING with '*',
|
||||||
// ANY_STRING_RECURSIVE with '*'.
|
// ANY_STRING_RECURSIVE with '*'.
|
||||||
if (conv) {
|
if (conv) {
|
||||||
for (size_t idx = 0; idx < str->size(); idx++) {
|
for (size_t idx = 0; idx < str->size(); idx++) {
|
||||||
switch (str->at(idx)) {
|
switch (str->at(idx)) {
|
||||||
case ANY_CHAR: {
|
|
||||||
str->at(idx) = L'?';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ANY_STRING:
|
case ANY_STRING:
|
||||||
case ANY_STRING_RECURSIVE: {
|
case ANY_STRING_RECURSIVE: {
|
||||||
str->at(idx) = L'*';
|
str->at(idx) = L'*';
|
||||||
|
@ -918,7 +914,7 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<
|
||||||
wcstring path_to_expand = input;
|
wcstring path_to_expand = input;
|
||||||
|
|
||||||
remove_internal_separator(&path_to_expand, flags & EXPAND_SKIP_WILDCARDS);
|
remove_internal_separator(&path_to_expand, flags & EXPAND_SKIP_WILDCARDS);
|
||||||
const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_CHAR */);
|
const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_STRING */);
|
||||||
|
|
||||||
if (has_wildcard && (flags & EXECUTABLES_ONLY)) {
|
if (has_wildcard && (flags & EXECUTABLES_ONLY)) {
|
||||||
; // don't do wildcard expansion for executables, see issue #785
|
; // don't do wildcard expansion for executables, see issue #785
|
||||||
|
|
|
@ -4120,15 +4120,16 @@ static void test_string() {
|
||||||
{{L"string", L"match", 0}, STATUS_INVALID_ARGS, L""},
|
{{L"string", L"match", 0}, STATUS_INVALID_ARGS, L""},
|
||||||
{{L"string", L"match", L"", 0}, STATUS_CMD_ERROR, L""},
|
{{L"string", L"match", L"", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
|
{{L"string", L"match", L"", L"", 0}, STATUS_CMD_OK, L"\n"},
|
||||||
{{L"string", L"match", L"?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
{{L"string", L"match", L"?", L"a", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"*", L"", 0}, STATUS_CMD_OK, L"\n"},
|
{{L"string", L"match", L"*", L"", 0}, STATUS_CMD_OK, L"\n"},
|
||||||
{{L"string", L"match", L"**", L"", 0}, STATUS_CMD_OK, L"\n"},
|
{{L"string", L"match", L"**", L"", 0}, STATUS_CMD_OK, L"\n"},
|
||||||
{{L"string", L"match", L"*", L"xyzzy", 0}, STATUS_CMD_OK, L"xyzzy\n"},
|
{{L"string", L"match", L"*", L"xyzzy", 0}, STATUS_CMD_OK, L"xyzzy\n"},
|
||||||
{{L"string", L"match", L"**", L"plugh", 0}, STATUS_CMD_OK, L"plugh\n"},
|
{{L"string", L"match", L"**", L"plugh", 0}, STATUS_CMD_OK, L"plugh\n"},
|
||||||
{{L"string", L"match", L"a*b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
{{L"string", L"match", L"a*b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
||||||
{{L"string", L"match", L"a??b", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
{{L"string", L"match", L"a??b", L"axxb", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"-i", L"a??B", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
{{L"string", L"match", L"a??b", L"a??b", 0}, STATUS_CMD_OK, L"a??b\n"},
|
||||||
{{L"string", L"match", L"-i", L"a??b", L"Axxb", 0}, STATUS_CMD_OK, L"Axxb\n"},
|
{{L"string", L"match", L"-i", L"a??B", L"axxb", 0}, STATUS_CMD_ERROR, L""},
|
||||||
|
{{L"string", L"match", L"-i", L"a??b", L"Axxb", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"a*", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
{{L"string", L"match", L"a*", L"axxb", 0}, STATUS_CMD_OK, L"axxb\n"},
|
||||||
{{L"string", L"match", L"*a", L"xxa", 0}, STATUS_CMD_OK, L"xxa\n"},
|
{{L"string", L"match", L"*a", L"xxa", 0}, STATUS_CMD_OK, L"xxa\n"},
|
||||||
{{L"string", L"match", L"*a*", L"axa", 0}, STATUS_CMD_OK, L"axa\n"},
|
{{L"string", L"match", L"*a*", L"axa", 0}, STATUS_CMD_OK, L"axa\n"},
|
||||||
|
@ -4137,14 +4138,14 @@ static void test_string() {
|
||||||
{{L"string", L"match", L"*a", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
{{L"string", L"match", L"*a", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
||||||
{{L"string", L"match", L"a*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
{{L"string", L"match", L"a*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
||||||
{{L"string", L"match", L"a*b*c", L"axxbyyc", 0}, STATUS_CMD_OK, L"axxbyyc\n"},
|
{{L"string", L"match", L"a*b*c", L"axxbyyc", 0}, STATUS_CMD_OK, L"axxbyyc\n"},
|
||||||
{{L"string", L"match", L"a*b?c", L"axxbyc", 0}, STATUS_CMD_OK, L"axxbyc\n"},
|
{{L"string", L"match", L"a*b?c", L"axxb?c", 0}, STATUS_CMD_OK, L"axxb?c\n"},
|
||||||
{{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
{{L"string", L"match", L"*?", L"a", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
|
{{L"string", L"match", L"*?", L"ab", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_OK, L"a\n"},
|
{{L"string", L"match", L"?*", L"a", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_OK, L"ab\n"},
|
{{L"string", L"match", L"?*", L"ab", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"\\*", L"*", 0}, STATUS_CMD_OK, L"*\n"},
|
{{L"string", L"match", L"\\*", L"*", 0}, STATUS_CMD_OK, L"*\n"},
|
||||||
{{L"string", L"match", L"a*\\", L"abc\\", 0}, STATUS_CMD_OK, L"abc\\\n"},
|
{{L"string", L"match", L"a*\\", L"abc\\", 0}, STATUS_CMD_OK, L"abc\\\n"},
|
||||||
{{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_OK, L"abc?\n"},
|
{{L"string", L"match", L"a*\\?", L"abc?", 0}, STATUS_CMD_ERROR, L""},
|
||||||
|
|
||||||
{{L"string", L"match", L"?", L"", 0}, STATUS_CMD_ERROR, L""},
|
{{L"string", L"match", L"?", L"", 0}, STATUS_CMD_ERROR, L""},
|
||||||
{{L"string", L"match", L"?", L"ab", 0}, STATUS_CMD_ERROR, L""},
|
{{L"string", L"match", L"?", L"ab", 0}, STATUS_CMD_ERROR, L""},
|
||||||
|
@ -4428,24 +4429,27 @@ static void test_illegal_command_exit_code() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const command_result_tuple_t tests[] = {
|
const command_result_tuple_t tests[] = {
|
||||||
{L"echo -n", STATUS_CMD_OK}, {L"pwd", STATUS_CMD_OK},
|
{L"echo -n", STATUS_CMD_OK},
|
||||||
// a `)` without a matching `(` is now a tokenizer error, and cannot be executed even as an illegal command
|
{L"pwd", STATUS_CMD_OK},
|
||||||
|
// a `)` without a matching `(` is now a tokenizer error, and cannot be executed even as an
|
||||||
|
// illegal command
|
||||||
// {L")", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}
|
// {L")", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}, {L") ", STATUS_ILLEGAL_CMD}
|
||||||
{L"*", STATUS_ILLEGAL_CMD}, {L"**", STATUS_ILLEGAL_CMD},
|
{L"*", STATUS_ILLEGAL_CMD},
|
||||||
{L"?", STATUS_ILLEGAL_CMD}, {L"abc?def", STATUS_ILLEGAL_CMD},
|
{L"**", STATUS_ILLEGAL_CMD},
|
||||||
|
{L"?", STATUS_CMD_UNKNOWN},
|
||||||
|
{L"abc?def", STATUS_CMD_UNKNOWN},
|
||||||
};
|
};
|
||||||
|
|
||||||
int res = 0;
|
int res = 0;
|
||||||
const io_chain_t empty_ios;
|
const io_chain_t empty_ios;
|
||||||
parser_t &parser = parser_t::principal_parser();
|
parser_t &parser = parser_t::principal_parser();
|
||||||
|
|
||||||
size_t i = 0;
|
for (const auto &test : tests) {
|
||||||
for (i = 0; i < sizeof tests / sizeof *tests; i++) {
|
res = parser.eval(test.txt, empty_ios, TOP);
|
||||||
res = parser.eval(tests[i].txt, empty_ios, TOP);
|
|
||||||
|
|
||||||
int exit_status = res ? STATUS_CMD_UNKNOWN : proc_get_last_status();
|
int exit_status = res ? STATUS_CMD_UNKNOWN : proc_get_last_status();
|
||||||
if (exit_status != tests[i].result) {
|
if (exit_status != test.result) {
|
||||||
err(L"command '%ls': expected exit code %d , got %d", tests[i].txt, tests[i].result,
|
err(L"command '%ls': expected exit code %d , got %d", test.txt, test.result,
|
||||||
exit_status);
|
exit_status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,6 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l
|
||||||
case BRACE_BEGIN:
|
case BRACE_BEGIN:
|
||||||
case BRACE_END:
|
case BRACE_END:
|
||||||
case BRACE_SEP:
|
case BRACE_SEP:
|
||||||
case ANY_CHAR:
|
|
||||||
case ANY_STRING:
|
case ANY_STRING:
|
||||||
case ANY_STRING_RECURSIVE: {
|
case ANY_STRING_RECURSIVE: {
|
||||||
has_magic = 1;
|
has_magic = 1;
|
||||||
|
@ -549,7 +548,6 @@ static void color_argument_internal(const wcstring &buffstr,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case L'*':
|
case L'*':
|
||||||
case L'?':
|
|
||||||
case L'(':
|
case L'(':
|
||||||
case L')': {
|
case L')': {
|
||||||
colors[in_pos] = highlight_spec_operator;
|
colors[in_pos] = highlight_spec_operator;
|
||||||
|
|
|
@ -423,9 +423,7 @@ wcstring parse_util_unescape_wildcards(const wcstring &str) {
|
||||||
for (size_t i = 0; cs[i] != L'\0'; i++) {
|
for (size_t i = 0; cs[i] != L'\0'; i++) {
|
||||||
if (cs[i] == L'*') {
|
if (cs[i] == L'*') {
|
||||||
result.push_back(ANY_STRING);
|
result.push_back(ANY_STRING);
|
||||||
} else if (cs[i] == L'?') {
|
} else if (cs[i] == L'\\' && cs[i + 1] == L'*') {
|
||||||
result.push_back(ANY_CHAR);
|
|
||||||
} else if (cs[i] == L'\\' && (cs[i + 1] == L'*' || cs[i + 1] == L'?')) {
|
|
||||||
result.push_back(cs[i + 1]);
|
result.push_back(cs[i + 1]);
|
||||||
i += 1;
|
i += 1;
|
||||||
} else if (cs[i] == L'\\' && cs[i + 1] == L'\\') {
|
} else if (cs[i] == L'\\' && cs[i + 1] == L'\\') {
|
||||||
|
@ -892,9 +890,7 @@ void parse_util_expand_variable_error(const wcstring &token, size_t global_token
|
||||||
default: {
|
default: {
|
||||||
wchar_t token_stop_char = char_after_dollar;
|
wchar_t token_stop_char = char_after_dollar;
|
||||||
// Unescape (see issue #50).
|
// Unescape (see issue #50).
|
||||||
if (token_stop_char == ANY_CHAR)
|
if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
|
||||||
token_stop_char = L'?';
|
|
||||||
else if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE)
|
|
||||||
token_stop_char = L'*';
|
token_stop_char = L'*';
|
||||||
|
|
||||||
// Determine which error message to use. The format string may not consume all the
|
// Determine which error message to use. The format string may not consume all the
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
/// Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos.
|
/// Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos.
|
||||||
static size_t wildcard_find(const wchar_t *wc) {
|
static size_t wildcard_find(const wchar_t *wc) {
|
||||||
for (size_t i = 0; wc[i] != L'\0'; i++) {
|
for (size_t i = 0; wc[i] != L'\0'; i++) {
|
||||||
if (wc[i] == ANY_CHAR || wc[i] == ANY_STRING || wc[i] == ANY_STRING_RECURSIVE) {
|
if (wc[i] == ANY_STRING || wc[i] == ANY_STRING_RECURSIVE) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,13 +64,12 @@ static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal) {
|
||||||
const wchar_t *end = str + len;
|
const wchar_t *end = str + len;
|
||||||
if (internal) {
|
if (internal) {
|
||||||
for (; str < end; str++) {
|
for (; str < end; str++) {
|
||||||
if ((*str == ANY_CHAR) || (*str == ANY_STRING) || (*str == ANY_STRING_RECURSIVE))
|
if (*str == ANY_STRING || *str == ANY_STRING_RECURSIVE) return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wchar_t prev = 0;
|
wchar_t prev = 0;
|
||||||
for (; str < end; str++) {
|
for (; str < end; str++) {
|
||||||
if (((*str == L'*') || (*str == L'?')) && (prev != L'\\')) return true;
|
if (*str == L'*' && prev != L'\\') return true;
|
||||||
prev = *str;
|
prev = *str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,13 +127,6 @@ static enum fuzzy_match_type_t wildcard_match_internal(const wchar_t *str, const
|
||||||
restart_is_out_of_str = (*str_x == 0);
|
restart_is_out_of_str = (*str_x == 0);
|
||||||
wc_x++;
|
wc_x++;
|
||||||
continue;
|
continue;
|
||||||
} else if (*wc_x == ANY_CHAR && *str_x != 0) {
|
|
||||||
if (is_first && *str_x == L'.') {
|
|
||||||
return fuzzy_match_none;
|
|
||||||
}
|
|
||||||
wc_x++;
|
|
||||||
str_x++;
|
|
||||||
continue;
|
|
||||||
} else if (*str_x != 0 && *str_x == *wc_x) { // ordinary character
|
} else if (*str_x != 0 && *str_x == *wc_x) { // ordinary character
|
||||||
wc_x++;
|
wc_x++;
|
||||||
str_x++;
|
str_x++;
|
||||||
|
@ -212,7 +204,7 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING.
|
// Locate the next wildcard character position, e.g. ANY_STRING.
|
||||||
const size_t next_wc_char_pos = wildcard_find(wc);
|
const size_t next_wc_char_pos = wildcard_find(wc);
|
||||||
|
|
||||||
// Maybe we have no more wildcards at all. This includes the empty string.
|
// Maybe we have no more wildcards at all. This includes the empty string.
|
||||||
|
@ -265,12 +257,6 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc,
|
||||||
// Our first character is a wildcard.
|
// Our first character is a wildcard.
|
||||||
assert(next_wc_char_pos == 0);
|
assert(next_wc_char_pos == 0);
|
||||||
switch (wc[0]) {
|
switch (wc[0]) {
|
||||||
case ANY_CHAR: {
|
|
||||||
if (str[0] == L'\0') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return wildcard_complete_internal(str + 1, wc + 1, params, flags, out);
|
|
||||||
}
|
|
||||||
case ANY_STRING: {
|
case ANY_STRING: {
|
||||||
// Hackish. If this is the last character of the wildcard, then just complete with
|
// Hackish. If this is the last character of the wildcard, then just complete with
|
||||||
// the empty string. This fixes cases like "f*<tab>" -> "f*o".
|
// the empty string. This fixes cases like "f*<tab>" -> "f*o".
|
||||||
|
@ -789,7 +775,7 @@ void wildcard_expander_t::expand_last_segment(const wcstring &base_dir, DIR *bas
|
||||||
///
|
///
|
||||||
/// Args:
|
/// Args:
|
||||||
/// base_dir: the "working directory" against which the wildcard is to be resolved
|
/// base_dir: the "working directory" against which the wildcard is to be resolved
|
||||||
/// wc: the wildcard string itself, e.g. foo*bar/baz (where * is acutally ANY_CHAR)
|
/// wc: the wildcard string itself, e.g. foo*bar/baz (where * is acutally ANY_STRING)
|
||||||
/// prefix: the string that should be prepended for completions that replace their token.
|
/// prefix: the string that should be prepended for completions that replace their token.
|
||||||
// This is usually the same thing as the original wildcard, but for fuzzy matching, we
|
// This is usually the same thing as the original wildcard, but for fuzzy matching, we
|
||||||
// expand intermediate segments. effective_prefix is always either empty, or ends with a slash
|
// expand intermediate segments. effective_prefix is always either empty, or ends with a slash
|
||||||
|
@ -810,7 +796,7 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
|
||||||
const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len;
|
const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len;
|
||||||
const wcstring wc_segment = wcstring(wc, wc_segment_len);
|
const wcstring wc_segment = wcstring(wc, wc_segment_len);
|
||||||
const bool segment_has_wildcards =
|
const bool segment_has_wildcards =
|
||||||
wildcard_has(wc_segment, true /* internal, i.e. look for ANY_CHAR instead of ? */);
|
wildcard_has(wc_segment, true /* internal, i.e. look for ANY_STRING instead of * */);
|
||||||
const wchar_t *const wc_remainder = next_slash ? next_slash + 1 : NULL;
|
const wchar_t *const wc_remainder = next_slash ? next_slash + 1 : NULL;
|
||||||
|
|
||||||
if (wc_segment.empty()) {
|
if (wc_segment.empty()) {
|
||||||
|
|
|
@ -11,10 +11,8 @@
|
||||||
|
|
||||||
// Enumeration of all wildcard types.
|
// Enumeration of all wildcard types.
|
||||||
enum {
|
enum {
|
||||||
/// Character representing any character except '/' (slash).
|
|
||||||
ANY_CHAR = WILDCARD_RESERVED_BASE,
|
|
||||||
/// Character representing any character string not containing '/' (slash).
|
/// Character representing any character string not containing '/' (slash).
|
||||||
ANY_STRING,
|
ANY_STRING = WILDCARD_RESERVED_BASE,
|
||||||
/// Character representing any character string.
|
/// Character representing any character string.
|
||||||
ANY_STRING_RECURSIVE,
|
ANY_STRING_RECURSIVE,
|
||||||
/// This is a special psuedo-char that is not used other than to mark the
|
/// This is a special psuedo-char that is not used other than to mark the
|
||||||
|
|
|
@ -117,16 +117,16 @@
|
||||||
# string unescape --style=var (string escape --style=var -- -)
|
# string unescape --style=var (string escape --style=var -- -)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match "?" a
|
# string match "*" a
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match "a*b" axxb
|
# string match "a*b" axxb
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -i "a??B" Axxb
|
# string match -i "a**B" Axxb
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# echo "ok?" | string match "*\?"
|
# echo "ok?" | string match "*?"
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -r "cat|dog|fish" "nice dog"
|
# string match -r "cat|dog|fish" "nice dog"
|
||||||
|
@ -190,7 +190,7 @@ string invalidarg; and echo "unexpected exit 0"
|
||||||
# string match -r -v "[dcantg].*" dog can cat diz
|
# string match -r -v "[dcantg].*" dog can cat diz
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -v "???" dog can cat diz
|
# string match -v "*" dog can cat diz
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -rvn a bbb
|
# string match -rvn a bbb
|
||||||
|
|
|
@ -124,17 +124,17 @@ string unescape --style=var -- (string escape --style=var -- -)
|
||||||
|
|
||||||
# The following tests verify that we can correctly match strings.
|
# The following tests verify that we can correctly match strings.
|
||||||
|
|
||||||
logmsg 'string match "?" a'
|
logmsg 'string match "*" a'
|
||||||
string match "?" a
|
string match "*" a
|
||||||
|
|
||||||
logmsg 'string match "a*b" axxb'
|
logmsg 'string match "a*b" axxb'
|
||||||
string match "a*b" axxb
|
string match "a*b" axxb
|
||||||
|
|
||||||
logmsg 'string match -i "a??B" Axxb'
|
logmsg 'string match -i "a**B" Axxb'
|
||||||
string match -i "a??B" Axxb
|
string match -i "a**B" Axxb
|
||||||
|
|
||||||
logmsg 'echo "ok?" | string match "*\?"'
|
logmsg 'echo "ok?" | string match "*?"'
|
||||||
echo "ok?" | string match "*\?"
|
echo "ok?" | string match "*?"
|
||||||
|
|
||||||
logmsg 'string match -r "cat|dog|fish" "nice dog"'
|
logmsg 'string match -r "cat|dog|fish" "nice dog"'
|
||||||
string match -r "cat|dog|fish" "nice dog"
|
string match -r "cat|dog|fish" "nice dog"
|
||||||
|
@ -199,8 +199,8 @@ string length; or echo "missing argument returns 1"
|
||||||
logmsg 'string match -r -v "[dcantg].*" dog can cat diz'
|
logmsg 'string match -r -v "[dcantg].*" dog can cat diz'
|
||||||
string match -r -v "[dcantg].*" dog can cat diz; or echo "no regexp invert match"
|
string match -r -v "[dcantg].*" dog can cat diz; or echo "no regexp invert match"
|
||||||
|
|
||||||
logmsg 'string match -v "???" dog can cat diz'
|
logmsg 'string match -v "*" dog can cat diz'
|
||||||
string match -v "???" dog can cat diz; or echo "no glob invert match"
|
string match -v "*" dog can cat diz; or echo "no glob invert match"
|
||||||
|
|
||||||
logmsg 'string match -rvn a bbb'
|
logmsg 'string match -rvn a bbb'
|
||||||
string match -rvn a bbb; or echo "exit 1"
|
string match -rvn a bbb; or echo "exit 1"
|
||||||
|
|
|
@ -170,7 +170,7 @@ _a_b_c_
|
||||||
-
|
-
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match "?" a
|
# string match "*" a
|
||||||
a
|
a
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -178,11 +178,11 @@ a
|
||||||
axxb
|
axxb
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -i "a??B" Axxb
|
# string match -i "a**B" Axxb
|
||||||
Axxb
|
Axxb
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# echo "ok?" | string match "*\?"
|
# echo "ok?" | string match "*?"
|
||||||
ok?
|
ok?
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -272,7 +272,7 @@ missing argument returns 1
|
||||||
no regexp invert match
|
no regexp invert match
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string match -v "???" dog can cat diz
|
# string match -v "*" dog can cat diz
|
||||||
no glob invert match
|
no glob invert match
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -65,7 +65,7 @@ for i in Test for continue break and switch builtins problems;
|
||||||
switch $i
|
switch $i
|
||||||
case Test
|
case Test
|
||||||
printf "%s " $i
|
printf "%s " $i
|
||||||
case "f??"
|
case "for"
|
||||||
printf "%s " 3b
|
printf "%s " 3b
|
||||||
case "c*"
|
case "c*"
|
||||||
echo pass
|
echo pass
|
||||||
|
|
|
@ -20,7 +20,7 @@ end
|
||||||
switch $smurf
|
switch $smurf
|
||||||
case cyan magenta yellow
|
case cyan magenta yellow
|
||||||
echo Test 3 fail
|
echo Test 3 fail
|
||||||
case "?????"
|
case "*"
|
||||||
echo Test 3 pass
|
echo Test 3 pass
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue