summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2021-10-16 11:16:56 -0400
committerGitHub <noreply@github.com>2021-10-16 11:16:56 -0400
commit3a21f1b10b91285e0078b67db8c9624bb4894f92 (patch)
treec9df0441a4daa8712dcaac9f0b3e899da63e1397
parent85ba3ba5782e04d514998f33f97b5f44cf859e86 (diff)
parenta30271a09ec00b518160b20552d7ecf9595afd6e (diff)
Merge pull request #515 from th1000s/wrap
Add side-by-side line wrapping mode
-rw-r--r--src/ansi/mod.rs1
-rw-r--r--src/cli.rs36
-rw-r--r--src/color.rs6
-rw-r--r--src/config.rs111
-rw-r--r--src/delta.rs4
-rw-r--r--src/features/line_numbers.rs161
-rw-r--r--src/features/side_by_side.rs213
-rw-r--r--src/format.rs28
-rw-r--r--src/main.rs20
-rw-r--r--src/minusplus.rs (renamed from src/plusminus.rs)24
-rw-r--r--src/options/set.rs6
-rw-r--r--src/paint.rs97
-rw-r--r--src/parse_style.rs2
-rw-r--r--src/style.rs8
-rw-r--r--src/subcommands/show_syntax_themes.rs2
-rw-r--r--src/syntect_color.rs15
-rw-r--r--src/syntect_utils.rs86
-rw-r--r--src/wrapping.rs1117
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))
diff --git a/src/cli.rs b/src/cli.rs
index f0f6165a..caa18de6 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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