diff --git a/CHANGELOG.md b/CHANGELOG.md index 622494fb8..4884a7383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,7 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `jj diff` no longer shows the contents of binary files. * `jj git` now has an `init` command that initializes a git backed repo. - + +* New template function `surround(prefix, suffix, content)` is added. ### Fixed bugs diff --git a/cli/src/template_builder.rs b/cli/src/template_builder.rs index d14be4244..653072d1b 100644 --- a/cli/src/template_builder.rs +++ b/cli/src/template_builder.rs @@ -753,6 +753,23 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( .try_collect()?; language.wrap_template(Box::new(SeparateTemplate::new(separator, contents))) } + "surround" => { + let [prefix_node, suffix_node, content_node] = + template_parser::expect_exact_arguments(function)?; + let prefix = expect_template_expression(language, build_ctx, prefix_node)?; + let suffix = expect_template_expression(language, build_ctx, suffix_node)?; + let content = expect_template_expression(language, build_ctx, content_node)?; + let template = ReformatTemplate::new(content, move |context, formatter, recorded| { + if recorded.data().is_empty() { + return Ok(()); + } + prefix.format(context, formatter)?; + recorded.replay(formatter)?; + suffix.format(context, formatter)?; + Ok(()) + }); + language.wrap_template(Box::new(template)) + } _ => return Err(TemplateParseError::no_such_function(function)), }; Ok(Expression::unlabeled(property)) @@ -1745,4 +1762,48 @@ mod tests { env.render_ok(r#"separate(hidden, "X", "Y", "Z")"#), @"XfalseYfalseZ"); } + + #[test] + fn test_surround_function() { + let mut env = TestTemplateEnv::default(); + env.add_keyword("lt", |language| { + language.wrap_string(Literal("<".to_owned())) + }); + env.add_keyword("gt", |language| { + language.wrap_string(Literal(">".to_owned())) + }); + env.add_keyword("content", |language| { + language.wrap_string(Literal("content".to_owned())) + }); + env.add_keyword("empty_content", |language| { + language.wrap_string(Literal("".to_owned())) + }); + env.add_color("error", crossterm::style::Color::DarkRed); + env.add_color("paren", crossterm::style::Color::Cyan); + + insta::assert_snapshot!(env.render_ok(r#"surround("{", "}", "")"#), @""); + insta::assert_snapshot!(env.render_ok(r#"surround("{", "}", "a")"#), @"{a}"); + + // Labeled + insta::assert_snapshot!( + env.render_ok( + r#"surround(label("paren", "("), label("paren", ")"), label("error", "a"))"#), + @"(a)"); + + // Keyword + insta::assert_snapshot!( + env.render_ok(r#"surround(lt, gt, content)"#), + @""); + insta::assert_snapshot!( + env.render_ok(r#"surround(lt, gt, empty_content)"#), + @""); + + // Conditional template as content + insta::assert_snapshot!( + env.render_ok(r#"surround(lt, gt, if(empty_content, "", "empty"))"#), + @""); + insta::assert_snapshot!( + env.render_ok(r#"surround(lt, gt, if(empty_content, "not empty", ""))"#), + @""); + } } diff --git a/docs/templates.md b/docs/templates.md index b47c55b7f..a03e27b26 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -75,6 +75,8 @@ The following functions are defined. Same as `content_1 ++ ... ++ content_n`. * `separate(separator: Template, content: Template...) -> Template`: Insert separator between **non-empty** contents. +* `surround(prefix: Template, suffix: Template, content: Template) -> Template`: + Surround **non-empty** content with texts such as parentheses. ## Types