summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Otto <th1000s@posteo.net>2022-01-03 22:46:36 +0100
committerDan Davison <dandavison7@gmail.com>2022-01-16 18:33:13 -0500
commitbbbb0bd8da7da90690feee0d1f11cbc6dfeef766 (patch)
tree276a2868e5ec7d2b684292e7e0453faa7051bdd4
parent102936b083f9c63ee94f3e5ad87a63ed5b3edd29 (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.yml2
-rw-r--r--src/features/line_numbers.rs14
-rw-r--r--src/format.rs103
-rw-r--r--src/handlers/blame.rs5
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 {