Files
delta-mirror/src/features/line_numbers.rs
2024-02-19 12:56:22 -05:00

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 @@
-一二三
";
}