mirror of
https://github.com/dandavison/delta.git
synced 2026-02-03 11:33:26 +01:00
990 lines
32 KiB
Rust
990 lines
32 KiB
Rust
use std::cmp::max;
|
|
|
|
use lazy_static::lazy_static;
|
|
use regex::Regex;
|
|
|
|
use crate::config;
|
|
use crate::delta::State;
|
|
use crate::features::hyperlinks;
|
|
use crate::features::side_by_side::ansifill::{self, ODD_PAD_CHAR};
|
|
use crate::features::side_by_side::{Left, PanelSide, Right};
|
|
use crate::features::OptionValueFunction;
|
|
use crate::format::{self, Align, Placeholder};
|
|
use crate::minusplus::*;
|
|
use crate::style::Style;
|
|
use crate::utils;
|
|
|
|
pub fn make_feature() -> Vec<(String, OptionValueFunction)> {
|
|
builtin_feature!([
|
|
(
|
|
"line-numbers",
|
|
bool,
|
|
None,
|
|
_opt => true
|
|
),
|
|
(
|
|
"line-numbers-left-style",
|
|
String,
|
|
None,
|
|
_opt => "blue"
|
|
),
|
|
(
|
|
"line-numbers-right-style",
|
|
String,
|
|
None,
|
|
_opt => "blue"
|
|
),
|
|
(
|
|
"line-numbers-minus-style",
|
|
String,
|
|
None,
|
|
opt => if opt.computed.is_light_mode {
|
|
"red".to_string()
|
|
} else {
|
|
"88".to_string()
|
|
}
|
|
),
|
|
(
|
|
"line-numbers-zero-style",
|
|
String,
|
|
None,
|
|
opt => if opt.computed.is_light_mode {"#dddddd"} else {"#444444"}
|
|
),
|
|
(
|
|
"line-numbers-plus-style",
|
|
String,
|
|
None,
|
|
opt => if opt.computed.is_light_mode {
|
|
"green".to_string()
|
|
} else {
|
|
"28".to_string()
|
|
}
|
|
)
|
|
])
|
|
}
|
|
|
|
pub fn linenumbers_and_styles<'a>(
|
|
line_numbers_data: &'a mut LineNumbersData,
|
|
state: &State,
|
|
config: &'a config::Config,
|
|
increment: bool,
|
|
) -> Option<(MinusPlus<Option<usize>>, MinusPlus<Style>)> {
|
|
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_style_minusplus[Minus],
|
|
config.line_numbers_zero_style,
|
|
config.line_numbers_style_minusplus[Plus],
|
|
);
|
|
let ((minus_number, plus_number), (minus_style, plus_style)) = match state {
|
|
State::HunkMinus(_, _) => {
|
|
line_numbers_data.line_number[Left] += increment as usize;
|
|
((Some(nr_left), None), (minus_style, plus_style))
|
|
}
|
|
State::HunkMinusWrapped => ((None, None), (minus_style, plus_style)),
|
|
State::HunkZero(_, _) => {
|
|
line_numbers_data.line_number[Left] += increment as usize;
|
|
line_numbers_data.line_number[Right] += increment as usize;
|
|
((Some(nr_left), Some(nr_right)), (zero_style, zero_style))
|
|
}
|
|
State::HunkZeroWrapped => ((None, None), (zero_style, zero_style)),
|
|
State::HunkPlus(_, _) => {
|
|
line_numbers_data.line_number[Right] += increment as usize;
|
|
((None, Some(nr_right)), (minus_style, plus_style))
|
|
}
|
|
State::HunkPlusWrapped => ((None, None), (minus_style, plus_style)),
|
|
_ => return None,
|
|
};
|
|
Some((
|
|
MinusPlus::new(minus_number, plus_number),
|
|
MinusPlus::new(minus_style, plus_style),
|
|
))
|
|
}
|
|
|
|
/// Return a vec of `ansi_term::ANSIGenericString`s representing the left and right fields of the
|
|
/// two-column line number display.
|
|
pub fn format_and_paint_line_numbers<'a>(
|
|
line_numbers_data: &'a LineNumbersData,
|
|
side_by_side_panel: Option<PanelSide>,
|
|
styles: MinusPlus<Style>,
|
|
line_numbers: MinusPlus<Option<usize>>,
|
|
config: &'a config::Config,
|
|
) -> Vec<ansi_term::ANSIGenericString<'a, str>> {
|
|
let mut formatted_numbers = Vec::new();
|
|
|
|
let (emit_left, emit_right) = match (config.side_by_side, side_by_side_panel) {
|
|
(false, _) => (true, true),
|
|
(true, Some(Left)) => (true, false),
|
|
(true, Some(Right)) => (false, true),
|
|
(true, None) => unreachable!(),
|
|
};
|
|
|
|
if emit_left {
|
|
formatted_numbers.extend(format_and_paint_line_number_field(
|
|
line_numbers_data,
|
|
Minus,
|
|
&styles,
|
|
&line_numbers,
|
|
config,
|
|
));
|
|
}
|
|
|
|
if emit_right {
|
|
formatted_numbers.extend(format_and_paint_line_number_field(
|
|
line_numbers_data,
|
|
Plus,
|
|
&styles,
|
|
&line_numbers,
|
|
config,
|
|
));
|
|
}
|
|
formatted_numbers
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref LINE_NUMBERS_PLACEHOLDER_REGEX: Regex =
|
|
format::make_placeholder_regex(&["nm", "np"]);
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct LineNumbersData<'a> {
|
|
pub format_data: MinusPlus<format::FormatStringData<'a>>,
|
|
pub line_number: MinusPlus<usize>,
|
|
pub hunk_max_line_number_width: usize,
|
|
pub plus_file: String,
|
|
}
|
|
|
|
pub type SideBySideLineWidth = MinusPlus<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> {
|
|
pub fn from_format_strings(
|
|
format: &'a MinusPlus<String>,
|
|
use_full_width: ansifill::UseFullPanelWidth,
|
|
) -> LineNumbersData<'a> {
|
|
let insert_center_space_on_odd_width = use_full_width.pad_width();
|
|
Self {
|
|
format_data: MinusPlus::new(
|
|
format::parse_line_number_format(
|
|
&format[Left],
|
|
&LINE_NUMBERS_PLACEHOLDER_REGEX,
|
|
false,
|
|
),
|
|
format::parse_line_number_format(
|
|
&format[Right],
|
|
&LINE_NUMBERS_PLACEHOLDER_REGEX,
|
|
insert_center_space_on_odd_width,
|
|
),
|
|
),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
/// Initialize line number data for a hunk.
|
|
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.line_number =
|
|
MinusPlus::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 empty_for_sbs(use_full_width: ansifill::UseFullPanelWidth) -> LineNumbersData<'a> {
|
|
let insert_center_space_on_odd_width = use_full_width.pad_width();
|
|
Self {
|
|
format_data: if insert_center_space_on_odd_width {
|
|
let format_left = vec![format::FormatStringPlaceholderData::default()];
|
|
let format_right = vec![format::FormatStringPlaceholderData {
|
|
prefix: format!("{ODD_PAD_CHAR}").into(),
|
|
prefix_len: 1,
|
|
..Default::default()
|
|
}];
|
|
MinusPlus::new(format_left, format_right)
|
|
} else {
|
|
MinusPlus::default()
|
|
},
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
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)
|
|
};
|
|
MinusPlus::new(
|
|
format_data_width(&self.format_data[Left]),
|
|
format_data_width(&self.format_data[Right]),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn format_and_paint_line_number_field<'a>(
|
|
line_numbers_data: &'a LineNumbersData,
|
|
side: MinusPlusIndex,
|
|
styles: &MinusPlus<Style>,
|
|
line_numbers: &MinusPlus<Option<usize>>,
|
|
config: &config::Config,
|
|
) -> Vec<ansi_term::ANSIGenericString<'a, str>> {
|
|
let min_field_width = line_numbers_data.hunk_max_line_number_width;
|
|
|
|
let format_data = &line_numbers_data.format_data[side];
|
|
let plus_file = &line_numbers_data.plus_file;
|
|
let style = &config.line_numbers_style_leftright[side];
|
|
|
|
let mut ansi_strings = Vec::new();
|
|
let mut suffix = "";
|
|
for placeholder in format_data {
|
|
ansi_strings.push(style.paint(placeholder.prefix.as_str()));
|
|
|
|
let width = if let Some(placeholder_width) = placeholder.width {
|
|
max(placeholder_width, min_field_width)
|
|
} else {
|
|
min_field_width
|
|
};
|
|
|
|
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(
|
|
line_numbers[Minus],
|
|
alignment_spec,
|
|
width,
|
|
placeholder.precision,
|
|
None,
|
|
config,
|
|
)))
|
|
}
|
|
Some(Placeholder::NumberPlus) => {
|
|
ansi_strings.push(styles[Plus].paint(format_line_number(
|
|
line_numbers[Plus],
|
|
alignment_spec,
|
|
width,
|
|
placeholder.precision,
|
|
Some(plus_file),
|
|
config,
|
|
)))
|
|
}
|
|
None => {}
|
|
_ => unreachable!("Invalid placeholder"),
|
|
}
|
|
suffix = placeholder.suffix.as_str();
|
|
}
|
|
ansi_strings.push(style.paint(suffix));
|
|
ansi_strings
|
|
}
|
|
|
|
/// Return line number formatted according to `alignment` and `width`.
|
|
fn format_line_number(
|
|
line_number: Option<usize>,
|
|
alignment: Align,
|
|
width: usize,
|
|
precision: Option<usize>,
|
|
plus_file: Option<&str>,
|
|
config: &config::Config,
|
|
) -> String {
|
|
let pad = |n| format::pad(n, width, alignment, precision);
|
|
match (line_number, config.hyperlinks, plus_file) {
|
|
(None, _, _) => " ".repeat(width),
|
|
(Some(n), true, Some(file)) => match utils::path::absolute_path(file, config) {
|
|
Some(absolute_path) => {
|
|
hyperlinks::format_osc8_file_hyperlink(absolute_path, line_number, &pad(n), config)
|
|
.to_string()
|
|
}
|
|
None => file.to_owned(),
|
|
},
|
|
(Some(n), _, _) => pad(n),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use regex::Captures;
|
|
|
|
use crate::ansi::strip_ansi_codes;
|
|
use crate::features::side_by_side::ansifill::ODD_PAD_CHAR;
|
|
use crate::format::FormatStringData;
|
|
use crate::tests::integration_test_utils::{make_config_from_args, run_delta, DeltaTest};
|
|
|
|
use super::*;
|
|
|
|
pub fn parse_line_number_format_with_default_regex(
|
|
format_string: &str,
|
|
) -> FormatStringData<'_> {
|
|
format::parse_line_number_format(format_string, &LINE_NUMBERS_PLACEHOLDER_REGEX, false)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_1() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("{nm}"),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "".into(),
|
|
placeholder: Some(Placeholder::NumberMinus),
|
|
..Default::default()
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_2() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("{np:4}"),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: None,
|
|
width: Some(4),
|
|
..Default::default()
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_3() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("{np:>4}"),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: Some(Align::Right),
|
|
width: Some(4),
|
|
precision: None,
|
|
..Default::default()
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_4() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("{np:_>4}"),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: Some(Align::Right),
|
|
width: Some(4),
|
|
..Default::default()
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_5() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("__{np:_>4}@@"),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "__".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: Some(Align::Right),
|
|
width: Some(4),
|
|
precision: None,
|
|
suffix: "@@".into(),
|
|
prefix_len: 2,
|
|
suffix_len: 2,
|
|
..Default::default()
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_6() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("__{nm:<3}@@---{np:_>4}**"),
|
|
vec![
|
|
format::FormatStringPlaceholderData {
|
|
prefix: "__".into(),
|
|
placeholder: Some(Placeholder::NumberMinus),
|
|
alignment_spec: Some(Align::Left),
|
|
width: Some(3),
|
|
precision: None,
|
|
suffix: "@@---{np:_>4}**".into(),
|
|
prefix_len: 2,
|
|
suffix_len: 15,
|
|
..Default::default()
|
|
},
|
|
format::FormatStringPlaceholderData {
|
|
prefix: "@@---".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: Some(Align::Right),
|
|
width: Some(4),
|
|
precision: None,
|
|
suffix: "**".into(),
|
|
prefix_len: 5,
|
|
suffix_len: 2,
|
|
..Default::default()
|
|
}
|
|
]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_regex_7() {
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex("__@@---**",),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: "".into(),
|
|
placeholder: None,
|
|
alignment_spec: None,
|
|
width: None,
|
|
precision: None,
|
|
suffix: "__@@---**".into(),
|
|
prefix_len: 0,
|
|
suffix_len: 9,
|
|
..Default::default()
|
|
},]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_odd_width_one() {
|
|
assert_eq!(
|
|
format::parse_line_number_format("|{nm:<4}|", &LINE_NUMBERS_PLACEHOLDER_REGEX, true),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: format!("{ODD_PAD_CHAR}|").into(),
|
|
placeholder: Some(Placeholder::NumberMinus),
|
|
alignment_spec: Some(Align::Left),
|
|
width: Some(4),
|
|
precision: None,
|
|
suffix: "|".into(),
|
|
prefix_len: 2,
|
|
suffix_len: 1,
|
|
..Default::default()
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_odd_width_two() {
|
|
assert_eq!(
|
|
format::parse_line_number_format(
|
|
"|{nm:<4}+{np:<4}|",
|
|
&LINE_NUMBERS_PLACEHOLDER_REGEX,
|
|
true
|
|
),
|
|
vec![
|
|
format::FormatStringPlaceholderData {
|
|
prefix: format!("{ODD_PAD_CHAR}|").into(),
|
|
placeholder: Some(Placeholder::NumberMinus),
|
|
alignment_spec: Some(Align::Left),
|
|
width: Some(4),
|
|
precision: None,
|
|
suffix: "+{np:<4}|".into(),
|
|
prefix_len: 2,
|
|
suffix_len: 9,
|
|
..Default::default()
|
|
},
|
|
format::FormatStringPlaceholderData {
|
|
prefix: "+".into(),
|
|
placeholder: Some(Placeholder::NumberPlus),
|
|
alignment_spec: Some(Align::Left),
|
|
width: Some(4),
|
|
precision: None,
|
|
suffix: "|".into(),
|
|
prefix_len: 1,
|
|
suffix_len: 1,
|
|
..Default::default()
|
|
}
|
|
]
|
|
);
|
|
}
|
|
#[test]
|
|
fn test_line_number_format_odd_width_none() {
|
|
assert_eq!(
|
|
format::parse_line_number_format("|++|", &LINE_NUMBERS_PLACEHOLDER_REGEX, true),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: format!("{ODD_PAD_CHAR}").into(),
|
|
placeholder: None,
|
|
alignment_spec: None,
|
|
width: None,
|
|
precision: None,
|
|
suffix: "|++|".into(),
|
|
prefix_len: 1,
|
|
suffix_len: 4,
|
|
..Default::default()
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_format_long() {
|
|
let long = "line number format which is too large for SSO";
|
|
assert!(long.len() > std::mem::size_of::<smol_str::SmolStr>());
|
|
assert_eq!(
|
|
parse_line_number_format_with_default_regex(&format!("{long}{{nm}}{long}")),
|
|
vec![format::FormatStringPlaceholderData {
|
|
prefix: long.into(),
|
|
prefix_len: long.len(),
|
|
placeholder: Some(Placeholder::NumberMinus),
|
|
alignment_spec: None,
|
|
width: None,
|
|
precision: None,
|
|
suffix: long.into(),
|
|
suffix_len: long.len(),
|
|
..Default::default()
|
|
},]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_placeholder_width_one() {
|
|
let data = parse_line_number_format_with_default_regex("");
|
|
assert_eq!(data[0].width(0), (0, 0));
|
|
|
|
let data = parse_line_number_format_with_default_regex("");
|
|
assert_eq!(data[0].width(4), (0, 0));
|
|
|
|
let data = parse_line_number_format_with_default_regex("│+│");
|
|
assert_eq!(data[0].width(4), (0, 3));
|
|
|
|
let data = parse_line_number_format_with_default_regex("{np}");
|
|
assert_eq!(data[0].width(4), (4, 0));
|
|
|
|
let data = parse_line_number_format_with_default_regex("│{np}│");
|
|
assert_eq!(data[0].width(4), (5, 1));
|
|
|
|
let data = parse_line_number_format_with_default_regex("│{np:2}│");
|
|
assert_eq!(data[0].width(4), (5, 1));
|
|
|
|
let data = parse_line_number_format_with_default_regex("│{np:6}│");
|
|
assert_eq!(data[0].width(4), (7, 1));
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_number_placeholder_width_two() {
|
|
let data = parse_line_number_format_with_default_regex("│{nm}│{np}│");
|
|
assert_eq!(data[0].width(1), (2, 6));
|
|
assert_eq!(data[1].width(1), (2, 1));
|
|
|
|
let data = parse_line_number_format_with_default_regex("│{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_with_default_regex("│{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() {
|
|
use crate::features::side_by_side::ansifill;
|
|
let w = ansifill::UseFullPanelWidth(false);
|
|
let format = MinusPlus::new("".into(), "".into());
|
|
let mut data = LineNumbersData::from_format_strings(&format, w.clone());
|
|
data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
|
|
assert_eq!(data.formatted_width(), MinusPlus::new(0, 0));
|
|
|
|
let format = MinusPlus::new("│".into(), "│+│".into());
|
|
let mut data = LineNumbersData::from_format_strings(&format, w.clone());
|
|
|
|
data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
|
|
assert_eq!(data.formatted_width(), MinusPlus::new(1, 3));
|
|
|
|
let format = MinusPlus::new("│{nm:^3}│".into(), "│{np:^3}│".into());
|
|
let mut data = LineNumbersData::from_format_strings(&format, w.clone());
|
|
|
|
data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
|
|
assert_eq!(data.formatted_width(), MinusPlus::new(8, 8));
|
|
|
|
let format = MinusPlus::new("│{nm:^3}│ │{np:<12}│ │{nm}│".into(), "".into());
|
|
let mut data = LineNumbersData::from_format_strings(&format, w.clone());
|
|
|
|
data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
|
|
assert_eq!(data.formatted_width(), MinusPlus::new(32, 0));
|
|
|
|
let format = MinusPlus::new("│{np:^3}│ │{nm:<12}│ │{np}│".into(), "".into());
|
|
let mut data = LineNumbersData::from_format_strings(&format, w);
|
|
|
|
data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into());
|
|
assert_eq!(data.formatted_width(), MinusPlus::new(32, 0));
|
|
}
|
|
|
|
fn _get_capture<'a>(i: usize, j: usize, caps: &'a [Captures]) -> &'a str {
|
|
caps[i].get(j).map_or("", |m| m.as_str())
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_minus_lines() {
|
|
DeltaTest::with_args(&[
|
|
"--line-numbers",
|
|
"--line-numbers-left-format",
|
|
"{nm:^4}⋮",
|
|
"--line-numbers-right-format",
|
|
"{np:^4}│",
|
|
"--line-numbers-left-style",
|
|
"0 1",
|
|
"--line-numbers-minus-style",
|
|
"0 2",
|
|
"--line-numbers-right-style",
|
|
"0 3",
|
|
"--line-numbers-plus-style",
|
|
"0 4",
|
|
])
|
|
.with_input(TWO_MINUS_LINES_DIFF)
|
|
.expect_after_header(
|
|
r#"
|
|
#indent_mark
|
|
1 ⋮ │a = 1
|
|
2 ⋮ │b = 23456"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_plus_lines() {
|
|
DeltaTest::with_args(&[
|
|
"--line-numbers",
|
|
"--line-numbers-left-format",
|
|
"{nm:^4}⋮",
|
|
"--line-numbers-right-format",
|
|
"{np:^4}│",
|
|
"--line-numbers-left-style",
|
|
"0 1",
|
|
"--line-numbers-minus-style",
|
|
"0 2",
|
|
"--line-numbers-right-style",
|
|
"0 3",
|
|
"--line-numbers-plus-style",
|
|
"0 4",
|
|
])
|
|
.with_input(TWO_PLUS_LINES_DIFF)
|
|
.expect_after_header(
|
|
r#"
|
|
#indent_mark
|
|
⋮ 1 │a = 1
|
|
⋮ 2 │b = 234567"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_one_minus_one_plus_line() {
|
|
let config = make_config_from_args(&[
|
|
"--line-numbers",
|
|
"--line-numbers-left-format",
|
|
"{nm:^4}⋮",
|
|
"--line-numbers-right-format",
|
|
"{np:^4}│",
|
|
"--line-numbers-left-style",
|
|
"0 1",
|
|
"--line-numbers-minus-style",
|
|
"0 2",
|
|
"--line-numbers-right-style",
|
|
"0 3",
|
|
"--line-numbers-plus-style",
|
|
"0 4",
|
|
]);
|
|
let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config);
|
|
let output = strip_ansi_codes(&output);
|
|
let mut lines = output.lines().skip(crate::config::HEADER_LEN);
|
|
assert_eq!(lines.next().unwrap(), " 1 ⋮ 1 │a = 1");
|
|
assert_eq!(lines.next().unwrap(), " 2 ⋮ │b = 2");
|
|
assert_eq!(lines.next().unwrap(), " ⋮ 2 │bb = 2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_repeated_placeholder() {
|
|
let config = make_config_from_args(&[
|
|
"--line-numbers",
|
|
"--line-numbers-left-format",
|
|
"{nm:^4} {nm:^4}⋮",
|
|
"--line-numbers-right-format",
|
|
"{np:^4}│",
|
|
"--line-numbers-left-style",
|
|
"0 1",
|
|
"--line-numbers-minus-style",
|
|
"0 2",
|
|
"--line-numbers-right-style",
|
|
"0 3",
|
|
"--line-numbers-plus-style",
|
|
"0 4",
|
|
]);
|
|
let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config);
|
|
let output = strip_ansi_codes(&output);
|
|
let mut lines = output.lines().skip(crate::config::HEADER_LEN);
|
|
assert_eq!(lines.next().unwrap(), " 1 1 ⋮ 1 │a = 1");
|
|
assert_eq!(lines.next().unwrap(), " 2 2 ⋮ │b = 2");
|
|
assert_eq!(lines.next().unwrap(), " ⋮ 2 │bb = 2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_five_digit_line_number() {
|
|
let config = make_config_from_args(&["--line-numbers"]);
|
|
let output = run_delta(FIVE_DIGIT_LINE_NUMBER_DIFF, &config);
|
|
let output = strip_ansi_codes(&output);
|
|
let mut lines = output.lines().skip(crate::config::HEADER_LEN);
|
|
assert_eq!(lines.next().unwrap(), "10000⋮10000│a = 1");
|
|
assert_eq!(lines.next().unwrap(), "10001⋮ │b = 2");
|
|
assert_eq!(lines.next().unwrap(), " ⋮10001│bb = 2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_unequal_digit_line_number() {
|
|
let config = make_config_from_args(&["--line-numbers"]);
|
|
let output = run_delta(UNEQUAL_DIGIT_DIFF, &config);
|
|
let output = strip_ansi_codes(&output);
|
|
let mut lines = output.lines().skip(crate::config::HEADER_LEN);
|
|
assert_eq!(lines.next().unwrap(), "10000⋮ 9999│a = 1");
|
|
assert_eq!(lines.next().unwrap(), "10001⋮ │b = 2");
|
|
assert_eq!(lines.next().unwrap(), " ⋮10000│bb = 2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_color_only() {
|
|
let config = make_config_from_args(&["--line-numbers", "--color-only"]);
|
|
let output = run_delta(TWO_MINUS_LINES_DIFF, &config);
|
|
let mut lines = output.lines().skip(5);
|
|
let (line_1, line_2) = (lines.next().unwrap(), lines.next().unwrap());
|
|
assert_eq!(strip_ansi_codes(line_1), " 1 ⋮ │-a = 1");
|
|
assert_eq!(strip_ansi_codes(line_2), " 2 ⋮ │-b = 23456");
|
|
}
|
|
|
|
#[test]
|
|
fn test_hunk_header_style_is_omit() {
|
|
let config = make_config_from_args(&["--line-numbers", "--hunk-header-style", "omit"]);
|
|
let output = run_delta(TWO_LINE_DIFFS, &config);
|
|
let output = strip_ansi_codes(&output);
|
|
let mut lines = output.lines().skip(4);
|
|
assert_eq!(lines.next().unwrap(), " 1 ⋮ 1 │a = 1");
|
|
assert_eq!(lines.next().unwrap(), " 2 ⋮ │b = 2");
|
|
assert_eq!(lines.next().unwrap(), " ⋮ 2 │bb = 2");
|
|
assert_eq!(lines.next().unwrap(), "");
|
|
assert_eq!(lines.next().unwrap(), " 499⋮ 499│a = 3");
|
|
assert_eq!(lines.next().unwrap(), " 500⋮ │b = 4");
|
|
assert_eq!(lines.next().unwrap(), " ⋮ 500│bb = 4");
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_numbers_continue_correctly() {
|
|
DeltaTest::with_args(&["--side-by-side", "--width", "44", "--line-fill-method=ansi"])
|
|
.with_input(DIFF_PLUS_MINUS_WITH_1_CONTEXT_DIFF)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = left side │ 2 │a = right side
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_line_numbers_continue_correctly_after_wrapping() {
|
|
DeltaTest::with_args(&[
|
|
"--side-by-side",
|
|
"--width",
|
|
"32",
|
|
"--line-fill-method=ansi",
|
|
"--wrap-left-symbol",
|
|
"@",
|
|
"--wrap-right-symbol",
|
|
"@",
|
|
"--wrap-right-prefix-symbol",
|
|
">",
|
|
])
|
|
.with_input(DIFF_PLUS_MINUS_WITH_1_CONTEXT_DIFF)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = left @│ 2 │a = right@
|
|
│ │side │ │ side
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
|
|
let cfg = &[
|
|
"--side-by-side",
|
|
"--width",
|
|
"42",
|
|
"--line-fill-method=ansi",
|
|
"--wrap-left-symbol",
|
|
"@",
|
|
"--wrap-right-symbol",
|
|
"@",
|
|
"--wrap-right-prefix-symbol",
|
|
">",
|
|
];
|
|
|
|
DeltaTest::with_args(cfg)
|
|
.with_input(DIFF_WITH_LONGER_MINUS_1_CONTEXT)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = one side │ 2 │a = one longer@
|
|
│ │ │ │ > side
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
|
|
DeltaTest::with_args(cfg)
|
|
.with_input(DIFF_WITH_LONGER_PLUS_1_CONTEXT)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = one longer@│ 2 │a = one side
|
|
│ │ > side│ │
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
|
|
DeltaTest::with_args(cfg)
|
|
.with_input(DIFF_MISMATCH_LONGER_MINUS_1_CONTEXT)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = left side @│ │
|
|
│ │which is longer│ │
|
|
│ │ │ 2 │a = other one
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
|
|
DeltaTest::with_args(cfg)
|
|
.with_input(DIFF_MISMATCH_LONGER_PLUS_1_CONTEXT)
|
|
.expect_after_header(
|
|
r#"
|
|
│ 1 │abc │ 1 │abc
|
|
│ 2 │a = other one │ │
|
|
│ │ │ 2 │a = right side@
|
|
│ │ │ │ which is long@
|
|
│ │ │ │er
|
|
│ 3 │xyz │ 3 │xyz"#,
|
|
);
|
|
}
|
|
|
|
pub const TWO_MINUS_LINES_DIFF: &str = "\
|
|
diff --git i/a.py w/a.py
|
|
index 223ca50..e69de29 100644
|
|
--- i/a.py
|
|
+++ w/a.py
|
|
@@ -1,2 +0,0 @@
|
|
-a = 1
|
|
-b = 23456
|
|
";
|
|
|
|
pub const TWO_PLUS_LINES_DIFF: &str = "\
|
|
diff --git c/a.py i/a.py
|
|
new file mode 100644
|
|
index 0000000..223ca50
|
|
--- /dev/null
|
|
+++ i/a.py
|
|
@@ -0,0 +1,2 @@
|
|
+a = 1
|
|
+b = 234567
|
|
";
|
|
|
|
pub const ONE_MINUS_ONE_PLUS_LINE_DIFF: &str = "\
|
|
diff --git i/a.py w/a.py
|
|
index 223ca50..367a6f6 100644
|
|
--- i/a.py
|
|
+++ w/a.py
|
|
@@ -1,2 +1,2 @@
|
|
a = 1
|
|
-b = 2
|
|
+bb = 2
|
|
";
|
|
|
|
const TWO_LINE_DIFFS: &str = "\
|
|
diff --git i/a.py w/a.py
|
|
index 223ca50..367a6f6 100644
|
|
--- i/a.py
|
|
+++ w/a.py
|
|
@@ -1,2 +1,2 @@
|
|
a = 1
|
|
-b = 2
|
|
+bb = 2
|
|
@@ -499,2 +499,2 @@
|
|
a = 3
|
|
-b = 4
|
|
+bb = 4
|
|
";
|
|
|
|
const FIVE_DIGIT_LINE_NUMBER_DIFF: &str = "\
|
|
diff --git i/a.py w/a.py
|
|
index 223ca50..367a6f6 100644
|
|
--- i/a.py
|
|
+++ w/a.py
|
|
@@ -10000,2 +10000,2 @@
|
|
a = 1
|
|
-b = 2
|
|
+bb = 2
|
|
";
|
|
|
|
const UNEQUAL_DIGIT_DIFF: &str = "\
|
|
diff --git i/a.py w/a.py
|
|
index 223ca50..367a6f6 100644
|
|
--- i/a.py
|
|
+++ w/a.py
|
|
@@ -10000,2 +9999,2 @@
|
|
a = 1
|
|
-b = 2
|
|
+bb = 2
|
|
";
|
|
|
|
const DIFF_PLUS_MINUS_WITH_1_CONTEXT_DIFF: &str = "\
|
|
--- a/a.py
|
|
+++ b/b.py
|
|
@@ -1,3 +1,3 @@
|
|
abc
|
|
-a = left side
|
|
+a = right side
|
|
xyz";
|
|
|
|
const DIFF_WITH_LONGER_MINUS_1_CONTEXT: &str = "\
|
|
--- a/a.py
|
|
+++ b/b.py
|
|
@@ -1,3 +1,3 @@
|
|
abc
|
|
-a = one side
|
|
+a = one longer side
|
|
xyz";
|
|
|
|
const DIFF_WITH_LONGER_PLUS_1_CONTEXT: &str = "\
|
|
--- a/a.py
|
|
+++ b/b.py
|
|
@@ -1,3 +1,3 @@
|
|
abc
|
|
-a = one longer side
|
|
+a = one side
|
|
xyz";
|
|
|
|
const DIFF_MISMATCH_LONGER_MINUS_1_CONTEXT: &str = "\
|
|
--- a/a.py
|
|
+++ b/b.py
|
|
@@ -1,3 +1,3 @@
|
|
abc
|
|
-a = left side which is longer
|
|
+a = other one
|
|
xyz";
|
|
|
|
const DIFF_MISMATCH_LONGER_PLUS_1_CONTEXT: &str = "\
|
|
--- a/a.py
|
|
+++ b/b.py
|
|
@@ -1,3 +1,3 @@
|
|
abc
|
|
-a = other one
|
|
+a = right side which is longer
|
|
xyz";
|
|
|
|
pub const TWO_MINUS_LINES_UNICODE_DIFF: &str = "\
|
|
diff --git a/a.py b/a.py
|
|
index 8b0d958..e69de29 100644
|
|
--- a/a.txt
|
|
+++ b/b.txt
|
|
@@ -1,1 +0,0 @@
|
|
-一二三
|
|
";
|
|
}
|