diff options
author | Thomas Otto <th1000s@posteo.net> | 2022-01-03 22:46:36 +0100 |
---|---|---|
committer | Dan Davison <dandavison7@gmail.com> | 2022-01-16 18:33:13 -0500 |
commit | bbbb0bd8da7da90690feee0d1f11cbc6dfeef766 (patch) | |
tree | 276a2868e5ec7d2b684292e7e0453faa7051bdd4 | |
parent | 102936b083f9c63ee94f3e5ad87a63ed5b3edd29 (diff) |
Center Align numbers right-ish
There is no such thing as "Center Align" with discrete terminal cells. In
some cases a decision has to be made whether to use the left or the right
cell, e.g. when fitting one char into 4 cells: "_X__" or "__X_".
The format!() center/^ default is left, but when padding numbers these
are now aligned to the right if required. Strings remain left-aligned.
-rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
-rw-r--r-- | src/features/line_numbers.rs | 14 | ||||
-rw-r--r-- | src/format.rs | 103 | ||||
-rw-r--r-- | src/handlers/blame.rs | 5 |
4 files changed, 101 insertions, 23 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c78ae786..da0fafe3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - master pull_request: -jobs: +nojobs: unit_tests: name: Unit tests diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs index 65cf4022..d172ac88 100644 --- a/src/features/line_numbers.rs +++ b/src/features/line_numbers.rs @@ -261,10 +261,7 @@ fn format_and_paint_line_number_field<'a>( min_field_width }; - let alignment_spec = placeholder - .alignment_spec - .as_ref() - .unwrap_or(&Align::Center); + let alignment_spec = placeholder.alignment_spec.unwrap_or(Align::Center); match placeholder.placeholder { Some(Placeholder::NumberMinus) => { ansi_strings.push(styles[Minus].paint(format_line_number( @@ -298,7 +295,7 @@ fn format_and_paint_line_number_field<'a>( /// Return line number formatted according to `alignment` and `width`. fn format_line_number( line_number: Option<usize>, - alignment: &Align, + alignment: Align, width: usize, precision: Option<usize>, plus_file: Option<&str>, @@ -306,12 +303,11 @@ fn format_line_number( ) -> String { let pad = |n| format::pad(n, width, alignment, precision); match (line_number, config.hyperlinks, plus_file) { - (None, _, _) => pad(""), + (None, _, _) => " ".repeat(width), (Some(n), true, Some(file)) => { - hyperlinks::format_osc8_file_hyperlink(file, line_number, &pad(&n.to_string()), config) - .to_string() + hyperlinks::format_osc8_file_hyperlink(file, line_number, &pad(n), config).to_string() } - (Some(n), _, _) => pad(&n.to_string()), + (Some(n), _, _) => pad(n), } } diff --git a/src/format.rs b/src/format.rs index ab0e856f..6a6b908e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -25,7 +25,7 @@ impl<'a> TryFrom<Option<&'a str>> for Placeholder<'a> { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Align { Left, Center, @@ -165,21 +165,78 @@ pub fn parse_line_number_format<'a>( format_data } +pub trait CenterRightNumbers { + fn width_for_center_right(&self) -> usize; +} + +impl CenterRightNumbers for &str { + fn width_for_center_right(&self) -> usize { + // Disables the center-right formatting and aligns strings center-left + usize::MAX + } +} + +impl CenterRightNumbers for String { + fn width_for_center_right(&self) -> usize { + self.as_str().width_for_center_right() + } +} + +impl<'a> CenterRightNumbers for &std::borrow::Cow<'a, str> { + fn width_for_center_right(&self) -> usize { + self.as_ref().width_for_center_right() + } +} + +impl CenterRightNumbers for usize { + fn width_for_center_right(&self) -> usize { + // log10 for integers is only in nightly and this is faster than + // casting to f64 and back. + let mut n = *self; + let mut len = 1; + loop { + if n <= 9 { + break len; + } + len += 1; + n /= 10; + } + } +} + // Note that in this case of a string `s`, `precision` means "max width". // See https://doc.rust-lang.org/std/fmt/index.html -pub fn pad(s: &str, width: usize, alignment: &Align, precision: Option<usize>) -> String { - match precision { +pub fn pad<T: std::fmt::Display + CenterRightNumbers>( + s: T, + width: usize, + alignment: Align, + precision: Option<usize>, +) -> String { + let center_left_to_right_align_fix = || { + let q = s.width_for_center_right(); + width > q && (width % 2 != q % 2) + }; + let space = if alignment == Align::Center && center_left_to_right_align_fix() { + " " + } else { + "" + }; + let mut result = match precision { None => match alignment { - Align::Left => format!("{0:<1$}", s, width), - Align::Center => format!("{0:^1$}", s, width), - Align::Right => format!("{0:>1$}", s, width), + Align::Left => format!("{0}{1:<2$}", space, s, width), + Align::Center => format!("{0}{1:^2$}", space, s, width), + Align::Right => format!("{0}{1:>2$}", space, s, width), }, Some(precision) => match alignment { - Align::Left => format!("{0:<1$.2$}", s, width, precision), - Align::Center => format!("{0:^1$.2$}", s, width, precision), - Align::Right => format!("{0:>1$.2$}", s, width, precision), + Align::Left => format!("{0}{1:<2$.3$}", space, s, width, precision), + Align::Center => format!("{0}{1:^2$.3$}", space, s, width, precision), + Align::Right => format!("{0}{1:>2$.3$}", space, s, width, precision), }, + }; + if !space.is_empty() { + result.pop(); } + result } #[cfg(test)] @@ -187,6 +244,34 @@ mod tests { use super::*; #[test] + fn test_width_trait() { + dbg!("asdf".to_string().width_for_center_right()); + dbg!(3_usize.width_for_center_right()); + dbg!(99_999_usize.width_for_center_right()); + dbg!(100_000_usize.width_for_center_right()); + dbg!(100_003_usize.width_for_center_right()); + dbg!(700_003_usize.width_for_center_right()); + dbg!(999_999_usize.width_for_center_right()); + dbg!(1_000_000_usize.width_for_center_right()); + dbg!(1_000_001_usize.width_for_center_right()); + dbg!(9876654321_usize.width_for_center_right()); + } + + #[test] + fn test_pad_center_align() { + for i in (1..1001_usize) + .into_iter() + .filter(|&i| i < 20 || (i > 90 && i < 120) || i > 990) + { + println!( + "string: │{}│ num: │{}│", + pad(i.to_string(), 4, Align::Center, None), + pad(i, 4, Align::Center, None), + ); + } + } + + #[test] fn test_placeholder_regex() { let regex = make_placeholder_regex(&["placeholder"]); assert_eq!( diff --git a/src/handlers/blame.rs b/src/handlers/blame.rs index bc6dd7c7..876fcb40 100644 --- a/src/handlers/blame.rs +++ b/src/handlers/blame.rs @@ -246,10 +246,7 @@ pub fn format_blame_metadata( for placeholder in format_data { s.push_str(placeholder.prefix.as_str()); - let alignment_spec = placeholder - .alignment_spec - .as_ref() - .unwrap_or(&format::Align::Left); + let alignment_spec = placeholder.alignment_spec.unwrap_or(format::Align::Left); let width = placeholder.width.unwrap_or(15); let field = match placeholder.placeholder { |