diff options
author | Dan Davison <dandavison7@gmail.com> | 2020-06-24 14:19:30 -0400 |
---|---|---|
committer | Dan Davison <dandavison7@gmail.com> | 2020-06-25 15:18:19 -0400 |
commit | 2c1db25a98cd660d56e1197056d4ccbbb3b3182f (patch) | |
tree | abc017435def288abd331f1e885ce4b0312da7fb | |
parent | 1dd1175ef87685689f4c2d5747d1837240eab695 (diff) |
Support subset of Rust string format language
-rw-r--r-- | src/cli.rs | 65 | ||||
-rw-r--r-- | src/features/numbers.rs | 148 | ||||
-rw-r--r-- | src/paint.rs | 9 |
3 files changed, 161 insertions, 61 deletions
@@ -154,22 +154,49 @@ within a style string): Specifying colors like this is useful if your terminal only supports 256 colors (i.e. doesn\'t support 24-bit color). + LINE NUMBERS ------------ -Options --number-left-format and --number-right-format allow you to specify a custom string to -display for the line number columns. The string should specify the location of the line number -using the placeholder %lm for the line number associated with the original file and %lp for the -line number associated with the updated file. +To display line numbers, use --line-numbers. + +Line numbers are displayed in two columns. Here's what it looks like by default: + + 1 ⋮ 1 │ unchanged line + 2 ⋮ │ removed line + ⋮ 2 │ added line + +In that output, the line numbers for the old (minus) version of the file appear in the left column, +and the line numbers for the new (plus) version of the file appear in the right column. In an +unchanged (zero) line, both columns contain a line number. + +The following options allow the line number display to be customized: + +--line-numbers-left-format: Change the contents of the left column +--line-numbers-right-format: Change the contents of the right column +--line-numbers-left-style: Change the style applied to the left column +--line-numbers-right-style: Change the style applied to the right column +--line-numbers-minus-style: Change the style applied to line numbers in minus lines +--line-numbers-zero-style: Change the style applied to line numbers in unchanged lines +--line-numbers-plus-style: Change the style applied to line numbers in plus lines + +Options --line-numbers-left-format and --line-numbers-right-format allow you to change the contents +of the line number columns. Their values are arbitrary format strings, which are allowed to contain +the placeholders {nm} for the line number associated with the old version of the file and {np} for +the line number associated with the new version of the file. The placeholders support a subset of +the string formatting syntax documented here: https://doc.rust-lang.org/std/fmt/#formatting-parameters. +Specifically, you can use the alignment, width, and fill syntax. -For example, to display the line numbers like +For example, the default value of --line-numbers-left-format is '{nm:^4}⋮'. This means that the +left column should display the minus line number (nm), center-aligned, padded with spaces to a +width of 4 characters, followed by a unicode dividing-line character (⋮). - 8 ⋮ 9 │ Here is an output line +Similarly, the default value of --line-numbers-right-format is '{np:^4}│ '. This means that the +right column should display the plus line number (np), center-aligned, padded with spaces to a +width of 4 characters, followed by a unicode dividing-line character (│), and a space. -you would use +Use '<' for left-align, '^' for center-align, and '>' for right-align. ---number-left-format '%lm ⋮' ---number-right-format '%lp │' If something isn't working correctly, or you have a feature request, please open an issue at https://github.com/dandavison/delta/issues. @@ -316,20 +343,16 @@ pub struct Opt { #[structopt(long = "number-zero-style")] pub number_zero_style: Option<String>, - /// Format string for the left column of line numbers (--number), if --number is set. Displays - /// the minus column by default. - /// Should include the placeholder %lm or %lp to indicate the position of the minus or plus - /// line number, respectively. - /// See the LINE NUMBERS section. - #[structopt(long = "number-left-format", default_value = "%lm⋮")] + /// Format string for the left column of line numbers. A typical value would be "{nm:^4}⋮" + /// which means to display the line numbers of the minus file (old version), followed by a + /// dividing character. See the LINE NUMBERS section. + #[structopt(long = "number-left-format", default_value = "{nm:^4}⋮")] pub number_left_format: String, - /// Format string for the right column of line numbers (--number), if --number is set. Displays - /// the plus column by default. - /// Should include the placeholder %lm or %lp to indicate the position of the minus or plus - /// line number, respectively. - /// See the LINE NUMBERS section. - #[structopt(long = "number-right-format", default_value = "%lp│ ")] + /// Format string for the right column of line numbers. A typical value would be "{np:^4}│ " + /// which means to display the line numbers of the plus file (new version), followed by a + /// dividing character, and a space. See the LINE NUMBERS section. + #[structopt(long = "number-right-format", default_value = "{np:^4}│ ")] pub number_right_format: String, /// Style (foreground, background, attributes) for the left line number format string diff --git a/src/features/numbers.rs b/src/features/numbers.rs index 17766261..b5d6c547 100644 --- a/src/features/numbers.rs +++ b/src/features/numbers.rs @@ -5,7 +5,9 @@ use regex::Regex; use crate::config; use crate::style::Style; -pub fn get_formatted_line_number_components<'a>( +/// 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: &'a Option<(Option<usize>, Option<usize>)>, config: &'a config::Config, ) -> Vec<ansi_term::ANSIGenericString<'a, str>> { @@ -27,19 +29,19 @@ pub fn get_formatted_line_number_components<'a>( let mut formatted_numbers = Vec::new(); - formatted_numbers.extend(format_number_components( - minus, - plus, + formatted_numbers.extend(format_and_paint_line_number_field( &config.number_left_format, &config.number_left_format_style, + minus, + plus, &number_minus_style, &number_plus_style, )); - formatted_numbers.extend(format_number_components( - minus, - plus, + formatted_numbers.extend(format_and_paint_line_number_field( &config.number_right_format, &config.number_right_format_style, + minus, + plus, &number_minus_style, &number_plus_style, )); @@ -48,31 +50,45 @@ pub fn get_formatted_line_number_components<'a>( } lazy_static! { - static ref LINE_NUMBER_REGEXP: Regex = Regex::new(r"%(lm|lp)").unwrap(); + static ref LINE_NUMBER_FORMAT_REGEX: Regex = Regex::new( + r"(?x) +\{ +(nm|np) # 1: Literal nm or np +(?: # Start optional format spec (non-capturing) + : # Literal colon + (?: # Start optional fill/alignment spec (non-capturing) + ([^<^>])? # 2: Optional fill character + ([<^>]) # 3: Alignment spec + )? # + (\d+) # 4: Width +)? # +\} +" + ) + .unwrap(); } -fn format_number_components<'a>( - minus: Option<usize>, - plus: Option<usize>, +fn format_and_paint_line_number_field<'a>( format_string: &'a str, number_format_style: &Style, + minus: Option<usize>, + plus: Option<usize>, number_minus_style: &Style, number_plus_style: &Style, ) -> Vec<ansi_term::ANSIGenericString<'a, str>> { let mut formatted_number_strings = Vec::new(); let mut offset = 0; - for _match in LINE_NUMBER_REGEXP.find_iter(&format_string) { + for caps in LINE_NUMBER_FORMAT_REGEX.captures_iter(&format_string) { + let _match = caps.get(0).unwrap(); formatted_number_strings .push(number_format_style.paint(&format_string[offset.._match.start()])); - match _match.as_str() { - "%lm" => { - formatted_number_strings.push(number_minus_style.paint(format_line_number(minus))) - } - "%lp" => { - formatted_number_strings.push(number_plus_style.paint(format_line_number(plus))) - } + match &caps[1] { + "nm" => formatted_number_strings + .push(number_minus_style.paint(format_line_number(minus, &caps[3], &caps[4]))), + "np" => formatted_number_strings + .push(number_plus_style.paint(format_line_number(plus, &caps[3], &caps[4]))), _ => unreachable!(), } offset = _match.end(); @@ -81,16 +97,23 @@ fn format_number_components<'a>( formatted_number_strings } -fn format_line_number(line_number: Option<usize>) -> String { - format!( - "{:^4}", - line_number - .map(|n| format!("{}", n)) - .as_deref() - .unwrap_or_else(|| "") - ) +/// Return line number formatted according to `alignment` and `width`. +fn format_line_number(line_number: Option<usize>, alignment: &str, width: &str) -> String { + let n = line_number + .map(|n| format!("{}", n)) + .unwrap_or_else(|| "".to_string()); + let default_width = 4; // Used only if \d+ cannot be parsed as usize + let w: usize = width.parse().unwrap_or(default_width); + match alignment { + "<" => format!("{0:<1$}", n, w), + "^" | "" => format!("{0:^1$}", n, w), + ">" => format!("{0:>1$}", n, w), + _ => unreachable!(), + } } +// If both minus and plus numbers are present then the line must be a zero line: return the zero +// style. Otherwise, return `default-style`. fn get_zero_or_default_style( minus: Option<usize>, plus: Option<usize>, @@ -106,17 +129,72 @@ fn get_zero_or_default_style( #[cfg(test)] pub mod tests { use console::strip_ansi_codes; + use regex::Captures; use crate::tests::integration_test_utils::integration_test_utils::{make_config, run_delta}; + use super::LINE_NUMBER_FORMAT_REGEX; + + #[test] + fn test_line_number_format_regex_1() { + let caps = LINE_NUMBER_FORMAT_REGEX + .captures_iter("{nm}") + .collect::<Vec<Captures>>(); + assert_eq!(caps.len(), 1); + assert_eq!(_get_capture(0, 1, &caps), "nm"); + assert_eq!(_get_capture(0, 2, &caps), ""); + assert_eq!(_get_capture(0, 3, &caps), ""); + assert_eq!(_get_capture(0, 4, &caps), ""); + } + + #[test] + fn test_line_number_format_regex_2() { + let caps = LINE_NUMBER_FORMAT_REGEX + .captures_iter("{np:4}") + .collect::<Vec<Captures>>(); + assert_eq!(caps.len(), 1); + assert_eq!(_get_capture(0, 1, &caps), "np"); + assert_eq!(_get_capture(0, 2, &caps), ""); + assert_eq!(_get_capture(0, 3, &caps), ""); + assert_eq!(_get_capture(0, 4, &caps), "4"); + } + + #[test] + fn test_line_number_format_regex_3() { + let caps = LINE_NUMBER_FORMAT_REGEX + .captures_iter("{np:>4}") + .collect::<Vec<Captures>>(); + assert_eq!(caps.len(), 1); + assert_eq!(_get_capture(0, 1, &caps), "np"); + assert_eq!(_get_capture(0, 2, &caps), ""); + assert_eq!(_get_capture(0, 3, &caps), ">"); + assert_eq!(_get_capture(0, 4, &caps), "4"); + } + + #[test] + fn test_line_number_format_regex_4() { + let caps = LINE_NUMBER_FORMAT_REGEX + .captures_iter("{np:_>4}") + .collect::<Vec<Captures>>(); + assert_eq!(caps.len(), 1); + assert_eq!(_get_capture(0, 1, &caps), "np"); + assert_eq!(_get_capture(0, 2, &caps), "_"); + assert_eq!(_get_capture(0, 3, &caps), ">"); + assert_eq!(_get_capture(0, 4, &caps), "4"); + } + + fn _get_capture<'a>(i: usize, j: usize, caps: &'a Vec<Captures>) -> &'a str { + caps[i].get(j).map_or("", |m| m.as_str()) + } + #[test] fn test_two_minus_lines() { let config = make_config(&[ "--number", "--number-left-format", - "%lm⋮", + "{nm:^4}⋮", "--number-right-format", - "%lp│", + "{np:^4}│", ]); let output = run_delta(TWO_MINUS_LINES_DIFF, &config); let mut lines = output.lines().skip(4); @@ -153,9 +231,9 @@ pub mod tests { let config = make_config(&[ "--number", "--number-left-format", - "%lm⋮", + "{nm:^4}⋮", "--number-right-format", - "%lp│", + "{np:^4}│", ]); let output = run_delta(TWO_PLUS_LINES_DIFF, &config); let mut lines = output.lines().skip(4); @@ -169,9 +247,9 @@ pub mod tests { let config = make_config(&[ "--number", "--number-left-format", - "%lm⋮", + "{nm:^4}⋮", "--number-right-format", - "%lp│", + "{np:^4}│", ]); let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config); let output = strip_ansi_codes(&output); @@ -186,9 +264,9 @@ pub mod tests { let config = make_config(&[ "--number", "--number-left-format", - "%lm %lm⋮", + "{nm:^4} {nm:^4}⋮", "--number-right-format", - "%lp│", + "{np:^4}│", ]); let output = run_delta(ONE_MINUS_ONE_PLUS_LINE_DIFF, &config); println!("{}", output); diff --git a/src/paint.rs b/src/paint.rs index 9b115a7d..14e16aa6 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -166,11 +166,10 @@ impl<'a> Painter<'a> { }; let mut handled_prefix = false; - let mut ansi_strings = if config.show_line_numbers && line_numbers.is_some() { - numbers::get_formatted_line_number_components(line_numbers, config) - } else { - Vec::new() - }; + let mut ansi_strings = Vec::new(); + if config.show_line_numbers && line_numbers.is_some() { + ansi_strings.extend(numbers::format_and_paint_line_numbers(line_numbers, config)) + } for (section_style, mut text) in superimpose_style_sections( syntax_sections, diff_sections, |