summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Otto <th1000s@posteo.net>2021-01-30 12:05:41 +0100
committerThomas Otto <th1000s@posteo.net>2021-10-16 14:07:30 +0200
commitebd008c364bc43cd71ee31a3570a03f6dd80a493 (patch)
tree0ff8c57a8c8ef53b999f6a33ef3595a778d2e933
parent464e9cab80dcc3301d1a8c26f8695d312d4642f2 (diff)
Add line number width calculation
-rw-r--r--src/features/line_numbers.rs112
-rw-r--r--src/features/side_by_side.rs17
-rw-r--r--src/format.rs28
3 files changed, 154 insertions, 3 deletions
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<format::FormatStringData<'a>>,
pub line_number: PlusMinus<usize>,
@@ -144,6 +144,8 @@ pub struct LineNumbersData<'a> {
pub plus_file: String,
}
+pub type SideBySideLineWidth = PlusMinus<usize>;
+
// 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::<usize>()
+ + 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<Captures>) -> &'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<usize>,
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<FormatStringPlaceholderData<'a>>;
@@ -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