mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-18 10:07:28 +00:00
templates: add tail
argument to truncate_*
When truncation occurs, the tail will be placed once before the start (for `truncate_start`) or after the end (for `truncate_end`) of the truncated content.
This commit is contained in:
parent
dbd0174ee8
commit
fbf8c2f82a
4 changed files with 122 additions and 114 deletions
|
@ -8,6 +8,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
* `truncate_start` and `truncate_end` now accept an optional `tail` named
|
||||
argument, to indicate when truncation has occurred.
|
||||
|
||||
### Release highlights
|
||||
|
||||
### Breaking changes
|
||||
|
|
|
@ -1318,22 +1318,32 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
map.insert(
|
||||
"truncate_start",
|
||||
|language, diagnostics, build_ctx, function| {
|
||||
let [width_node, content_node] = function.expect_exact_arguments()?;
|
||||
let ([width_node, content_node], [tail_node]) =
|
||||
function.expect_named_arguments(&["", "", "tail"])?;
|
||||
let width = expect_usize_expression(language, diagnostics, build_ctx, width_node)?;
|
||||
let content =
|
||||
expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let template = new_truncate_template(content, width, text_util::write_truncated_start);
|
||||
let tail = tail_node
|
||||
.map(|node| expect_plain_text_expression(language, diagnostics, build_ctx, node))
|
||||
.transpose()?;
|
||||
let template =
|
||||
new_truncate_template(content, width, tail, text_util::write_truncated_start);
|
||||
Ok(L::wrap_template(template))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"truncate_end",
|
||||
|language, diagnostics, build_ctx, function| {
|
||||
let [width_node, content_node] = function.expect_exact_arguments()?;
|
||||
let ([width_node, content_node], [tail_node]) =
|
||||
function.expect_named_arguments(&["", "", "tail"])?;
|
||||
let width = expect_usize_expression(language, diagnostics, build_ctx, width_node)?;
|
||||
let content =
|
||||
expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let template = new_truncate_template(content, width, text_util::write_truncated_end);
|
||||
let tail = tail_node
|
||||
.map(|node| expect_plain_text_expression(language, diagnostics, build_ctx, node))
|
||||
.transpose()?;
|
||||
let template =
|
||||
new_truncate_template(content, width, tail, text_util::write_truncated_end);
|
||||
Ok(L::wrap_template(template))
|
||||
},
|
||||
);
|
||||
|
@ -1450,17 +1460,28 @@ where
|
|||
fn new_truncate_template<'a, W>(
|
||||
content: Box<dyn Template + 'a>,
|
||||
width: Box<dyn TemplateProperty<Output = usize> + 'a>,
|
||||
tail: Option<Box<dyn TemplateProperty<Output = String> + 'a>>,
|
||||
write_truncated: W,
|
||||
) -> Box<dyn Template + 'a>
|
||||
where
|
||||
W: Fn(&mut dyn Formatter, &FormatRecorder, usize) -> io::Result<usize> + 'a,
|
||||
W: Fn(&mut dyn Formatter, &FormatRecorder, usize, &str) -> io::Result<usize> + 'a,
|
||||
{
|
||||
let template = ReformatTemplate::new(content, move |formatter, recorded| {
|
||||
let width = match width.extract() {
|
||||
Ok(width) => width,
|
||||
Err(err) => return formatter.handle_error(err),
|
||||
};
|
||||
write_truncated(formatter.as_mut(), recorded, width)?;
|
||||
let tail = match tail.as_ref().map(|t| t.extract()) {
|
||||
Some(Err(err)) => return formatter.handle_error(err),
|
||||
Some(Ok(tail)) => Some(tail),
|
||||
None => None,
|
||||
};
|
||||
write_truncated(
|
||||
formatter.as_mut(),
|
||||
recorded,
|
||||
width,
|
||||
tail.as_deref().unwrap_or_default(),
|
||||
)?;
|
||||
Ok(())
|
||||
});
|
||||
Box::new(template)
|
||||
|
@ -2770,9 +2791,15 @@ mod tests {
|
|||
insta::assert_snapshot!(
|
||||
env.render_ok(r"truncate_start(2, label('red', 'foobar')) ++ 'baz'"),
|
||||
@"[38;5;9mar[39mbaz");
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r"truncate_start(2, label('red', 'foobar'), tail='**') ++ 'baz'"),
|
||||
@"**[38;5;9mar[39mbaz");
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r"truncate_end(2, label('red', 'foobar')) ++ 'baz'"),
|
||||
@"[38;5;9mfo[39mbaz");
|
||||
insta::assert_snapshot!(
|
||||
env.render_ok(r"truncate_end(2, label('red', 'foobar'), tail='**') ++ 'baz'"),
|
||||
@"[38;5;9mfo[39m**baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -231,10 +231,14 @@ pub fn write_truncated_start(
|
|||
formatter: &mut dyn Formatter,
|
||||
recorded_content: &FormatRecorder,
|
||||
max_width: usize,
|
||||
tail: &str,
|
||||
) -> io::Result<usize> {
|
||||
let data = recorded_content.data();
|
||||
let (start, truncated_width) = truncate_start_pos_bytes(data, max_width);
|
||||
let truncated_start = start + count_start_zero_width_chars_bytes(&data[start..]);
|
||||
if truncated_start != 0 {
|
||||
formatter.write_all(tail.as_bytes())?;
|
||||
}
|
||||
recorded_content.replay_with(formatter, |formatter, range| {
|
||||
let start = cmp::max(range.start, truncated_start);
|
||||
if start < range.end {
|
||||
|
@ -253,6 +257,7 @@ pub fn write_truncated_end(
|
|||
formatter: &mut dyn Formatter,
|
||||
recorded_content: &FormatRecorder,
|
||||
max_width: usize,
|
||||
tail: &str,
|
||||
) -> io::Result<usize> {
|
||||
let data = recorded_content.data();
|
||||
let (truncated_end, truncated_width) = truncate_end_pos_bytes(data, max_width);
|
||||
|
@ -263,6 +268,9 @@ pub fn write_truncated_end(
|
|||
}
|
||||
Ok(())
|
||||
})?;
|
||||
if truncated_end != data.len() {
|
||||
formatter.write_all(tail.as_bytes())?;
|
||||
}
|
||||
Ok(truncated_width)
|
||||
}
|
||||
|
||||
|
@ -679,48 +687,34 @@ mod tests {
|
|||
}
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 6).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 5).map(|_| ())),
|
||||
@"[38;5;1moo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 3).map(|_| ())),
|
||||
@"[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 2).map(|_| ())),
|
||||
@"[38;5;6mar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_start(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(6, "" ), @"[38;5;1mfoo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(6, "..."), @"[38;5;1mfoo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(5, "" ), @"[38;5;1moo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(5, "..."), @"...[38;5;1moo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(3, "" ), @"[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(3, "..."), @"...[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(2, "" ), @"[38;5;6mar[39m");
|
||||
insta::assert_snapshot!(render(2, "..."), @"...[38;5;6mar[39m");
|
||||
insta::assert_snapshot!(render(0, "" ), @"");
|
||||
insta::assert_snapshot!(render(0, "..."), @"...");
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 6).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 5).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mba[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 3).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 2).map(|_| ())),
|
||||
@"[38;5;1mfo[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_end(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(6, "" ), @"[38;5;1mfoo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(6, "…"), @"[38;5;1mfoo[39m[38;5;6mbar[39m");
|
||||
insta::assert_snapshot!(render(5, "" ), @"[38;5;1mfoo[39m[38;5;6mba[39m");
|
||||
insta::assert_snapshot!(render(5, "…"), @"[38;5;1mfoo[39m[38;5;6mba[39m…");
|
||||
insta::assert_snapshot!(render(3, "" ), @"[38;5;1mfoo[39m");
|
||||
insta::assert_snapshot!(render(3, "…"), @"[38;5;1mfoo[39m…");
|
||||
insta::assert_snapshot!(render(2, "" ), @"[38;5;1mfo[39m");
|
||||
insta::assert_snapshot!(render(2, "…"), @"[38;5;1mfo[39m…");
|
||||
insta::assert_snapshot!(render(0, "" ), @"");
|
||||
insta::assert_snapshot!(render(0, "…"), @"…");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -729,56 +723,38 @@ mod tests {
|
|||
write!(recorder, "a\u{300}bc\u{300}一二三").unwrap();
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 2).map(|_| ())),
|
||||
@"三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 3).map(|_| ())),
|
||||
@"三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 6).map(|_| ())),
|
||||
@"一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 7).map(|_| ())),
|
||||
@"c̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 9).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 10).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_start(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(1, "" ), @"");
|
||||
insta::assert_snapshot!(render(1, "123"), @"123");
|
||||
insta::assert_snapshot!(render(2, "" ), @"三");
|
||||
insta::assert_snapshot!(render(2, "123"), @"123三");
|
||||
insta::assert_snapshot!(render(3, "" ), @"三");
|
||||
insta::assert_snapshot!(render(3, "123"), @"123三");
|
||||
insta::assert_snapshot!(render(6, "" ), @"一二三");
|
||||
insta::assert_snapshot!(render(6, "123"), @"123一二三");
|
||||
insta::assert_snapshot!(render(7, "" ), @"c̀一二三");
|
||||
insta::assert_snapshot!(render(7, "123"), @"123c̀一二三");
|
||||
insta::assert_snapshot!(render(9, "" ), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(9, "123"), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(10, "" ), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(10, "123"), @"àbc̀一二三");
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 1).map(|_| ())),
|
||||
@"à"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 4).map(|_| ())),
|
||||
@"àbc̀"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 5).map(|_| ())),
|
||||
@"àbc̀一"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 9).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 10).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_end(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(1, "" ), @"à");
|
||||
insta::assert_snapshot!(render(1, "__"), @"à__");
|
||||
insta::assert_snapshot!(render(4, "" ), @"àbc̀");
|
||||
insta::assert_snapshot!(render(4, "__"), @"àbc̀__");
|
||||
insta::assert_snapshot!(render(5, "" ), @"àbc̀一");
|
||||
insta::assert_snapshot!(render(5, "__"), @"àbc̀一__");
|
||||
insta::assert_snapshot!(render(9, "" ), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(9, "__"), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(10, "" ), @"àbc̀一二三");
|
||||
insta::assert_snapshot!(render(10, "__"), @"àbc̀一二三");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -786,24 +762,22 @@ mod tests {
|
|||
let recorder = FormatRecorder::new();
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_start(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(0, "" ), @"");
|
||||
insta::assert_snapshot!(render(0, "[]"), @"");
|
||||
insta::assert_snapshot!(render(1, "" ), @"");
|
||||
insta::assert_snapshot!(render(1, "[]"), @"");
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
let render = |max_width, tail| {
|
||||
format_colored(|fmt| write_truncated_end(fmt, &recorder, max_width, tail).map(|_| ()))
|
||||
};
|
||||
insta::assert_snapshot!(render(0, "" ), @"");
|
||||
insta::assert_snapshot!(render(0, "[]"), @"");
|
||||
insta::assert_snapshot!(render(1, "" ), @"");
|
||||
insta::assert_snapshot!(render(1, "[]"), @"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -55,10 +55,14 @@ The following functions are defined.
|
|||
* `pad_end(width: Integer, content: Template[, fill_char: Template])`: Pad (or
|
||||
left-justify) content by adding trailing fill characters. The `content`
|
||||
shouldn't have newline character.
|
||||
* `truncate_start(width: Integer, content: Template)`: Truncate `content` by
|
||||
removing leading characters. The `content` shouldn't have newline character.
|
||||
* `truncate_end(width: Integer, content: Template)`: Truncate `content` by
|
||||
removing trailing characters. The `content` shouldn't have newline character.
|
||||
* `truncate_start(width: Integer, content: Template[, tail: Template])`:
|
||||
Truncate `content` by removing leading characters. The `content` shouldn't
|
||||
have newline character. If the content is truncated and `tail` was provided,
|
||||
`tail` will be placed before `content`.
|
||||
* `truncate_end(width: Integer, content: Template[, tail: Template])`: Truncate
|
||||
`content` by removing trailing characters. The `content` shouldn't have
|
||||
newline character. If the content is truncated and `tail` was provided, `tail`
|
||||
will be place after `content`.
|
||||
* `label(label: Template, content: Template) -> Template`: Apply label to
|
||||
the content. The `label` is evaluated as a space-separated string.
|
||||
* `raw_escape_sequence(content: Template) -> Template`: Preserves any escape
|
||||
|
|
Loading…
Reference in a new issue