From 227eccefdb220491bd25e9e51a316a85e6e6a0a1 Mon Sep 17 00:00:00 2001 From: Josh Steadmon Date: Thu, 30 Jan 2025 12:05:13 -0800 Subject: [PATCH] templater: add pad_centered template function Add a new pad_center function that centers content within a minimum width. If an odd number of fill characters is required, the trailing fill will be one character longer than the leading fill. Fixes #5066. --- CHANGELOG.md | 2 ++ cli/src/template_builder.rs | 25 +++++++++++++ cli/src/text_util.rs | 70 +++++++++++++++++++++++++++++++++++++ docs/templates.md | 4 +++ 4 files changed, 101 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d1034f3a..c88363b330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,6 +102,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * New template function `config(name)` to access to configuration variable from template. +* New template function `pad_centered()` to center content within a minimum width. + * Templater now supports `list.filter(|x| ..)` method. * The `diff` commit template keyword now supports custom formatting via diff --git a/cli/src/template_builder.rs b/cli/src/template_builder.rs index 21f7a395d2..6597b3fdce 100644 --- a/cli/src/template_builder.rs +++ b/cli/src/template_builder.rs @@ -1475,6 +1475,22 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun let template = new_pad_template(content, fill_char, width, text_util::write_padded_end); Ok(L::wrap_template(template)) }); + map.insert( + "pad_centered", + |language, diagnostics, build_ctx, function| { + let ([width_node, content_node], [fill_char_node]) = + function.expect_named_arguments(&["", "", "fill_char"])?; + let width = expect_usize_expression(language, diagnostics, build_ctx, width_node)?; + let content = + expect_template_expression(language, diagnostics, build_ctx, content_node)?; + let fill_char = fill_char_node + .map(|node| expect_template_expression(language, diagnostics, build_ctx, node)) + .transpose()?; + let template = + new_pad_template(content, fill_char, width, text_util::write_padded_centered); + Ok(L::wrap_template(template)) + }, + ); map.insert( "truncate_start", |language, diagnostics, build_ctx, function| { @@ -2980,6 +2996,9 @@ mod tests { insta::assert_snapshot!( env.render_ok(r"'{' ++ pad_end(5, label('red', 'foo')) ++ '}'"), @"{foo }"); + insta::assert_snapshot!( + env.render_ok(r"'{' ++ pad_centered(5, label('red', 'foo')) ++ '}'"), + @"{ foo }"); // Labeled fill char insta::assert_snapshot!( @@ -2988,6 +3007,9 @@ mod tests { insta::assert_snapshot!( env.render_ok(r"pad_end(5, label('red', 'foo'), fill_char=label('cyan', '='))"), @"foo=="); + insta::assert_snapshot!( + env.render_ok(r"pad_centered(5, label('red', 'foo'), fill_char=label('cyan', '='))"), + @"=foo="); // Error in fill char: the output looks odd (because the error message // isn't 1-width character), but is still readable. @@ -2997,6 +3019,9 @@ mod tests { insta::assert_snapshot!( env.render_ok(r"pad_end(5, 'foo', fill_char=bad_string)"), @"foo<Bad>"); + insta::assert_snapshot!( + env.render_ok(r"pad_centered(5, 'foo', fill_char=bad_string)"), + @"foo"); } #[test] diff --git a/cli/src/text_util.rs b/cli/src/text_util.rs index a7f88dc659..7595b0dc8d 100644 --- a/cli/src/text_util.rs +++ b/cli/src/text_util.rs @@ -302,6 +302,28 @@ pub fn write_padded_end( Ok(()) } +/// Writes text padded to `min_width` by adding leading and trailing fill +/// characters. +/// +/// The input `recorded_content` should be a single-line text. The +/// `recorded_fill_char` should be bytes of a 1-width character. +pub fn write_padded_centered( + formatter: &mut dyn Formatter, + recorded_content: &FormatRecorder, + recorded_fill_char: &FormatRecorder, + min_width: usize, +) -> io::Result<()> { + // We don't care about the width of non-UTF-8 bytes, but should not panic. + let width = String::from_utf8_lossy(recorded_content.data()).width(); + let fill_width = min_width.saturating_sub(width); + let fill_left = fill_width / 2; + let fill_right = fill_width - fill_left; + write_padding(formatter, recorded_fill_char, fill_left)?; + recorded_content.replay(formatter)?; + write_padding(formatter, recorded_fill_char, fill_right)?; + Ok(()) +} + fn write_padding( formatter: &mut dyn Formatter, recorded_fill_char: &FormatRecorder, @@ -843,6 +865,24 @@ mod tests { format_colored(|formatter| write_padded_end(formatter, &recorder, &fill, 8)), @"foobar==" ); + + // Pad centered + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 6)), + @"foobar" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 7)), + @"foobar=" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 8)), + @"=foobar=" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 13)), + @"===foobar====" + ); } #[test] @@ -864,6 +904,12 @@ mod tests { format_colored(|formatter| write_padded_end(formatter, &recorder, &fill, 6)), @"foo===" ); + + // Pad centered + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 6)), + @"=foo==" + ); } #[test] @@ -890,6 +936,20 @@ mod tests { format_colored(|formatter| write_padded_end(formatter, &recorder, &fill, 10)), @"àbc̀一二三=" ); + + // Pad centered + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 9)), + @"àbc̀一二三" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 10)), + @"àbc̀一二三=" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 13)), + @"==àbc̀一二三==" + ); } #[test] @@ -916,6 +976,16 @@ mod tests { format_colored(|formatter| write_padded_end(formatter, &recorder, &fill, 1)), @"=" ); + + // Pad centered + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 0)), + @"" + ); + insta::assert_snapshot!( + format_colored(|formatter| write_padded_centered(formatter, &recorder, &fill, 1)), + @"=" + ); } #[test] diff --git a/docs/templates.md b/docs/templates.md index 3a275faaf1..d8c6202d6f 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -55,6 +55,10 @@ 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. +* `pad_centered(width: Integer, content: Template[, fill_char: Template])`: Pad + content by adding both leading and trailing fill characters. If an odd number + of fill characters are needed, the trailing fill will be one longer than the + leading fill. The `content` shouldn't have newline characters. * `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