use syntect::highlighting::Style as SyntectStyle;
use unicode_segmentation::UnicodeSegmentation;
use crate::config::INLINE_SYMBOL_WIDTH_1;
use crate::config::Config;
use crate::delta::DiffType;
use crate::delta::State;
use crate::features::line_numbers::{self, SideBySideLineWidth};
use crate::features::side_by_side::{available_line_width, line_is_too_long, Left, Right};
use crate::minusplus::*;
use crate::paint::LineSections;
use crate::style::Style;
#[derive(Clone, Debug)]
pub struct WrapConfig {
pub left_symbol: String,
pub right_symbol: String,
pub right_prefix_symbol: String,
pub use_wrap_right_permille: usize,
pub max_lines: usize,
pub inline_hint_syntect_style: SyntectStyle,
}
#[derive(PartialEq)]
enum Stop {
StackEmpty,
LineLimit,
}
pub fn wrap_line<'a, I, S>(
config: &'a Config,
line: I,
line_width: usize,
fill_style: &S,
inline_hint_style: &Option<S>,
) -> Vec<LineSections<'a, S>>
where
I: IntoIterator<Item = (S, &'a str)> + std::fmt::Debug,
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
S: Copy + Default + std::fmt::Debug,
{
let mut result = Vec::new();
let wrap_config = &config.wrap_config;
struct CurrLine<'a, S: Default> {
line_segments: LineSections<'a, S>,
len: usize,
}
impl<'a, S: Default> CurrLine<'a, S> {
fn reset() -> Self {
CurrLine {
line_segments: Vec::new(),
len: 0,
}
}
fn push_and_set_len(&mut self, text: (S, &'a str), len: usize) {
self.line_segments.push(text);
self.len = len;
}
fn has_text(&self) -> bool {
self.len > 0
}
fn text_len(&self) -> usize {
self.len
}
}
let mut curr_line = CurrLine::reset();
let symbol_style = match inline_hint_style {
Some(style) => *style,
None => *fill_style,
};
let mut stack = line.into_iter().rev().collect::<Vec<_>>();
let max_lines = if line_width <= INLINE_SYMBOL_WIDTH_1 {
1
} else {
wrap_config.max_lines
};
let line_limit_reached = |result: &Vec<_>| max_lines > 0 && result.len() + 1 >= max_lines;
let stop = loop {
if stack.is_empty() {
break Stop::StackEmpty;
} else if line_limit_reached(&result) {
break Stop::LineLimit;
}
let (style, text, graphemes) = stack
.pop()
.map(|(style, text)| (style, text, text.grapheme_indices(true).collect::<Vec<_>>()))
.unwrap();
let new_len = curr_line.len + graphemes.len();
let must_split = if new_len < line_width {
curr_line.push_and_set_len((style, text), new_len);
false
} else if new_len == line_width {
match stack.last() {
None => {
curr_line.push_and_set_len((style, text), new_len);
false
}
#[allow(clippy::identity_op)]
Some((next_style, nl)) if stack.len() == 1 && *nl == "\n" => {
curr_line.push_and_set_len((style, text), new_len);
curr_line.push_and_set_len((*next_style, *nl), new_len + 0);
stack.pop();
false
}
_ => true,
}
} else if new_len == line_width + 1 && stack.is_empty() {
if text.ends_with('\n') {
curr_line.push_and_set_len((style, text), new_len - 1);
false
} else {
true
}
} else {
true
};
if must_split {
let grapheme_split_pos = graphemes.len() - (new_len - line_width) - 1;
let mut line_segments = curr_line.line_segments;
let next_line = if grapheme_split_pos == 0 {
text
} else {
let byte_split_pos = graphemes[grapheme_split_pos].0;
let this_line = &text[..byte_split_pos];
line_segments.push((style, this_line));
&text[byte_split_pos..]
};
stack.push((style, next_line));
line_segments.push((symbol_style, &wrap_config.left_symbol));
result.push(line_segments);
curr_line = CurrLine::reset();
}
};
if result.len() == 1 && curr_line.has_text() {
let current_permille = (curr_line.text_len() * 1000) / line_width;
let pad_len = line_width.saturating_sub(curr_line.text_len());
if wrap_config.use_wrap_right_permille > current_permille && pad_len > 0 {
const SPACES: &str = " ";
match result.last_mut() {
Some(ref mut vec) if !vec.is_empty() => {
vec.last_mut().unwrap().1 = &wrap_config.right_symbol
}
_ => unreachable!("wrap result must not be empty"),
}
let mut right_aligned_line = Vec::new();
for _ in 0..(pad_len / SPACES.len()) {
right_aligned_line.push((*fill_style, SPACES));
}
match pad_len % SPACES.len() {
0 => (),
n => right_aligned_line.push((*fill_style, &SPACES[0..n])),
}
right_aligned_line.push((symbol_style, &wrap_config.right_prefix_symbol));
right_aligned_line.extend(curr_line.line_segments.into_iter());
curr_line.line_segments = right_aligned_line;
}
}
if curr_line.has_text() {
result.push(curr_line.line_segments);
}
if stop == Stop::LineLimit && result.len() != max_lines {
result.push(Vec::new());
}
if !stack.is_empty() {
if result.is_empty() {
result.push(Vec::new());
}
result.last_mut().unwrap().extend(stack.into_iter().rev());
}
result
}
fn wrap_if_too_long<'a, S>(
config: &'a Config,
wrapped: &mut Vec<LineSections<'a, S>>,
input_vec: LineSections<'a, S>,
must_wrap: bool,
line_width: usize,
fill_style: &S,
inline_hint_style: &Option<S>,
) -> (usize, usize)
where
S: Copy + Default + std::fmt::Debug,
{
let size_prev = wrapped.len();
if must_wrap {
wrapped.append(&mut wrap_line(
config,
input_vec.into_iter(),
line_width,
fill_style,
inline_hint_style,
));
} else {
wrapped.push(input_vec.to_vec());
}
(size_prev, wrapped.len())
}
#[allow(clippy::comparison_chain, clippy::type_complexity)]
pub fn wrap_minusplus_block<'c: 'a, 'a>(
config: &'c Config,
syntax: MinusPlus<Vec<LineSections<'a, SyntectStyle>>>,
diff: MinusPlus<Vec<LineSections<'a, Style>>>,
alignment: &[(Option<usize>, Option<usize>)],
line_width: &SideBySideLineWidth,
wrapinfo: &'a MinusPlus<Vec<bool>>,
) -> (
Vec<(Option<usize>, Option<usize>)>,
MinusPlus<Vec<State>>,
MinusPlus<Vec<LineSections<'a, SyntectStyle>>>,
MinusPlus<Vec<LineSections<'a, Style>>>,
) {
let mut new_alignment = Vec::new();
let mut new_states = MinusPlus::<Vec<State>>::default();
let mut new_wrapped_syntax = MinusPlus::default();
let mut new_wrapped_diff = MinusPlus::default();
let mut syntax = MinusPlus::new(syntax.minus.into_iter(), syntax.plus.into_iter());
let mut diff = MinusPlus::new(diff.minus.into_iter(), diff.plus.into_iter());
let mut wrapinfo = MinusPlus::new(wrapinfo[Left].iter(), wrapinfo[Right].iter());
let fill_style = MinusPlus::new(&config.minus_style, &config.plus_style);
#[allow(clippy::too_many_arguments)]
pub fn wrap_syntax_and_diff<'a, ItSyn, ItDiff, ItWrap>(
config: &'a Config,
wrapped_syntax: &mut Vec<LineSections<'a, SyntectStyle>>,
wrapped_diff: &mut Vec<LineSections<'a, Style>>,
syntax_iter: &mut ItSyn,
diff_iter: &mut ItDiff,
wrapinfo_iter: &mut ItWrap,
line_width: usize,
fill_style: &Style,
errhint: &'a str,
) -> (usize, usize)
where
ItSyn: Iterator<Item = LineSections<'a, SyntectStyle>>,
ItDiff: Iterator<Item = LineSections<'a, Style>>,
ItWrap: Iterator<Item = &'a bool>,
{
let must_wrap = *wrapinfo_iter
.next()
.unwrap_or_else(|| panic!("bad wrap info {}", errhint));
let (start, extended_to) = wrap_if_too_long(
config,
wrapped_syntax,
syntax_iter
.next()
.unwrap_or_else(|| panic!("bad syntax alignment {}", errhint)),
must_wrap,
line_width,
&config.null_syntect_style,
&Some(config.wrap_config.inline_hint_syntect_style),
);
let inline_hint_style = if config
.inline_hint_style
.ansi_term_style
.background
.is_some()
{
Some(config.inline_hint_style)
} else {
None
};
let (start2, extended_to2) = wrap_if_too_long(
config,
wrapped_diff,
diff_iter
.next()
.unwrap_or_else(|| panic!("bad diff alignment {}", errhint)),
must_wrap,
line_width,
fill_style,
&inline_hint_style,
);
assert_eq!(
(start, extended_to),
(start2, extended_to2),
"syntax and diff wrapping differs {}",
errhint
);
(start, extended_to)
}
macro_rules! wrap_and_assert {
($side:tt, $errhint:tt, $have:tt, $expected:tt) => {{
assert_eq!(*$have, $expected, "bad alignment index {}", $errhint);
$expected += 1;
wrap_syntax_and_diff(
&config,
&mut new_wrapped_syntax[$side],
&mut new_wrapped_diff[$side],
&mut syntax[$side],
&mut diff[$side],
&mut wrapinfo[$side],
line_width[$side],
&fill_style[$side],
$errhint,
)
}};
}
let mut m_expected = 0;
let mut p_expected = 0;
for (minus, plus) in alignment {
let (minus_extended, plus_extended) = match (minus, plus) {
(Some(m), None) => {
let (minus_start, extended_to) = wrap_and_assert!(Left, "[*l*] (-)", m, m_expected);
for i in minus_start..extended_to {
new_alignment.push((Some(i), None));
}
(extended_to - minus_start, 0)
}
(None, Some(p)) => {
let (plus_start, extended_to) = wrap_and_assert!(Right, "(-) [*r*]", p, p_expected);
for i in plus_start..extended_to {
new_alignment.push((None, Some(i)));
}
(0, extended_to - plus_start)
}
(Some(m), Some(p)) => {
let (minus_start, m_extended_to) =
wrap_and_assert!(Left, "[*l*] (r)", m, m_expected);
let (plus_start, p_extended_to) =
wrap_and_assert!(Right, "(l) [*r*]", p, p_expected);
for (new_m, new_p) in (minus_start..m_extended_to).zip(plus_start..p_extended_to) {
new_alignment.push((Some(new_m), Some(new_p)));
}
let minus_extended = m_extended_to - minus_start;
let plus_extended = p_extended_to - plus_start;
let plus_minus = (minus_extended as isize) - (plus_extended as isize);
if plus_minus > 0 {
for m in (m_extended_to as isize - plus_minus) as usize..m_extended_to {
new_alignment.push((Some(m), None));
}
} else if plus_minus < 0 {
for p in (p_extended_to as isize + plus_minus) as usize..p_extended_to {
new_alignment.push((None, Some(p)));
}
}
(minus_extended, plus_extended)
}
_ => unreachable!("None-None alignment"),
};
if minus_extended > 0 {
new_states[Left].push(State::HunkMinus(DiffType::Unified, None));
for _ in 1..minus_extended {
new_states[Left].push(State::HunkMinusWrapped);
}
}
if plus_extended > 0 {
new_states[Right].push(State::HunkPlus(DiffType::Unified, None));
for _ in 1..plus_extended {
new_states[Right].push(State::HunkPlusWrapped);
}
}
}
(
new_alignment,
new_states,
new_wrapped_syntax,
new_wrapped_diff,
)
}
#[allow(clippy::comparison_chain, clippy::type_complexity)]
pub fn wrap_zero_block<'c: 'a, 'a>(
config: &'c Config,
raw_line: &str,
mut states: Vec<State>,
syntax_style_sections: Vec<LineSections<'a, SyntectStyle>>,
diff_style_sections: Vec<LineSections<'a, Style>>,
line_numbers_data: &Option<&mut line_numbers::LineNumbersData>,
) -> (
Vec<State>,
Vec<LineSections<'a, SyntectStyle>>,
Vec<LineSections<'a, Style>>,
) {
let line_width = if let Some(line_numbers_data) = line_numbers_data {
let width = available_line_width(config, line_numbers_data);
std::cmp::min(width[Left], width[Right])
} else {
std::cmp::min(
config.side_by_side_data[Left].width,
config.side_by_side_data[Right].width,
)
};
debug_assert_eq!(diff_style_sections.len(), 1);
let should_wrap = line_is_too_long(raw_line, line_width);
if should_wrap {
let syntax_style = wrap_line(
config,
syntax_style_sections.into_iter().flatten(),
line_width,
&SyntectStyle::default(),
&Some(config.wrap_config.inline_hint_syntect_style),
);
let inline_hint_style = if config
.inline_hint_style
.ansi_term_style
.background
.is_some()
{
Some(config.inline_hint_style)
} else {
None
};
let diff_style = wrap_line(
config,
diff_style_sections.into_iter().flatten(),
line_width,
&Style {
is_syntax_highlighted: true,
..config.null_style
},
&inline_hint_style,
);
states.resize_with(syntax_style.len(), || State::HunkZeroWrapped);
(states, syntax_style, diff_style)
} else {
(states, syntax_style_sections, diff_style_sections)
}
}
#[cfg(test)]
mod tests {
use lazy_static::lazy_static;
use syntect::highlighting::Style as SyntectStyle;
use super::wrap_line;
use super::WrapConfig;
use crate::ansi::strip_ansi_codes;
use crate::config::Config;
use crate::paint::LineSections;
use crate::style::Style;
use crate::tests::integration_test_utils::{make_config_from_args, run_delta};
lazy_static! {
static ref S1: Style = Style {
is_syntax_highlighted: true,
..Default::default()
};
}
lazy_static! {
static ref S2: Style = Style {
is_emph: true,
..Default::default()
};
}
lazy_static! {
static ref SY: SyntectStyle = SyntectStyle::default();
}
lazy_static! {
static ref SD: Style = Style::default();
}
const W: &str = &"+"; const WR: &str = &"<"; const RA: &str = &">";
lazy_static! {
static ref WRAP_DEFAULT_ARGS: Vec<&'static str> = vec![
"--wrap-left-symbol",
W,
"--wrap-right-symbol",
WR,
"--wrap-right-prefix-symbol",
RA,
"--wrap-max-lines",
"4",
"--wrap-right-percent",
"37.0%",
];
}
lazy_static! {
static ref TEST_WRAP_CFG: WrapConfig =
make_config_from_args(&WRAP_DEFAULT_ARGS).wrap_config;
}
fn default_wrap_cfg_plus<'a>(args: &[&'a str]) -> Vec<&'a str> {
let mut result = WRAP_DEFAULT_ARGS.clone();
result.extend_from_slice(args);
result
}
fn mk_wrap_cfg(wrap_cfg: &WrapConfig) -> Config {
let mut cfg: Config = Config::from(make_config_from_args(&[]));
cfg.wrap_config = wrap_cfg.clone();
cfg
}
fn wrap_test<'a, I, S>(cfg: &'a Config, line: I, line_width: usize) -> Vec<LineSections<'a, S>>
where
I: IntoIterator<Item = (S, &'a str)> + std::fmt::Debug,
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
S: Copy + Default + std::fmt::Debug,
{
wrap_line(&cfg, line, line_width, &S::default(), &None)
}
#[test]
fn test_wrap_line_single() {
let cfg = mk_wrap_cfg(&TEST_WRAP_CFG);
{
let line = vec![(*SY, "0")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*SY, "0")]]);
}
{
let line = vec![(*S1, "012"), (*S2, "34")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*S1, "012"), (*S2, "34")]]);
}
{
let line = vec![(*S1, "012"), (*S2, "345")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*S1, "012"), (*S2, "345")]]);
}
{
let line = vec![(*S1, "")];
let lines = wrap_test(&cfg, line, 6);
assert!(lines.is_empty());
}
{
let line = vec![(*S1, ""), (*S2, "0")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*S1, ""), (*S2, "0")]]);
}
{
let line = vec![(*S1, "0"), (*S2, "")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*S1, "0"), (*S2, "")]]);
}
{
let line = vec![
(*S1, "0"),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, ""),
];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(
lines,
vec![vec![
(*S1, "0"),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, ""),
(*S1, ""),
(*S2, "")
]]
);
}
}
#[test]
fn test_wrap_line_align_right_1() {
let cfg = mk_wrap_cfg(&TEST_WRAP_CFG);
let line = vec![(*S1, "0123456789ab")];
let lines = wrap_test(&cfg, line, 11);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0].last().unwrap().1, WR);
assert_eq!(lines[1], [(*SD, " "), (*SD, ">"), (*S1, "ab")]);
}
#[test]
fn test_wrap_line_align_right_2() {
let line = vec![(*S1, "012"), (*S2, "3456")];
{
let cfg = mk_wrap_cfg(&TEST_WRAP_CFG);
let lines = wrap_test(&cfg, line.clone(), 6);
assert_eq!(
lines,
vec![
vec![(*S1, "012"), (*S2, "34"), (*SD, WR)],
vec![(*SD, " "), (*SD, RA), (*S2, "56")]
]
);
}
{
let mut no_align_right = TEST_WRAP_CFG.clone();
no_align_right.use_wrap_right_permille = 1; let cfg_no_align_right = mk_wrap_cfg(&no_align_right);
let lines = wrap_test(&cfg_no_align_right, line, 6);
assert_eq!(
lines,
vec![vec![(*S1, "012"), (*S2, "34"), (*SD, W)], vec![(*S2, "56")]]
);
}
}
#[test]
fn test_wrap_line_newlines<'a>() {
fn mk_input(len: usize) -> LineSections<'static, Style> {
const IN: &str = "0123456789abcdefZ";
let v = &[*S1, *S2];
let s1s2 = v.iter().cycle();
let text: Vec<_> = IN.matches(|_| true).take(len + 1).collect();
s1s2.zip(text.iter())
.map(|(style, text)| (style.clone(), *text))
.collect()
}
fn mk_input_nl(len: usize) -> LineSections<'static, Style> {
const NL: &str = "\n";
let mut line = mk_input(len);
line.push((*S2, NL));
line
}
fn mk_expected<'a>(
vec: &LineSections<'a, Style>,
from: usize,
to: usize,
append: Option<(Style, &'a str)>,
) -> LineSections<'a, Style> {
let mut result: Vec<_> = vec[from..to].iter().cloned().collect();
if let Some(val) = append {
result.push(val);
}
result
}
let cfg = mk_wrap_cfg(&TEST_WRAP_CFG);
{
let line = vec![(*S1, "012"), (*S2, "345\n")];
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![vec![(*S1, "012"), (*S2, "345\n")]]);
}
{
for i in 0..=5 {
let line = mk_input(i);
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![mk_input(i)]);
let line = mk_input_nl(i);
let lines = wrap_test(&cfg, line, 6);
assert_eq!(lines, vec![mk_input_nl(i)]);
}
}
{
let line = mk_input_nl(9);
let lines = wrap_test(&cfg, line, 3);
let expected = mk_input_nl(9);
let line1 = mk_expected(&expected, 0, 2, Some((*SD, &W)));
let line2 = mk_expected(&expected, 2, 4, Some((*SD, &W)));
let line3 = mk_expected(&expected, 4, 6, Some((*SD, &W)));
let line4 = mk_expected(&expected, 6, 8, Some((*SD, &W)));
let line5 = mk_expected(&expected, 8, 11, None);
assert_eq!(lines, vec![line1, line2, line3, line4, line5]);
}
{
let line = mk_input_nl(10);
let lines = wrap_test(&cfg, line, 3);
let expected = mk_input_nl(10);
let line1 = mk_expected(&expected, 0, 2, Some((*SD, &W)));
let line2 = mk_expected(&expected, 2, 4, Some((*SD, &W)));
let line3 = mk_expected(&expected, 4, 6, Some((*SD, &W)));
let line4 = mk_expected(&expected, 6, 8, Some((*SD, &W)));
let line5 = mk_expected(&expected, 8, 11, Some((*S2, "\n")));
assert_eq!(lines, vec![line1, line2, line3, line4, line5]);
}
{
let line = vec![(*S1, "abc"), (*S2, "01230123012301230123"), (*S1, "ZZZZZ")];
let wcfg1 = mk_wrap_cfg(&WrapConfig {
max_lines: 1,
..TEST_WRAP_CFG.clone()
});
let wcfg2 = mk_wrap_cfg(&WrapConfig {
max_lines: 2,
..TEST_WRAP_CFG.clone()
});
let wcfg3 = mk_wrap_cfg(&WrapConfig {
max_lines: 3,
..TEST_WRAP_CFG.clone()
});
let lines = wrap_line(&wcfg1, line.clone(), 4, &Style::default(), &None);
assert_eq!(lines.len(), 1);
assert_eq!(lines.last().unwrap().last().unwrap().1, "ZZZZZ");
let lines = wrap_line(&wcfg2, line.clone(), 4, &Style::default(), &None);
assert_eq!(lines.len(), 2);
assert_eq!(lines.last().unwrap().last().unwrap().1, "ZZZZZ");
let lines = wrap_line(&wcfg3, line.clone(), 4, &Style::default(), &None);
assert_eq!(lines.len(), 3);
assert_eq!(lines.last().unwrap().last().unwrap().1, "ZZZZZ");
}
}
#[test]
fn test_wrap_line_unicode() {
let cfg = mk_wrap_cfg(&TEST_WRAP_CFG);
let line = vec![(*S1, "abc"), (*S2, "mnö̲"), (*S1, "xyz")];
let lines = wrap_test(&cfg, line, 4);
assert_eq!(
lines,
vec![
vec![(*S1, "abc"), (*SD, &W)],
vec![(*S2, "mnö̲"), (*SD, &W)],
vec![(*S1, "xyz")]
]
);
let line = vec![(*S1, "abc"), (*S2, "deநி"), (*S1, "ghij")];
let lines = wrap_test(&cfg, line, 4);
assert_eq!(
lines,
vec![
vec![(*S1, "abc"), (*SD, &W)],
vec![(*S2, "deநி"), (*SD, &W)],
vec![(*S1, "ghij")]
]
);
}
const HUNK_ZERO_DIFF: &str = "\
diff --git i/a.py w/a.py
index 223ca50..e69de29 100644
--- i/a.py
+++ w/a.py
@@ -4,3 +15,3 @@
abcdefghijklmnopqrstuvwxzy 0123456789 0123456789 0123456789 0123456789 0123456789
-a = 1
+a = 2
";
const HUNK_ZERO_LARGE_LINENUMBERS_DIFF: &str = "\
diff --git i/a.py w/a.py
index 223ca50..e69de29 100644
--- i/a.py
+++ w/a.py
@@ -10,3 +101999,3 @@
abcdefghijklmnopqrstuvwxzy 0123456789 0123456789 0123456789 0123456789 0123456789
-a = 1
+a = 2
";
const HUNK_MP_DIFF: &str = "\
diff --git i/a.py w/a.py
index 223ca50..e69de29 100644
--- i/a.py
+++ w/a.py
@@ -4,3 +15,3 @@
abcdefghijklmnopqrstuvwxzy 0123456789 0123456789 0123456789 0123456789 0123456789
-a = 0123456789 0123456789 0123456789 0123456789 0123456789
+b = 0123456789 0123456789 0123456789 0123456789 0123456789
";
const HUNK_ALIGN_DIFF_HEADER: &str = "--- a\n+++ b\n@@ -1,1 +1,1 @@\n";
const HUNK_ALIGN_DIFF_SHORT: &str = ".........1.........2....\n";
const HUNK_ALIGN_DIFF_LONG: &str =
".........1.........2.........3.........4.........5.........6\n";
#[test]
fn test_wrap_with_unequal_hunk_zero_width() {
let mut config = make_config_from_args(&default_wrap_cfg_plus(&[
"--side-by-side",
"--line-numbers-left-format",
"│L│",
"--line-numbers-right-format",
"│RRRR│",
"--width",
"40",
"--line-fill-method",
"spaces",
]));
config.truncation_symbol = ">".into();
let output = run_delta(HUNK_ZERO_DIFF, &config);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│L│abcdefghijklm+ │RRRR│abcdefghijklm+",
"│L│nopqrstuvwxzy+ │RRRR│nopqrstuvwxzy+",
"│L│ 0123456789 0+ │RRRR│ 0123456789 0+",
"│L│123456789 012+ │RRRR│123456789 012+",
"│L│3456789 01234567>│RRRR│3456789 01234>",
"│L│a = 1 │RRRR│a = 2 ",
];
assert_eq!(lines, expected);
}
#[test]
fn test_wrap_with_large_hunk_zero_line_numbers() {
let mut config = make_config_from_args(&default_wrap_cfg_plus(&[
"--side-by-side",
"--line-numbers-left-format",
"│LLL│",
"--line-numbers-right-format",
"│WW {nm} +- {np:2} WW│",
"--width",
"60",
"--line-fill-method",
"ansi",
]));
config.truncation_symbol = ">".into();
let output = run_delta(HUNK_ZERO_LARGE_LINENUMBERS_DIFF, &config);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│LLL│abcde+ │WW 10 +- 101999 WW│abcde+",
"│LLL│fghij+ │WW +- WW│fghij+",
"│LLL│klmno+ │WW +- WW│klmno+",
"│LLL│pqrst+ │WW +- WW│pqrst+",
"│LLL│uvwxzy 0123456789 012345>│WW +- WW│uvwxz>",
"│LLL│a = 1 │WW +- 102000 WW│a = 2",
];
assert_eq!(lines, expected);
}
#[test]
fn test_wrap_with_keep_markers() {
use crate::features::side_by_side::ansifill::ODD_PAD_CHAR;
let mut config = make_config_from_args(&default_wrap_cfg_plus(&[
"--side-by-side",
"--keep-plus-minus-markers",
"--width",
"45",
]));
config.truncation_symbol = ">".into();
let output = run_delta(HUNK_MP_DIFF, &config);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 4 │ abcdefghijklmn+ │ 15 │ abcdefghijklmn+",
"│ │ opqrstuvwxzy 0+ │ │ opqrstuvwxzy 0+",
"│ │ 123456789 0123+ │ │ 123456789 0123+",
"│ │ 456789 0123456+ │ │ 456789 0123456+",
"│ │ 789 0123456789> │ │ 789 0123456789>",
"│ 5 │-a = 0123456789+ │ 16 │+b = 0123456789+",
"│ │ 0123456789 01+ │ │ 0123456789 01+",
"│ │ 23456789 01234+ │ │ 23456789 01234+",
"│ │ 56789 01234567+ │ │ 56789 01234567+",
"│ │ 89 │ │ 89",
];
assert_eq!(lines, expected);
for line in lines {
assert_eq!(line.chars().nth(22), Some(ODD_PAD_CHAR));
}
}
#[test]
fn test_alignment_2_lines_vs_3_lines() {
let config =
make_config_from_args(&default_wrap_cfg_plus(&["--side-by-side", "--width", "55"]));
{
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2< │ 1 │.........1.........2+",
"│ │ >.... │ │.........3.........4+",
"│ │ │ │.........5.........6",
];
assert_eq!(lines, expected);
}
{
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_LONG, HUNK_ALIGN_DIFF_SHORT
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2+ │ 1 │.........1.........2<",
"│ │.........3.........4+ │ │ >....",
"│ │.........5.........6 │ │",
];
assert_eq!(lines, expected);
}
}
#[test]
fn test_alignment_1_line_vs_3_lines() {
let config = make_config_from_args(&default_wrap_cfg_plus(&[
"--side-by-side",
"--width",
"61",
"--line-fill-method",
"spaces",
]));
{
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2....│ 1 │.........1.........2...+",
"│ │ │ │......3.........4......+",
"│ │ │ │...5.........6 ",
];
assert_eq!(lines, expected);
}
{
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_LONG, HUNK_ALIGN_DIFF_SHORT
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2...+│ 1 │.........1.........2....",
"│ │......3.........4......+│ │",
"│ │...5.........6 │ │",
];
assert_eq!(lines, expected);
}
}
#[test]
fn test_wrap_max_lines_2() {
let mut config = make_config_from_args(&default_wrap_cfg_plus(&[
"--side-by-side",
"--width",
"72",
"--line-fill-method",
"spaces",
]));
config.truncation_symbol = ">".into();
{
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2.... │ 1 │.........1.........2.........+",
"│ │ │ │3.........4.........5........+",
"│ │ │ │.6 ",
];
assert_eq!(lines, expected);
}
{
config.wrap_config.max_lines = 2;
let output = run_delta(
&format!(
"{}-{}+{}",
HUNK_ALIGN_DIFF_HEADER, HUNK_ALIGN_DIFF_SHORT, HUNK_ALIGN_DIFF_LONG
),
&config,
);
let output = strip_ansi_codes(&output);
let lines: Vec<_> = output.lines().skip(crate::config::HEADER_LEN).collect();
let expected = vec![
"│ 1 │.........1.........2.... │ 1 │.........1.........2.........+",
"│ │ │ │3.........4.........5........>",
];
assert_eq!(lines, expected);
}
}
}