templater: add fill(width, content) function

The parameter order follows indent()/label() functions, but this might be
a bad idea because fill() is more likely to have optional parameters. We can
instead add template.fill(width) method as well as .indent(prefix). If we take
this approach, we'll probably need to add string.fill()/indent() methods,
and/or implicit cast at method resolution. The good thing about the method
syntax is that we can add string.refill(), etc. for free, without inventing
generic labeled template functions.

For #1043, I think it's better to add a config like ui.log-word-wrap = true.
We could add term_width/graph_width keywords to the templater, but the
implementation would be more complicated, and is difficult to use for the
basic use case. Unlike Mercurial, our templater doesn't have a context map
to override the graph_width stub.
This commit is contained in:
Yuya Nishihara 2023-03-04 23:07:14 +09:00
parent ec554f6df2
commit c52efd9df3
3 changed files with 51 additions and 0 deletions

View file

@ -56,6 +56,8 @@ The following operators are supported.
The following functions are defined.
* `fill(width: Integer, content: Template) -> Template`: Fill lines at
the given `width`.
* `indent(prefix: Template, content: Template) -> Template`: Indent
non-empty lines by the given `prefix`.
* `label(label: Template, content: Template) -> Template`: Apply label to

View file

@ -1071,6 +1071,16 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>(
function: &FunctionCallNode,
) -> TemplateParseResult<Expression<L::Property>> {
let property = match function.name {
"fill" => {
let [width_node, content_node] = expect_exact_arguments(function)?;
let width = expect_integer_expression(language, width_node)?;
let content = build_expression(language, content_node)?.into_template();
let template = ReformatTemplate::new(content, move |context, formatter, recorded| {
let width = width.extract(context).try_into().unwrap_or(0);
text_util::write_wrapped(formatter, recorded, width)
});
language.wrap_template(template)
}
"indent" => {
let [prefix_node, content_node] = expect_exact_arguments(function)?;
let prefix = build_expression(language, prefix_node)?.into_template();

View file

@ -332,6 +332,45 @@ fn test_templater_signature() {
insta::assert_snapshot!(render(r#"author.username()"#), @"x");
}
#[test]
fn test_templater_fill_function() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render = |template| get_colored_template_output(&test_env, &repo_path, "@-", template);
insta::assert_snapshot!(
render(r#"fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n")"#),
@r###"
The quick fox jumps
over the lazy dog
"###);
// Word-wrap, then indent
insta::assert_snapshot!(
render(r#""START marker to help insta\n" ++
indent(" ", fill(20, "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox jumps
over the lazy dog
"###);
// Word-wrap indented (no special handling for leading spaces)
insta::assert_snapshot!(
render(r#""START marker to help insta\n" ++
fill(20, indent(" ", "The quick fox jumps over the " ++
label("error", "lazy") ++ " dog\n"))"#),
@r###"
START marker to help insta
The quick fox
jumps over the lazy
dog
"###);
}
#[test]
fn test_templater_indent_function() {
let test_env = TestEnvironment::default();