summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Otto <th1000s@posteo.net>2021-01-30 12:05:41 +0100
committerThomas Otto <th1000s@posteo.net>2021-04-19 22:37:41 +0200
commit78640d439fe488b8b647cae8301387b1995ea0eb (patch)
treead0547bdfbd9e20a8ec9fcc160ad2d09a5a2a74e
parentd4f3f69b4e114b43fb874342dc244f77c69848f2 (diff)
Add line number width calculation
-rw-r--r--src/features/line_numbers.rs184
-rw-r--r--src/features/side_by_side.rs25
2 files changed, 179 insertions, 30 deletions
diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs
index d5c38488..866aa3e9 100644
--- a/src/features/line_numbers.rs
+++ b/src/features/line_numbers.rs
@@ -2,14 +2,18 @@ use std::cmp::max;
use lazy_static::lazy_static;
use regex::Regex;
+use unicode_segmentation::UnicodeSegmentation;
use crate::config;
use crate::delta::State;
use crate::features::hyperlinks;
use crate::features::side_by_side;
+use crate::features::side_by_side::LeftRight;
use crate::features::OptionValueFunction;
use crate::style::Style;
+use crate::features::side_by_side::PanelSide::{Left, Right};
+
pub fn make_feature() -> Vec<(String, OptionValueFunction)> {
builtin_feature!([
(
@@ -67,8 +71,8 @@ pub fn format_and_paint_line_numbers<'a>(
side_by_side_panel: Option<side_by_side::PanelSide>,
config: &'a config::Config,
) -> Vec<ansi_term::ANSIGenericString<'a, str>> {
- let m_ref = &mut line_numbers_data.hunk_minus_line_number;
- let p_ref = &mut line_numbers_data.hunk_plus_line_number;
+ let nr_left = line_numbers_data.line_number[Left];
+ let nr_right = line_numbers_data.line_number[Right];
let (minus_style, zero_style, plus_style) = (
config.line_numbers_minus_style,
config.line_numbers_zero_style,
@@ -76,20 +80,17 @@ pub fn format_and_paint_line_numbers<'a>(
);
let ((minus_number, plus_number), (minus_style, plus_style)) = match state {
State::HunkMinus(_) => {
- let m = *m_ref;
- *m_ref += 1;
- ((Some(m), None), (minus_style, plus_style))
+ line_numbers_data.line_number[Left] += 1;
+ ((Some(nr_left), None), (minus_style, plus_style))
}
State::HunkZero => {
- let (m, p) = (*m_ref, *p_ref);
- *m_ref += 1;
- *p_ref += 1;
- ((Some(m), Some(p)), (zero_style, zero_style))
+ line_numbers_data.line_number[Left] += 1;
+ line_numbers_data.line_number[Right] += 1;
+ ((Some(nr_left), Some(nr_right)), (zero_style, zero_style))
}
State::HunkPlus(_) => {
- let p = *p_ref;
- *p_ref += 1;
- ((None, Some(p)), (minus_style, plus_style))
+ line_numbers_data.line_number[Right] += 1;
+ ((None, Some(nr_right)), (minus_style, plus_style))
}
_ => return Vec::new(),
};
@@ -105,7 +106,7 @@ pub fn format_and_paint_line_numbers<'a>(
if emit_left {
formatted_numbers.extend(format_and_paint_line_number_field(
- &line_numbers_data.left_format_data,
+ &line_numbers_data.format_data[Left],
&config.line_numbers_left_style,
minus_number,
plus_number,
@@ -119,7 +120,7 @@ pub fn format_and_paint_line_numbers<'a>(
if emit_right {
formatted_numbers.extend(format_and_paint_line_number_field(
- &line_numbers_data.right_format_data,
+ &line_numbers_data.format_data[Right],
&config.line_numbers_right_style,
minus_number,
plus_number,
@@ -154,10 +155,8 @@ lazy_static! {
#[derive(Default)]
pub struct LineNumbersData<'a> {
- pub left_format_data: LineNumberFormatData<'a>,
- pub right_format_data: LineNumberFormatData<'a>,
- pub hunk_minus_line_number: usize,
- pub hunk_plus_line_number: usize,
+ pub format_data: LeftRight<LineNumberFormatData<'a>>,
+ pub line_number: LeftRight<usize>,
pub hunk_max_line_number_width: usize,
pub plus_file: String,
}
@@ -173,15 +172,33 @@ pub struct LineNumberPlaceholderData<'a> {
pub alignment_spec: Option<&'a str>,
pub width: Option<usize>,
pub suffix: &'a str,
+ prefix_len: usize,
+ suffix_len: usize,
+}
+
+impl<'a> LineNumberPlaceholderData<'a> {
+ 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,
+ )
+ }
}
impl<'a> LineNumbersData<'a> {
pub fn from_format_strings(left_format: &'a str, right_format: &'a str) -> LineNumbersData<'a> {
Self {
- left_format_data: parse_line_number_format(left_format),
- right_format_data: parse_line_number_format(right_format),
- hunk_minus_line_number: 0,
- hunk_plus_line_number: 0,
+ format_data: LeftRight::new(
+ parse_line_number_format(left_format),
+ parse_line_number_format(right_format),
+ ),
+ line_number: LeftRight::new(0, 0),
hunk_max_line_number_width: 0,
plus_file: "".to_string(),
}
@@ -191,23 +208,54 @@ impl<'a> LineNumbersData<'a> {
pub fn initialize_hunk(&mut self, line_numbers: &[(usize, usize)], plus_file: String) {
// Typically, line_numbers has length 2: an entry for the minus file, and one for the plus
// file. In the case of merge commits, it may be longer.
- self.hunk_minus_line_number = line_numbers[0].0;
- self.hunk_plus_line_number = line_numbers[line_numbers.len() - 1].0;
+ self.line_number =
+ LeftRight::new(line_numbers[0].0, line_numbers[line_numbers.len() - 1].0);
let hunk_max_line_number = line_numbers.iter().map(|(n, d)| n + d).max().unwrap();
self.hunk_max_line_number_width =
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: &LineNumberFormatData<'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)
+ };
+ side_by_side::LeftRight::new(
+ format_data_width(&self.format_data[Left]),
+ format_data_width(&self.format_data[Right]),
+ )
+ }
}
+pub type SideBySideLineWidth = LeftRight<usize>;
+
fn parse_line_number_format(format_string: &str) -> LineNumberFormatData {
let mut format_data = Vec::new();
let mut offset = 0;
for captures in LINE_NUMBERS_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(LineNumberPlaceholderData {
- 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| {
@@ -215,7 +263,9 @@ fn parse_line_number_format(format_string: &str) -> LineNumberFormatData {
.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();
}
@@ -227,6 +277,8 @@ fn parse_line_number_format(format_string: &str) -> LineNumberFormatData {
alignment_spec: None,
width: None,
suffix: &format_string[0..],
+ prefix_len: 0,
+ suffix_len: format_string.graphemes(true).count(),
})
}
format_data
@@ -328,6 +380,8 @@ pub mod tests {
alignment_spec: None,
width: None,
suffix: "",
+ prefix_len: 0,
+ suffix_len: 0,
}]
)
}
@@ -342,6 +396,8 @@ pub mod tests {
alignment_spec: None,
width: Some(4),
suffix: "",
+ prefix_len: 0,
+ suffix_len: 0,
}]
)
}
@@ -356,6 +412,8 @@ pub mod tests {
alignment_spec: Some(">"),
width: Some(4),
suffix: "",
+ prefix_len: 0,
+ suffix_len: 0,
}]
)
}
@@ -370,6 +428,8 @@ pub mod tests {
alignment_spec: Some(">"),
width: Some(4),
suffix: "",
+ prefix_len: 0,
+ suffix_len: 0,
}]
)
}
@@ -384,6 +444,8 @@ pub mod tests {
alignment_spec: Some(">"),
width: Some(4),
suffix: "@@",
+ prefix_len: 2,
+ suffix_len: 2,
}]
)
}
@@ -399,6 +461,9 @@ pub mod tests {
alignment_spec: Some("<"),
width: Some(3),
suffix: "@@---{np:_>4}**",
+
+ prefix_len: 2,
+ suffix_len: 15,
},
LineNumberPlaceholderData {
prefix: "@@---",
@@ -406,6 +471,9 @@ pub mod tests {
alignment_spec: Some(">"),
width: Some(4),
suffix: "**",
+
+ prefix_len: 5,
+ suffix_len: 2,
}
]
)
@@ -421,10 +489,74 @@ pub mod tests {
alignment_spec: None,
width: None,
suffix: "__@@---**",
+ prefix_len: 0,
+ suffix_len: 9,
},]
)
}
+ #[test]
+ fn test_line_number_placeholder_width_one() {
+ let data = parse_line_number_format("");
+ assert_eq!(data[0].width(0), (0, 0));
+
+ let data = parse_line_number_format("");
+ assert_eq!(data[0].width(4), (0, 0));
+
+ let data = parse_line_number_format("│+│");
+ assert_eq!(data[0].width(4), (0, 3));
+
+ let data = parse_line_number_format("{np}");
+ assert_eq!(data[0].width(4), (4, 0));
+
+ let data = parse_line_number_format("│{np}│");
+ assert_eq!(data[0].width(4), (5, 1));
+
+ let data = parse_line_number_format("│{np:2}│");
+ assert_eq!(data[0].width(4), (5, 1));
+
+ let data = parse_line_number_format("│{np:6}│");
+ assert_eq!(data[0].width(4), (7, 1));
+ }
+
+ #[test]
+ fn test_line_number_placeholder_width_two() {
+ let data = parse_line_number_format("│{nm}│{np}│");
+ 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}│");
+ 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}│");
+ 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(), LeftRight::new(0, 0));
+
+ let mut data = LineNumbersData::from_format_strings("│", "│+│");
+ data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
+ assert_eq!(data.formatted_width(), LeftRight::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(), LeftRight::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(), LeftRight::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(), LeftRight::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 70340b69..dceb2bc6 100644
--- a/src/features/side_by_side.rs
+++ b/src/features/side_by_side.rs
@@ -100,6 +100,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| {
+ 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(PanelSide::Left), line_width(PanelSide::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>(
@@ -179,8 +196,8 @@ pub fn paint_zero_lines_side_by_side(
// TODO: Avoid doing the superimpose_style_sections work twice.
// HACK: These are getting incremented twice, so knock them back down once.
if let Some(d) = line_numbers_data.as_mut() {
- d.hunk_minus_line_number -= 1;
- d.hunk_plus_line_number -= 1;
+ d.line_number[Left] -= 1;
+ d.line_number[Right] -= 1;
}
right_pad_left_panel_line(
&mut left_panel_line,
@@ -380,12 +397,12 @@ fn paint_minus_or_plus_panel_line(
(s, t) if s == t => {}
(State::HunkPlus(_), State::HunkMinus(_)) => {
if let Some(d) = line_numbers_data.as_mut() {
- d.hunk_minus_line_number -= 1;
+ d.line_number[Left] -= 1;
}
}
(State::HunkMinus(_), State::HunkPlus(_)) => {
if let Some(d) = line_numbers_data.as_mut() {
- d.hunk_plus_line_number -= 1;
+ d.line_number[Right] -= 1;
}
}
_ => unreachable!(),