mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-31 03:51:14 -03:00
Always treat brace at command start as compound statement
For backwards compatibility, fish does not treat "{echo,hello}" as a compound
statement but as brace expansion (effectively "echo hello"). We interpret
"{X...}" as compound statement only if X is whitespace or ';' (which is an
interesting solution).
A brace expansion at the very start of a command
is usually pointless (space separation is shorter).
The exception are cases where the command name and the first few arguments
share a suffix.
$ {,1,2,3,4}echo
1echo 2echo 3echo 4echo
Not sure if anyone uses anything like that. Perhaps we want to trade
compatibility for simplicity. I don't have a strong opinion on this.
Always parse the opening brace as first character of a command token as
compound statement.
Brace expansion can still be used with a trick like: «''{echo,foo}»
Closes #11477
This commit is contained in:
@@ -21,7 +21,7 @@ Notable improvements and fixes
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- Tokens like ``{ echo, echo }`` in command position are no longer interpreted as brace expansion but as compound command.
|
||||
- Tokens like ``{echo,echo}`` or ``{ echo, echo }`` in command position are no longer interpreted as brace expansion but as compound command.
|
||||
- Terminfo-style key names (``bind -k``) are no longer supported. They had been superseded by the native notation since 4.0,
|
||||
and currently they would map back to information from terminfo, which does not match what terminals would send with the kitty keyboard protocol (:issue:`11342`).
|
||||
- fish no longer reads the terminfo database, so its behavior is no longer affected by the :envvar:`TERM` environment variable (:issue:`11344`).
|
||||
|
||||
@@ -917,6 +917,12 @@ If there is nothing between a brace and a comma or two commas, it's interpreted
|
||||
|
||||
To use a "," as an element, :ref:`quote <quotes>` or :ref:`escape <escapes>` it.
|
||||
|
||||
The very first character of a command token is never interpreted as expanding brace, because it's the beginning of a :ref:`compound statement <cmd-begin>`::
|
||||
|
||||
> {echo hello, && echo world}
|
||||
hello,
|
||||
world
|
||||
|
||||
.. _cartesian-product:
|
||||
|
||||
Combining lists
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
ast::unescape_keyword,
|
||||
common::charptr2wcstring,
|
||||
reader::{get_quote, is_backslashed},
|
||||
tokenizer::is_brace_statement,
|
||||
util::wcsfilecmp,
|
||||
wutil::sprintf,
|
||||
};
|
||||
@@ -667,20 +666,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
|
||||
|
||||
// Get all the arguments.
|
||||
let mut tokens = Vec::new();
|
||||
{
|
||||
let proc_range =
|
||||
parse_util_process_extent(&cmdline, position_in_statement, Some(&mut tokens));
|
||||
let start = proc_range.start;
|
||||
if start != 0
|
||||
&& cmdline.as_char_slice()[start - 1] == '{'
|
||||
&& (start == cmdline.len()
|
||||
|| !is_brace_statement(cmdline.as_char_slice().get(start).copied()))
|
||||
{
|
||||
// We don't want to suggest commands here, since this command line parses as
|
||||
// brace expansion.
|
||||
return;
|
||||
}
|
||||
}
|
||||
parse_util_process_extent(&cmdline, position_in_statement, Some(&mut tokens));
|
||||
let actual_token_count = tokens.len();
|
||||
|
||||
// Hack: fix autosuggestion by removing prefixing "and"s #6249.
|
||||
|
||||
@@ -57,9 +57,8 @@ fn test_tokenizer() {
|
||||
let s = L!("{echo, foo}");
|
||||
let mut t = Tokenizer::new(s, TokFlags(0));
|
||||
let token = t.next().unwrap();
|
||||
assert_eq!(token.type_, TokenType::string);
|
||||
assert_eq!(token.length, 11);
|
||||
assert!(t.next().is_none());
|
||||
assert_eq!(token.type_, TokenType::left_brace);
|
||||
assert_eq!(token.length, 1);
|
||||
}
|
||||
{
|
||||
let s = L!("{ echo; foo}");
|
||||
|
||||
@@ -290,10 +290,6 @@ pub struct Tokenizer<'c> {
|
||||
on_quote_toggle: Option<&'c mut dyn FnMut(usize)>,
|
||||
}
|
||||
|
||||
pub(crate) fn is_brace_statement(next_char: Option<char>) -> bool {
|
||||
next_char.map_or(true, |next| next.is_ascii_whitespace() || next == ';')
|
||||
}
|
||||
|
||||
impl<'c> Tokenizer<'c> {
|
||||
/// Constructor for a tokenizer. b is the string that is to be tokenized. It is not copied, and
|
||||
/// should not be freed by the caller until after the tokenizer is destroyed.
|
||||
@@ -426,9 +422,7 @@ fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(result)
|
||||
}
|
||||
'{' if self.brace_statement_parser.as_ref()
|
||||
.is_some_and(|parser| parser.at_command_position)
|
||||
&& is_brace_statement(self.start.as_char_slice().get(self.token_cursor + 1).copied())
|
||||
=>
|
||||
.is_some_and(|parser| parser.at_command_position) =>
|
||||
{
|
||||
self.brace_statement_parser.as_mut().unwrap().unclosed_brace_statements += 1;
|
||||
let mut result = Tok::new(TokenType::left_brace);
|
||||
|
||||
@@ -67,26 +67,33 @@ e{cho,cho,cho}
|
||||
{ echo no semi }
|
||||
# CHECK: no semi
|
||||
|
||||
# Ambiguous cases
|
||||
{echo no space}
|
||||
# CHECK: no space
|
||||
|
||||
# Ambiguous case
|
||||
{ echo ,comma;}
|
||||
# CHECK: ,comma
|
||||
|
||||
PATH= {echo no space}
|
||||
# CHECKERR: fish: Unknown command: '{echo no space}'
|
||||
# CHECKERR: {{.*}}/braces.fish (line {{\d+}}):
|
||||
# CHECKERR: PATH= {echo no space}
|
||||
# CHECKERR: ^~~~~~~~~~~~~~^
|
||||
|
||||
PATH= {echo comma, no space;}
|
||||
# CHECKERR: fish: Unknown command: 'echo comma'
|
||||
# CHECKERR: {{.*}}/braces.fish (line {{\d+}}):
|
||||
# CHECKERR: PATH= {echo comma, no space;}
|
||||
# CHECKERR: ^~~~~~~~~~~~~~~~~~~~~~^
|
||||
# Ambiguous case with no trailing space
|
||||
{echo comma, no space;}
|
||||
# CHECK: comma, no space
|
||||
|
||||
# Ambiguous case with no space
|
||||
{echo,hello}
|
||||
# CHECK: hello
|
||||
PATH= {echo,hello}
|
||||
# CHECKERR: fish: Unknown command: echo,hello
|
||||
# CHECKERR: {{.*}}/braces.fish (line {{\d+}}):
|
||||
# CHECKERR: PATH= {echo,hello}
|
||||
# CHECKERR: ^~~~~~~~~^
|
||||
|
||||
function foo,
|
||||
echo foo,
|
||||
end
|
||||
function bar
|
||||
echo bar
|
||||
end
|
||||
{foo,;bar}
|
||||
# CHECK: foo,
|
||||
# CHECK: bar
|
||||
|
||||
# Trailing tokens
|
||||
set -l fish (status fish-path)
|
||||
@@ -157,7 +164,7 @@ end
|
||||
}
|
||||
# CHECK: while
|
||||
|
||||
{ { echo inner}
|
||||
{{echo inner}
|
||||
echo outer}
|
||||
# CHECK: inner
|
||||
# CHECK: outer
|
||||
@@ -172,9 +179,8 @@ complete foo -a '123 456'
|
||||
complete -C 'foo {' | sed 1q
|
||||
# CHECK: {{\{.*}}
|
||||
|
||||
complete -C '{'
|
||||
echo nothing
|
||||
# CHECK: nothing
|
||||
complete -C '{' | grep ^if\t
|
||||
# CHECK: if{{\t}}Evaluate block if condition is true
|
||||
complete -C '{ ' | grep ^if\t
|
||||
# CHECK: if{{\t}}Evaluate block if condition is true
|
||||
|
||||
|
||||
@@ -459,6 +459,9 @@ echo \\
|
||||
echo '{ { } }'
|
||||
# CHECK: { { } }
|
||||
|
||||
echo '{{}}'
|
||||
# CHECK: { { } }
|
||||
|
||||
echo '
|
||||
{
|
||||
|
||||
|
||||
Reference in New Issue
Block a user