summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2020-06-24 14:19:30 -0400
committerDan Davison <dandavison7@gmail.com>2020-06-25 15:18:19 -0400
commit2c1db25a98cd660d56e1197056d4ccbbb3b3182f (patch)
treeabc017435def288abd331f1e885ce4b0312da7fb
parent1dd1175ef87685689f4c2d5747d1837240eab695 (diff)
Support subset of Rust string format language
-rw-r--r--src/cli.rs65
-rw-r--r--src/features/numbers.rs148
-rw-r--r--src/paint.rs9
3 files changed, 161 insertions, 61 deletions
diff --git a/src/cli.rs b/src/cli.rs
index e0a227c8..bdb93d55 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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,