diff options
author | Dan Davison <dandavison7@gmail.com> | 2021-10-16 11:16:56 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-16 11:16:56 -0400 |
commit | 3a21f1b10b91285e0078b67db8c9624bb4894f92 (patch) | |
tree | c9df0441a4daa8712dcaac9f0b3e899da63e1397 | |
parent | 85ba3ba5782e04d514998f33f97b5f44cf859e86 (diff) | |
parent | a30271a09ec00b518160b20552d7ecf9595afd6e (diff) |
Merge pull request #515 from th1000s/wrap
Add side-by-side line wrapping mode
-rw-r--r-- | src/ansi/mod.rs | 1 | ||||
-rw-r--r-- | src/cli.rs | 36 | ||||
-rw-r--r-- | src/color.rs | 6 | ||||
-rw-r--r-- | src/config.rs | 111 | ||||
-rw-r--r-- | src/delta.rs | 4 | ||||
-rw-r--r-- | src/features/line_numbers.rs | 161 | ||||
-rw-r--r-- | src/features/side_by_side.rs | 213 | ||||
-rw-r--r-- | src/format.rs | 28 | ||||
-rw-r--r-- | src/main.rs | 20 | ||||
-rw-r--r-- | src/minusplus.rs (renamed from src/plusminus.rs) | 24 | ||||
-rw-r--r-- | src/options/set.rs | 6 | ||||
-rw-r--r-- | src/paint.rs | 97 | ||||
-rw-r--r-- | src/parse_style.rs | 2 | ||||
-rw-r--r-- | src/style.rs | 8 | ||||
-rw-r--r-- | src/subcommands/show_syntax_themes.rs | 2 | ||||
-rw-r--r-- | src/syntect_color.rs | 15 | ||||
-rw-r--r-- | src/syntect_utils.rs | 86 | ||||
-rw-r--r-- | src/wrapping.rs | 1117 |
18 files changed, 1760 insertions, 177 deletions
diff --git a/src/ansi/mod.rs b/src/ansi/mod.rs index 442ead85..643b47f0 100644 --- a/src/ansi/mod.rs +++ b/src/ansi/mod.rs @@ -12,6 +12,7 @@ use iterator::{AnsiElementIterator, Element}; pub const ANSI_CSI_CLEAR_TO_EOL: &str = "\x1b[0K"; pub const ANSI_CSI_CLEAR_TO_BOL: &str = "\x1b[1K"; pub const ANSI_SGR_RESET: &str = "\x1b[0m"; +pub const ANSI_SGR_REVERSE: &str = "\x1b[7m"; pub fn strip_ansi_codes(s: &str) -> String { strip_ansi_codes_from_strings_iterator(ansi_strings_iterator(s)) @@ -13,7 +13,8 @@ use crate::bat_utils::output::PagingMode; use crate::git_config::{GitConfig, GitConfigEntry}; use crate::options; -#[derive(StructOpt, Default)] +// No Default trait as this ignores `default_value = ..` +#[derive(StructOpt)] #[structopt( name = "delta", about = "A viewer for git and diff output", @@ -446,6 +447,12 @@ pub struct Opt { #[structopt(long = "default-language")] pub default_language: Option<String>, + #[structopt(long = "inline-hint-style", default_value = "blue")] + /// Style (foreground, background, attributes) for content added by delta to + /// the original diff such as special characters to highlight tabs, and the + /// symbols used to indicate wrapped lines. See STYLES section. + pub inline_hint_style: String, + /// The regular expression used to decide what a word is for the within-line highlight /// algorithm. For less fine-grained matching than the default try --word-diff-regex="\S+" /// --max-line-distance=1.0 (this is more similar to `git --word-diff`). @@ -497,6 +504,32 @@ pub struct Opt { #[structopt(long = "line-numbers-right-style", default_value = "auto")] pub line_numbers_right_style: String, + /// How often a line should be wrapped if it does not fit. Zero means to never wrap. Any content + /// which does not fit will be truncated. A value of "unlimited" means a line will be wrapped + /// as many times as required. + #[structopt(long = "wrap-max-lines", default_value = "2")] + pub wrap_max_lines: String, + + /// Symbol added to the end of a line indicating that the content has been wrapped + /// onto the next line and continues left-aligned. + #[structopt(long = "wrap-left-symbol", default_value = "↵")] + pub wrap_left_symbol: String, + + /// Symbol added to the end of a line indicating that the content has been wrapped + /// onto the next line and continues right-aligned. + #[structopt(long = "wrap-right-symbol", default_value = "↴")] + pub wrap_right_symbol: String, + + /// Threshold for right-aligning wrapped content. If the length of the remaining wrapped + /// content, as a percentage of width, is less than this quantity it will be right-aligned. + /// Otherwise it will be left-aligned. + #[structopt(long = "wrap-right-percent", default_value = "37.0")] + pub wrap_right_percent: String, + + /// Symbol displayed in front of right-aligned wrapped content. + #[structopt(long = "wrap-right-prefix-symbol", default_value = "…")] + pub wrap_right_prefix_symbol: String, + #[structopt(long = "file-modified-label", default_value = "")] /// Text to display in front of a modified file path. pub file_modified_label: String, @@ -524,6 +557,7 @@ pub struct Opt { #[structopt(long = "max-line-length", default_value = "512")] /// Truncate lines longer than this. To prevent any truncation, set to zero. Note that /// delta will be slow on very long lines (e.g. minified .js) if truncation is disabled. + /// When wrapping lines it is automatically set to fit at least all visible characters. pub max_line_length: usize, /// How to extend the background color to the end of the line in side-by-side mode. Can diff --git a/src/color.rs b/src/color.rs index 8b7428be..e7c3f62f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -7,7 +7,7 @@ use lazy_static::lazy_static; use syntect::highlighting::Color as SyntectColor; use crate::bat_utils::terminal::to_ansi_color; -use crate::syntect_color; +use crate::syntect_utils; pub fn parse_color(s: &str, true_color: bool) -> Option<Color> { if s == "normal" { @@ -22,8 +22,8 @@ pub fn parse_color(s: &str, true_color: bool) -> Option<Color> { } else { s.parse::<u8>() .ok() - .and_then(syntect_color::syntect_color_from_ansi_number) - .or_else(|| syntect_color::syntect_color_from_ansi_name(s)) + .and_then(syntect_utils::syntect_color_from_ansi_number) + .or_else(|| syntect_utils::syntect_color_from_ansi_name(s)) .unwrap_or_else(die) }; to_ansi_color(syntect_color, true_color) diff --git a/src/config.rs b/src/config.rs index fbd58044..4074551b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,17 +7,51 @@ use structopt::clap; use syntect::highlighting::Style as SyntectStyle; use syntect::highlighting::Theme as SyntaxTheme; use syntect::parsing::SyntaxSet; +use unicode_segmentation::UnicodeSegmentation; +use crate::ansi; use crate::bat_utils::output::PagingMode; use crate::cli; use crate::color; use crate::delta::State; use crate::env; +use crate::fatal; use crate::features::navigate; use crate::features::side_by_side; use crate::git_config::{GitConfig, GitConfigEntry}; use crate::paint::BgFillMethod; use crate::style::{self, Style}; +use crate::syntect_utils::FromDeltaStyle; +use crate::wrapping::WrapConfig; + +pub const INLINE_SYMBOL_WIDTH_1: usize = 1; + +fn remove_percent_suffix(arg: &str) -> &str { + match &arg.strip_suffix('%') { + Some(s) => s, + None => arg, + } +} + +fn ensure_display_width_1(what: &str, arg: String) -> String { + match arg.grapheme_indices(true).count() { + INLINE_SYMBOL_WIDTH_1 => arg, + width => fatal(format!( + "Invalid value for {}, display width of \"{}\" must be {} but is {}", + what, arg, INLINE_SYMBOL_WIDTH_1, width + )), + } +} + +fn adapt_wrap_max_lines_argument(arg: String) -> usize { + if arg == "∞" || arg == "unlimited" || arg.starts_with("inf") { + 0 + } else { + arg.parse::<usize>() + .unwrap_or_else(|err| fatal(format!("Invalid wrap-max-lines argument: {}", err))) + + 1 + } +} pub struct Config { pub available_terminal_width: usize, @@ -50,6 +84,7 @@ pub struct Config { pub hyperlinks: bool, pub hyperlinks_commit_link_format: Option<String>, pub hyperlinks_file_link_format: String, + pub inline_hint_style: Style, pub inspect_raw_lines: cli::InspectRawLines, pub keep_plus_minus_markers: bool, pub line_fill_method: BgFillMethod, @@ -95,6 +130,7 @@ pub struct Config { pub true_color: bool, pub truncation_symbol: String, pub whitespace_error_style: Style, + pub wrap_config: WrapConfig, pub zero_style: Style, } @@ -173,6 +209,13 @@ impl From<cli::Opt> for Config { &opt.computed.available_terminal_width, ); + let inline_hint_style = Style::from_str( + &opt.inline_hint_style, + None, + None, + opt.computed.true_color, + false, + ); let git_minus_style = match opt.git_config_entries.get("color.diff.old") { Some(GitConfigEntry::Style(s)) => Style::from_git_str(s), _ => *style::GIT_DEFAULT_MINUS_STYLE, @@ -193,10 +236,7 @@ impl From<cli::Opt> for Config { // Note that "default" is not documented Some("ansi") | Some("default") | None => BgFillMethod::TryAnsiSequence, Some("spaces") => BgFillMethod::Spaces, - _ => { - eprintln!("Invalid option for line-fill-method: Expected \"ansi\" or \"spaces\"."); - process::exit(1); - } + _ => fatal("Invalid option for line-fill-method: Expected \"ansi\" or \"spaces\"."), }; let navigate_regexp = if opt.navigate || opt.show_themes { @@ -212,6 +252,8 @@ impl From<cli::Opt> for Config { None }; + let wrap_max_lines_plus1 = adapt_wrap_max_lines_argument(opt.wrap_max_lines); + Self { available_terminal_width: opt.computed.available_terminal_width, background_color_extends_to_terminal_width: opt @@ -256,8 +298,17 @@ impl From<cli::Opt> for Config { hyperlinks_commit_link_format: opt.hyperlinks_commit_link_format, hyperlinks_file_link_format: opt.hyperlinks_file_link_format, inspect_raw_lines: opt.computed.inspect_raw_lines, + inline_hint_style, keep_plus_minus_markers: opt.keep_plus_minus_markers, - line_fill_method, + line_fill_method: if opt.side_by_side { + // Panels in side-by-side always sum up to an even number, if the terminal has + // an odd width then extending the background color with an ANSI sequence + // would indicate the wrong width and extend beyond truncated or wrapped content, + // thus spaces are used here by default. + BgFillMethod::Spaces + } else { + line_fill_method + }, line_numbers: opt.line_numbers, line_numbers_left_format: opt.line_numbers_left_format, line_numbers_left_style, @@ -269,7 +320,23 @@ impl From<cli::Opt> for Config { line_buffer_size: opt.line_buffer_size, max_line_distance: opt.max_line_distance, max_line_distance_for_naively_paired_lines, - max_line_length: opt.max_line_length, + max_line_length: match (opt.side_by_side, wrap_max_lines_plus1) { + (false, _) | (true, 1) => opt.max_line_length, + // Ensure there is enough text to wrap, either don't truncate the input at all (0) + // or ensure there is enough for the requested number of lines. + // The input can contain ANSI sequences, so round up a bit. This is enough for + // normal `git diff`, but might not be with ANSI heavy input. + (true, 0) => 0, + (true, wrap_max_lines) => { + let single_pane_width = opt.computed.available_terminal_width / 2; + let add_25_percent_or_term_width = + |x| x + std::cmp::max((x * 250) / 1000, single_pane_width) as usize; + std::cmp::max( + opt.max_line_length, + add_25_percent_or_term_width(single_pane_width * wrap_max_lines), + ) + } + }, minus_emph_style, minus_empty_line_marker_style, minus_file: opt.minus_file, @@ -298,7 +365,33 @@ impl From<cli::Opt> for Config { tab_width: opt.tab_width, tokenization_regex, true_color: opt.computed.true_color, - truncation_symbol: "→".to_string(), + truncation_symbol: format!("{}→{}", ansi::ANSI_SGR_REVERSE, ansi::ANSI_SGR_RESET), + wrap_config: WrapConfig { + left_symbol: ensure_display_width_1("wrap-left-symbol", opt.wrap_left_symbol), + right_symbol: ensure_display_width_1("wrap-right-symbol", opt.wrap_right_symbol), + right_prefix_symbol: ensure_display_width_1( + "wrap-right-prefix-symbol", + opt.wrap_right_prefix_symbol, + ), + use_wrap_right_permille: { + let arg = &opt.wrap_right_percent; + let percent = remove_percent_suffix(arg) + .parse::<f64>() + .unwrap_or_else(|err| { + fatal(format!( + "Could not parse wrap-right-percent argument {}: {}.", + &arg, err + )) + }); + if percent.is_finite() && percent > 0.0 && percent < 100.0 { + (percent * 10.0).round() as usize + } else { + fatal("Invalid value for wrap-right-percent, not between 0 and 100.") + } + }, + max_lines: wrap_max_lines_plus1, + inline_hint_syntect_style: SyntectStyle::from_delta_style(inline_hint_style), + }, whitespace_error_style, zero_style, } @@ -530,6 +623,10 @@ pub fn delta_unreachable(message: &str) -> ! { } #[cfg(test)] +// Usual length of the header returned by `run_delta()`, often `skip()`-ed. +pub const HEADER_LEN: usize = 7; + +#[cfg(test)] pub mod tests { use crate::bat_utils::output::PagingMode; use crate::cli; diff --git a/src/delta.rs b/src/delta.rs index f1ed2313..92d8691f 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -24,6 +24,10 @@ pub enum State { SubmoduleShort(String), // In a submodule section, with gitconfig diff.submodule = short Blame(String), // In a line of `git blame` output. Unknown, + // The following elements are created when a line is wrapped to display it: + HunkZeroWrapped, // Wrapped unchanged line + HunkMinusWrapped, // Wrapped removed line + HunkPlusWrapped, // Wrapped added line } #[derive(Debug, PartialEq)] diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs index 0d282557..ff0a58ef 100644 --- a/src/features/line_numbers.rs +++ b/src/features/line_numbers.rs @@ -6,10 +6,10 @@ use regex::Regex; use crate::config; use crate::delta::State; use crate::features::hyperlinks; -use crate::features::side_by_side::PanelSide; +use crate::features::side_by_side::{Left, PanelSide, Right}; use crate::features::OptionValueFunction; use crate::format; -use crate::plusminus::*; +use crate::minusplus::*; use crate::style::Style; pub fn make_feature() -> Vec<(String, OptionValueFunction)> { @@ -69,8 +69,8 @@ pub fn format_and_paint_line_numbers<'a>( side_by_side_panel: Option<PanelSide>, config: &'a config::Config, ) -> Vec<ansi_term::ANSIGenericString<'a, str>> { - let nr_left = line_numbers_data.line_number[Minus]; - let nr_right = line_numbers_data.line_number[Plus]; + 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, @@ -78,18 +78,21 @@ pub fn format_and_paint_line_numbers<'a>( ); let ((minus_number, plus_number), (minus_style, plus_style)) = match state { State::HunkMinus(_) => { - line_numbers_data.line_number[Minus] += 1; + line_numbers_data.line_number[Left] += 1; ((Some(nr_left), None), (minus_style, plus_style)) } + State::HunkMinusWrapped => ((None, None), (minus_style, plus_style)), State::HunkZero => { - line_numbers_data.line_number[Minus] += 1; - line_numbers_data.line_number[Plus] += 1; + 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::HunkZeroWrapped => ((None, None), (zero_style, zero_style)), State::HunkPlus(_) => { - line_numbers_data.line_number[Plus] += 1; + line_numbers_data.line_number[Right] += 1; ((None, Some(nr_right)), (minus_style, plus_style)) } + State::HunkPlusWrapped => ((None, None), (minus_style, plus_style)), _ => return Vec::new(), }; @@ -97,14 +100,14 @@ pub fn format_and_paint_line_numbers<'a>( let (emit_left, emit_right) = match (config.side_by_side, side_by_side_panel) { (false, _) => (true, true), - (true, Some(PanelSide::Left)) => (true, false), - (true, Some(PanelSide::Right)) => (false, 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.format_data[PanelSide::Left], + &line_numbers_data.format_data[Left], &config.line_numbers_left_style, minus_number, plus_number, @@ -118,7 +121,7 @@ pub fn format_and_paint_line_numbers<'a>( if emit_right { formatted_numbers.extend(format_and_paint_line_number_field( - &line_numbers_data.format_data[PanelSide::Right], + &line_numbers_data.format_data[Right], &config.line_numbers_right_style, minus_number, plus_number, @@ -136,24 +139,26 @@ 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>, + 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(left_format: &'a str, right_format: &'a str) -> LineNumbersData<'a> { Self { - format_data: PlusMinus::new( + format_data: MinusPlus::new( format::parse_line_number_format(left_format, &*LINE_NUMBERS_PLACEHOLDER_REGEX), format::parse_line_number_format(right_format, &*LINE_NUMBERS_PLACEHOLDER_REGEX), ), - line_number: PlusMinus::new(0, 0), + line_number: MinusPlus::new(0, 0), hunk_max_line_number_width: 0, plus_file: "".to_string(), } @@ -164,12 +169,38 @@ impl<'a> LineNumbersData<'a> { // 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 = - PlusMinus::new(line_numbers[0].0, line_numbers[line_numbers.len() - 1].0); + 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 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)] @@ -258,6 +289,8 @@ pub mod tests { alignment_spec: None, width: None, suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -272,6 +305,8 @@ pub mod tests { alignment_spec: None, width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -286,6 +321,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -300,6 +337,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "", + prefix_len: 0, + suffix_len: 0, }] ) } @@ -314,6 +353,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "@@", + prefix_len: 2, + suffix_len: 2, }] ) } @@ -332,6 +373,8 @@ pub mod tests { alignment_spec: Some("<"), width: Some(3), suffix: "@@---{np:_>4}**", + prefix_len: 2, + suffix_len: 15, }, format::FormatStringPlaceholderData { prefix: "@@---", @@ -339,6 +382,8 @@ pub mod tests { alignment_spec: Some(">"), width: Some(4), suffix: "**", + prefix_len: 5, + suffix_len: 2, } ] ) @@ -354,10 +399,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(), MinusPlus::new(0, 0)); + + let mut data = LineNumbersData::from_format_strings("│", "│+│"); + data.initialize_hunk(&[(10, 11), (10000, 100001)], "a".into()); + assert_eq!(data.formatted_width(), MinusPlus::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(), MinusPlus::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(), MinusPlus::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(), MinusPlus::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()) } @@ -380,7 +493,7 @@ pub mod tests { "0 4", ]); let output = run_delta(TWO_MINUS_LINES_DIFF, &config); - let mut lines = output.lines().skip(7); + let mut lines = output.lines().skip(crate::config::HEADER_LEN); 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"); @@ -404,7 +517,7 @@ pub mod tests { "0 4", ]); let output = run_delta(TWO_PLUS_LINES_DIFF, &config); - let mut lines = output.lines().skip(7); + let mut lines = output.lines().skip(crate::config::HEADER_LEN); 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 = 234567"); @@ -429,7 +542,7 @@ pub mod tests { ]); let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config); let output = strip_ansi_codes(&output); - let mut lines = output.lines().skip(7); + 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"); @@ -454,7 +567,7 @@ pub mod tests { ]); let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config); let output = strip_ansi_codes(&output); - let mut lines = output.lines().skip(7); + 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"); @@ -465,7 +578,7 @@ pub mod tests { 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(7); + 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"); @@ -476,7 +589,7 @@ pub mod tests { let config = make_config_from_a |