diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index f4cd8ee20..a5d754480 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -118,6 +118,13 @@ impl Highlighter for NuHighlighter { next_token, )) } + FlatShape::StringInterpolation => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } FlatShape::Filepath => output.push(( // nushell Path get_shape_color(shape.1.to_string(), &self.config), diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 4cc12ab3b..f6fabe71a 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -18,6 +18,7 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style { "flatshape_operator" => Style::new().fg(Color::Yellow), "flatshape_signature" => Style::new().fg(Color::Green).bold(), "flatshape_string" => Style::new().fg(Color::Green), + "flatshape_string_interpolation" => Style::new().fg(Color::Cyan).bold(), "flatshape_filepath" => Style::new().fg(Color::Cyan), "flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(), "flatshape_variable" => Style::new().fg(Color::Purple), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e8eb9517d..e724a44ff 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -3,7 +3,8 @@ use std::cmp::Ordering; use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ - IntoPipelineData, PipelineData, Range, ShellError, Span, Spanned, Type, Unit, Value, VarId, + IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, ShellError, Span, + Spanned, Type, Unit, Value, VarId, }; use crate::get_full_help; @@ -341,6 +342,23 @@ pub fn eval_expression( }) } Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr), + Expr::StringInterpolation(exprs) => { + let mut parts = vec![]; + for expr in exprs { + parts.push(eval_expression(engine_state, stack, expr)?); + } + + let config = stack.get_config().unwrap_or_default(); + + parts + .into_iter() + .into_pipeline_data(None) + .collect_string("", &config) + .map(|x| Value::String { + val: x, + span: expr.span, + }) + } Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d4efe69a2..1fc2f2e3a 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -19,6 +19,7 @@ pub enum FlatShape { Operator, Signature, String, + StringInterpolation, Filepath, GlobPattern, Variable, @@ -42,6 +43,7 @@ impl Display for FlatShape { FlatShape::Operator => write!(f, "flatshape_operator"), FlatShape::Signature => write!(f, "flatshape_signature"), FlatShape::String => write!(f, "flatshape_string"), + FlatShape::StringInterpolation => write!(f, "flatshape_string_interpolation"), FlatShape::Filepath => write!(f, "flatshape_filepath"), FlatShape::GlobPattern => write!(f, "flatshape_globpattern"), FlatShape::Variable => write!(f, "flatshape_variable"), @@ -215,6 +217,26 @@ pub fn flatten_expression( } output } + Expr::StringInterpolation(exprs) => { + let mut output = vec![( + Span { + start: expr.span.start, + end: expr.span.start + 2, + }, + FlatShape::StringInterpolation, + )]; + for expr in exprs { + output.extend(flatten_expression(working_set, expr)); + } + output.push(( + Span { + start: expr.span.end - 1, + end: expr.span.end, + }, + FlatShape::StringInterpolation, + )); + output + } Expr::Record(list) => { let mut output = vec![]; for l in list { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 2b7c69de7..bb7c68e8a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1014,7 +1014,7 @@ pub(crate) fn parse_dollar_expr( ) -> (Expression, Option) { let contents = working_set.get_span_contents(span); - if contents.starts_with(b"$\"") { + if contents.starts_with(b"$\"") || contents.starts_with(b"$'") { parse_string_interpolation(working_set, span) } else if let (expr, None) = parse_range(working_set, span) { (expr, None) @@ -1036,16 +1036,22 @@ pub fn parse_string_interpolation( let contents = working_set.get_span_contents(span); - let start = if contents.starts_with(b"$\"") { - span.start + 2 + let (start, end) = if contents.starts_with(b"$\"") { + let end = if contents.ends_with(b"\"") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + (span.start + 2, end) + } else if contents.starts_with(b"$'") { + let end = if contents.ends_with(b"'") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + (span.start + 2, end) } else { - span.start - }; - - let end = if contents.ends_with(b"\"") && contents.len() > 2 { - span.end - 1 - } else { - span.end + (span.start, span.end) }; let inner_span = Span { start, end }; @@ -1134,30 +1140,15 @@ pub fn parse_string_interpolation( } } - if let Some(decl_id) = working_set.find_decl(b"build-string") { - ( - Expression { - expr: Expr::Call(Box::new(Call { - head: Span { - start: span.start, - end: span.start + 2, - }, - named: vec![], - positional: output, - decl_id, - })), - span, - ty: Type::String, - custom_completion: None, - }, - error, - ) - } else { - ( - Expression::garbage(span), - Some(ParseError::UnknownCommand(span)), - ) - } + ( + Expression { + expr: Expr::StringInterpolation(output), + span, + ty: Type::String, + custom_completion: None, + }, + error, + ) } pub fn parse_variable_expr( @@ -3529,6 +3520,12 @@ pub fn find_captures_in_expr( } Expr::Signature(_) => {} Expr::String(_) => {} + Expr::StringInterpolation(exprs) => { + for expr in exprs { + let result = find_captures_in_expr(working_set, expr, seen); + output.extend(&result); + } + } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); let result = find_captures_in_block(working_set, block, seen); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 446df034c..c728a9b6a 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -33,6 +33,7 @@ pub enum Expr { FullCellPath(Box), ImportPattern(ImportPattern), Signature(Box), + StringInterpolation(Vec), Nothing, Garbage, } diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index ca3393a59..edeafa55f 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -160,6 +160,14 @@ impl Expression { } false } + Expr::StringInterpolation(items) => { + for i in items { + if i.has_in_variable(working_set) { + return true; + } + } + false + } Expr::Operator(_) => false, Expr::Range(left, middle, right, ..) => { if let Some(left) = &left { @@ -321,6 +329,11 @@ impl Expression { } Expr::Signature(_) => {} Expr::String(_) => {} + Expr::StringInterpolation(items) => { + for i in items { + i.replace_in_variable(working_set, new_var_id) + } + } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); diff --git a/src/tests/test_strings.rs b/src/tests/test_strings.rs index cf31427d6..905251abb 100644 --- a/src/tests/test_strings.rs +++ b/src/tests/test_strings.rs @@ -79,3 +79,8 @@ fn string_in_valuestream() -> TestResult { "true", ) } + +#[test] +fn single_tick_interpolation() -> TestResult { + run_test(r#"$'(3 + 4)'"#, "7") +}