diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 919d8d6be..972b26ff2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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`). diff --git a/doc_src/language.rst b/doc_src/language.rst index 173aef660..1acff6e06 100644 --- a/doc_src/language.rst +++ b/doc_src/language.rst @@ -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 ` or :ref:`escape ` 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 `:: + + > {echo hello, && echo world} + hello, + world + .. _cartesian-product: Combining lists diff --git a/src/complete.rs b/src/complete.rs index 4a679d72a..7c0343b96 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -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. diff --git a/src/tests/tokenizer.rs b/src/tests/tokenizer.rs index b36b4bf1a..6394e1fb7 100644 --- a/src/tests/tokenizer.rs +++ b/src/tests/tokenizer.rs @@ -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}"); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 39bb0866c..771e6e77a 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -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) -> 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 { 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); diff --git a/tests/checks/braces.fish b/tests/checks/braces.fish index 4b555ebc2..af3e055a1 100644 --- a/tests/checks/braces.fish +++ b/tests/checks/braces.fish @@ -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 diff --git a/tests/checks/indent.fish b/tests/checks/indent.fish index d8fbad767..c3afa9986 100644 --- a/tests/checks/indent.fish +++ b/tests/checks/indent.fish @@ -459,6 +459,9 @@ echo \\ echo '{ { } }' # CHECK: { { } } + echo '{{}}' + # CHECK: { { } } + echo ' {