mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-24 12:48:55 +00:00
templater: add indent(prefix, content) function
The argument order is different from Mercurial's indent() function. I think indent(prefix, content) is more readable for lengthy content. However, indent(content, prefix, ...) might be better if we want to add an optional firstline_prefix argument.
This commit is contained in:
parent
558aa15e6e
commit
8f8a9c91bc
4 changed files with 98 additions and 1 deletions
|
@ -55,6 +55,8 @@ The following operators are supported.
|
|||
|
||||
The following functions are defined.
|
||||
|
||||
* `indent(prefix: Template, content: Template) -> Template`: Indent
|
||||
non-empty lines by the given `prefix`.
|
||||
* `label(label: Template, content: Template) -> Template`: Apply label to
|
||||
the content. The `label` is evaluated as a space-separated string.
|
||||
* `if(condition: Boolean, then: Template[, else: Template]) -> Template`:
|
||||
|
|
|
@ -25,7 +25,7 @@ use pest_derive::Parser;
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::templater::{
|
||||
ConditionalTemplate, IntoTemplate, LabelTemplate, ListTemplate, Literal,
|
||||
ConditionalTemplate, IndentTemplate, IntoTemplate, LabelTemplate, ListTemplate, Literal,
|
||||
PlainTextFormattedProperty, SeparateTemplate, Template, TemplateFunction, TemplateProperty,
|
||||
TemplatePropertyFn, TimestampRange,
|
||||
};
|
||||
|
@ -1045,6 +1045,13 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>(
|
|||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<Expression<'a, L::Context, L::Property>> {
|
||||
let expression = match function.name {
|
||||
"indent" => {
|
||||
let [prefix_node, content_node] = expect_exact_arguments(function)?;
|
||||
let prefix = build_expression(language, prefix_node)?.into_template();
|
||||
let content = build_expression(language, content_node)?.into_template();
|
||||
let template = Box::new(IndentTemplate::new(prefix, content));
|
||||
Expression::Template(template)
|
||||
}
|
||||
"label" => {
|
||||
let [label_node, content_node] = expect_exact_arguments(function)?;
|
||||
let label_property = build_expression(language, label_node)?.into_plain_text();
|
||||
|
|
|
@ -97,6 +97,46 @@ impl Template<()> for i64 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Indents each line by the given prefix.
|
||||
pub struct IndentTemplate<S, T> {
|
||||
prefix: S,
|
||||
content: T,
|
||||
}
|
||||
|
||||
impl<S, T> IndentTemplate<S, T> {
|
||||
pub fn new<C>(prefix: S, content: T) -> Self
|
||||
where
|
||||
S: Template<C>,
|
||||
T: Template<C>,
|
||||
{
|
||||
IndentTemplate { prefix, content }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S, T> Template<C> for IndentTemplate<S, T>
|
||||
where
|
||||
S: Template<C>,
|
||||
T: Template<C>,
|
||||
{
|
||||
fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> {
|
||||
let mut recorder = FormatRecorder::new();
|
||||
self.content.format(context, &mut recorder)?;
|
||||
let mut new_line = true;
|
||||
recorder.replay_with(formatter, |formatter, data| {
|
||||
for line in data.split_inclusive(|&c| c == b'\n') {
|
||||
if new_line && line != b"\n" {
|
||||
// Prefix inherits the current labels. This is implementation detail
|
||||
// and may be fixed later.
|
||||
self.prefix.format(context, formatter)?;
|
||||
}
|
||||
formatter.write_all(line)?;
|
||||
new_line = line.ends_with(b"\n");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LabelTemplate<T, L> {
|
||||
content: T,
|
||||
labels: L,
|
||||
|
|
|
@ -316,6 +316,54 @@ fn test_templater_signature() {
|
|||
insta::assert_snapshot!(render(r#"author.username()"#), @"x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_templater_indent_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);
|
||||
|
||||
// Empty line shouldn't be indented. Not using insta here because we test
|
||||
// whitespace existence.
|
||||
assert_eq!(render(r#"indent("__", "")"#), "");
|
||||
assert_eq!(render(r#"indent("__", "\n")"#), "\n");
|
||||
assert_eq!(render(r#"indent("__", "a\n\nb")"#), "__a\n\n__b");
|
||||
|
||||
// "\n" at end of labeled text
|
||||
insta::assert_snapshot!(
|
||||
render(r#"indent("__", label("error", "a\n") ++ label("warning", "b\n"))"#),
|
||||
@r###"
|
||||
[38;5;1m__a[39m
|
||||
[38;5;3m__b[39m
|
||||
"###);
|
||||
|
||||
// "\n" in labeled text
|
||||
insta::assert_snapshot!(
|
||||
render(r#"indent("__", label("error", "a") ++ label("warning", "b\nc"))"#),
|
||||
@r###"
|
||||
[38;5;1m__a[39m[38;5;3mb[39m
|
||||
[38;5;3m__c[39m
|
||||
"###);
|
||||
|
||||
// Labeled prefix + unlabeled content
|
||||
insta::assert_snapshot!(
|
||||
render(r#"indent(label("error", "XX"), "a\nb\n")"#),
|
||||
@r###"
|
||||
[38;5;1mXX[39ma
|
||||
[38;5;1mXX[39mb
|
||||
"###);
|
||||
|
||||
// Nested indent, silly but works
|
||||
insta::assert_snapshot!(
|
||||
render(r#"indent(label("hint", "A"),
|
||||
label("warning", indent(label("hint", "B"),
|
||||
label("error", "x\n") ++ "y")))"#),
|
||||
@r###"
|
||||
[38;5;6mAB[38;5;1mx[39m
|
||||
[38;5;6mAB[38;5;3my[39m
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_templater_label_function() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
Loading…
Reference in a new issue