From ebd008c364bc43cd71ee31a3570a03f6dd80a493 Mon Sep 17 00:00:00 2001 From: Thomas Otto Date: Sat, 30 Jan 2021 12:05:41 +0100 Subject: Add line number width calculation --- src/features/line_numbers.rs | 112 ++++++++++++++++++++++++++++++++++++++++++- src/features/side_by_side.rs | 17 +++++++ src/format.rs | 28 ++++++++++- 3 files changed, 154 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs index 2ac0de01..c8570ad2 100644 --- a/src/features/line_numbers.rs +++ b/src/features/line_numbers.rs @@ -136,7 +136,7 @@ lazy_static! { static ref LINE_NUMBERS_PLACEHOLDER_REGEX: Regex = format::make_placeholder_regex(&["nm", "np"]); } -#[derive(Default)] +#[derive(Default, Debug)] pub struct LineNumbersData<'a> { pub format_data: PlusMinus>, pub line_number: PlusMinus, @@ -144,6 +144,8 @@ pub struct LineNumbersData<'a> { pub plus_file: String, } +pub type SideBySideLineWidth = PlusMinus; + // Although it's probably unusual, a single format string can contain multiple placeholders. E.g. // line-numbers-right-format = "{nm} {np}|" impl<'a> LineNumbersData<'a> { @@ -170,6 +172,32 @@ impl<'a> LineNumbersData<'a> { 1 + (hunk_max_line_number as f64).log10().floor() as usize; self.plus_file = plus_file; } + + pub fn formatted_width(&self) -> SideBySideLineWidth { + let format_data_width = |format_data: &format::FormatStringData<'a>| { + // Provide each Placeholder with the max_line_number_width to calculate the + // actual width. Only use prefix and suffix of the last element, otherwise + // only the prefix (as the suffix also contains the following prefix). + format_data + .last() + .map(|last| { + let (prefix_width, suffix_width) = last.width(self.hunk_max_line_number_width); + format_data + .iter() + .rev() + .skip(1) + .map(|p| p.width(self.hunk_max_line_number_width).0) + .sum::() + + prefix_width + + suffix_width + }) + .unwrap_or(0) + }; + PlusMinus::new( + format_data_width(&self.format_data[Left]), + format_data_width(&self.format_data[Right]), + ) + } } #[allow(clippy::too_many_arguments)] @@ -258,6 +286,8 @@ pub mod tests { alignment_spec: None, width: None, suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -272,6 +302,8 @@ pub mod tests { alignment_spec: None, width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -286,6 +318,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -300,6 +334,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -314,6 +350,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "@@", + prefix_len: 2, + suffix_len: 2, }] ) } @@ -332,6 +370,8 @@ pub mod tests { alignment_spec: Some("<"), width: Some(3), suffix: "@@---{np:_>4}**", + prefix_len: 2, + suffix_len: 15, }, format::FormatStringPlaceholderData { prefix: "@@---", @@ -339,6 +379,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "**", + prefix_len: 5, + suffix_len: 2, } ] ) @@ -354,10 +396,78 @@ pub mod tests { alignment_spec: None, width: None, suffix: "__@@---**", + prefix_len: 0, + suffix_len: 9, },] ) } + #[test] + fn test_line_number_placeholder_width_one() { + use format::parse_line_number_format; + + let data = parse_line_number_format("", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(0), (0, 0)); + + let data = parse_line_number_format("", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (0, 0)); + + let data = parse_line_number_format("│+│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (0, 3)); + + let data = parse_line_number_format("{np}", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (4, 0)); + + let data = parse_line_number_format("│{np}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (5, 1)); + + let data = parse_line_number_format("│{np:2}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (5, 1)); + + let data = parse_line_number_format("│{np:6}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(4), (7, 1)); + } + + #[test] + fn test_line_number_placeholder_width_two() { + use format::parse_line_number_format; + + let data = parse_line_number_format("│{nm}│{np}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(1), (2, 6)); + assert_eq!(data[1].width(1), (2, 1)); + + let data = parse_line_number_format("│{nm:_>5}│{np:1}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(1), (6, 8)); + assert_eq!(data[1].width(1), (2, 1)); + + let data = parse_line_number_format("│{nm}│{np:5}│", &LINE_NUMBERS_PLACEHOLDER_REGEX); + assert_eq!(data[0].width(7), (8, 8)); + assert_eq!(data[1].width(7), (8, 1)); + } + + #[test] + fn test_line_numbers_data() { + let mut data = LineNumbersData::from_format_strings("", ""); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), PlusMinus::new(0, 0)); + + let mut data = LineNumbersData::from_format_strings("│", "│+│"); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), PlusMinus::new(1, 3)); + + let mut data = LineNumbersData::from_format_strings("│{nm:^3}│", "│{np:^3}│"); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), PlusMinus::new(8, 8)); + + let mut data = LineNumbersData::from_format_strings("│{nm:^3}│ │{np:<12}│ │{nm}│", ""); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), PlusMinus::new(32, 0)); + + let mut data = LineNumbersData::from_format_strings("│{np:^3}│ │{nm:<12}│ │{np}│", ""); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), PlusMinus::new(32, 0)); + } + fn _get_capture<'a>(i: usize, j: usize, caps: &'a Vec) -> &'a str { caps[i].get(j).map_or("", |m| m.as_str()) } diff --git a/src/features/side_by_side.rs b/src/features/side_by_side.rs index ca493949..9b9a10f8 100644 --- a/src/features/side_by_side.rs +++ b/src/features/side_by_side.rs @@ -61,6 +61,23 @@ impl SideBySideData { } } +pub fn available_line_width( + config: &Config, + data: &line_numbers::LineNumbersData, +) -> line_numbers::SideBySideLineWidth { + let linennumbers_width = data.formatted_width(); + + // The width can be reduced by the line numbers and/or a possibly kept 1-wide "+/-/ " prefix. + let line_width = |side: PanelSide| { + config.side_by_side_data[side] + .width + .saturating_sub(linennumbers_width[side]) + .saturating_sub(config.keep_plus_minus_markers as usize) + }; + + LeftRight::new(line_width(Left), line_width(Right)) +} + /// Emit a sequence of minus and plus lines in side-by-side mode. #[allow(clippy::too_many_arguments)] pub fn paint_minus_and_plus_lines_side_by_side<'a>( diff --git a/src/format.rs b/src/format.rs index 67d1f509..1f8bfb00 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,4 +1,5 @@ use regex::Regex; +use unicode_segmentation::UnicodeSegmentation; #[derive(Debug, Default, PartialEq)] pub struct FormatStringPlaceholderData<'a> { @@ -7,6 +8,23 @@ pub struct FormatStringPlaceholderData<'a> { pub alignment_spec: Option<&'a str>, pub width: Option, pub suffix: &'a str, + pub prefix_len: usize, + pub suffix_len: usize, +} + +impl<'a> FormatStringPlaceholderData<'a> { + pub fn width(&self, hunk_max_line_number_width: usize) -> (usize, usize) { + // Only if Some(placeholder) is present will there be a number formatted + // by this placeholder, if not width is also None. + ( + self.prefix_len + + std::cmp::max( + self.placeholder.map_or(0, |_| hunk_max_line_number_width), + self.width.unwrap_or(0), + ), + self.suffix_len, + ) + } } pub type FormatStringData<'a> = Vec>; @@ -40,8 +58,10 @@ pub fn parse_line_number_format<'a>( for captures in placeholder_regex.captures_iter(format_string) { let _match = captures.get(0).unwrap(); + let prefix = &format_string[offset.._match.start()]; + let suffix = &format_string[_match.end()..]; format_data.push(FormatStringPlaceholderData { - prefix: &format_string[offset.._match.start()], + prefix, placeholder: captures.get(1).map(|m| m.as_str()), alignment_spec: captures.get(3).map(|m| m.as_str()), width: captures.get(4).map(|m| { @@ -49,7 +69,9 @@ pub fn parse_line_number_format<'a>( .parse() .unwrap_or_else(|_| panic!("Invalid width in format string: {}", format_string)) }), - suffix: &format_string[_match.end()..], + suffix, + prefix_len: prefix.graphemes(true).count(), + suffix_len: suffix.graphemes(true).count(), }); offset = _match.end(); } @@ -61,6 +83,8 @@ pub fn parse_line_number_format<'a>( alignment_spec: None, width: None, suffix: &format_string[0..], + prefix_len: 0, + suffix_len: format_string.graphemes(true).count(), }) } format_data -- cgit v1.2.3