diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 9505c58c8d..daa5abb25b 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -183,6 +183,20 @@ fn external_arg_with_non_option_like_embedded_quotes() { ) } +#[test] +fn external_arg_with_string_interpolation() { + Playground::setup("external arg with string interpolation", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^nu --testbin cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" + "# + )); + + assert_eq!(actual.out, "foo=4 foo=4 foo=4"); + }) +} + #[test] fn external_arg_with_variable_name() { Playground::setup("external failed command with semicolon", |dirs, _| { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 463deb9b18..ba718cc771 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -261,7 +261,9 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre }; let mut spans = vec![]; let mut state = State::Bare { from: 0 }; - for (index, &ch) in contents.iter().enumerate() { + let mut index = 0; + while index < contents.len() { + let ch = contents[index]; match &mut state { State::Bare { from } => match ch { b'"' | b'\'' => { @@ -277,6 +279,23 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre depth: 1, }; } + b'$' => { + if let Some("e_char @ (b'"' | b'\'')) = contents.get(index + 1) { + // Start a dollar quote (interpolated string) + if index != *from { + spans.push(make_span(*from, index)); + } + state = State::Quote { + from: index, + quote_char, + escaped: false, + depth: 1, + }; + // Skip over two chars (the dollar sign and the quote) + index += 2; + continue; + } + } // Continue to consume _ => (), }, @@ -316,6 +335,7 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre } }, } + index += 1; } // Add the final span