From a3c64ecf65f34792eda1a7d18975d9b1e8eed542 Mon Sep 17 00:00:00 2001 From: Thomas Otto Date: Sun, 16 Jan 2022 21:46:20 +0100 Subject: Placeholder may contain a type field Similar to Pythons `{n:2.1f}` where f indicates a floating point type. The type may be separated by an underscore: `{n:<15.14_type}`. Add a FormatStringPlaceholderDataAnyPlaceholder template which works without a borrowed Placeholder. --- src/features/line_numbers.rs | 30 +++--- src/format.rs | 230 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 228 insertions(+), 32 deletions(-) diff --git a/src/features/line_numbers.rs b/src/features/line_numbers.rs index d172ac88..bc55f2b9 100644 --- a/src/features/line_numbers.rs +++ b/src/features/line_numbers.rs @@ -335,12 +335,7 @@ pub mod tests { vec![format::FormatStringPlaceholderData { prefix: "".into(), placeholder: Some(Placeholder::NumberMinus), - alignment_spec: None, - width: None, - precision: None, - suffix: "".into(), - prefix_len: 0, - suffix_len: 0, + ..Default::default() }] ) } @@ -354,10 +349,7 @@ pub mod tests { placeholder: Some(Placeholder::NumberPlus), alignment_spec: None, width: Some(4), - precision: None, - suffix: "".into(), - prefix_len: 0, - suffix_len: 0, + ..Default::default() }] ) } @@ -372,9 +364,7 @@ pub mod tests { alignment_spec: Some(Align::Right), width: Some(4), precision: None, - suffix: "".into(), - prefix_len: 0, - suffix_len: 0, + ..Default::default() }] ) } @@ -388,10 +378,7 @@ pub mod tests { placeholder: Some(Placeholder::NumberPlus), alignment_spec: Some(Align::Right), width: Some(4), - precision: None, - suffix: "".into(), - prefix_len: 0, - suffix_len: 0, + ..Default::default() }] ) } @@ -409,6 +396,7 @@ pub mod tests { suffix: "@@".into(), prefix_len: 2, suffix_len: 2, + ..Default::default() }] ) } @@ -427,6 +415,7 @@ pub mod tests { suffix: "@@---{np:_>4}**".into(), prefix_len: 2, suffix_len: 15, + ..Default::default() }, format::FormatStringPlaceholderData { prefix: "@@---".into(), @@ -437,6 +426,7 @@ pub mod tests { suffix: "**".into(), prefix_len: 5, suffix_len: 2, + ..Default::default() } ] ) @@ -455,6 +445,7 @@ pub mod tests { suffix: "__@@---**".into(), prefix_len: 0, suffix_len: 9, + ..Default::default() },] ) } @@ -472,6 +463,7 @@ pub mod tests { suffix: "|".into(), prefix_len: 2, suffix_len: 1, + ..Default::default() }] ); } @@ -494,6 +486,7 @@ pub mod tests { suffix: "+{np:<4}|".into(), prefix_len: 2, suffix_len: 9, + ..Default::default() }, format::FormatStringPlaceholderData { prefix: "+".into(), @@ -504,6 +497,7 @@ pub mod tests { suffix: "|".into(), prefix_len: 1, suffix_len: 1, + ..Default::default() } ] ); @@ -521,6 +515,7 @@ pub mod tests { suffix: "|++|".into(), prefix_len: 1, suffix_len: 4, + ..Default::default() }] ); } @@ -543,6 +538,7 @@ pub mod tests { precision: None, suffix: long.into(), suffix_len: long.len(), + ..Default::default() },] ) } diff --git a/src/format.rs b/src/format.rs index 6a6b908e..9afd3b10 100644 --- a/src/format.rs +++ b/src/format.rs @@ -48,18 +48,50 @@ impl TryFrom> for Align { } } -#[derive(Debug, Default, PartialEq)] -pub struct FormatStringPlaceholderData<'a> { +#[derive(Debug, PartialEq, Clone)] +pub struct FormatStringPlaceholderDataAnyPlaceholder { pub prefix: SmolStr, pub prefix_len: usize, - pub placeholder: Option>, + pub placeholder: Option, pub alignment_spec: Option, pub width: Option, pub precision: Option, + pub fmt_type: SmolStr, pub suffix: SmolStr, pub suffix_len: usize, } +impl Default for FormatStringPlaceholderDataAnyPlaceholder { + fn default() -> Self { + Self { + prefix: SmolStr::default(), + prefix_len: 0, + placeholder: None, + alignment_spec: None, + width: None, + precision: None, + fmt_type: SmolStr::default(), + suffix: SmolStr::default(), + suffix_len: 0, + } + } +} + +impl FormatStringPlaceholderDataAnyPlaceholder { + pub fn only_string(s: &str) -> Self { + Self { + suffix: s.into(), + suffix_len: s.graphemes(true).count(), + ..Self::default() + } + } +} + +pub type FormatStringPlaceholderData<'a> = + FormatStringPlaceholderDataAnyPlaceholder>; + +pub type FormatStringSimple = FormatStringPlaceholderDataAnyPlaceholder<()>; + impl<'a> FormatStringPlaceholderData<'a> { pub fn width(&self, hunk_max_line_number_width: usize) -> (usize, usize) { // Only if Some(placeholder) is present will there be a number formatted @@ -75,6 +107,19 @@ impl<'a> FormatStringPlaceholderData<'a> { self.suffix_len, ) } + pub fn into_simple(self) -> FormatStringSimple { + FormatStringSimple { + prefix: self.prefix, + prefix_len: self.prefix_len, + placeholder: None, + alignment_spec: self.alignment_spec, + width: self.width, + precision: self.precision, + fmt_type: self.fmt_type, + suffix: self.suffix, + suffix_len: self.suffix_len, + } + } } pub type FormatStringData<'a> = Vec>; @@ -83,18 +128,21 @@ pub fn make_placeholder_regex(labels: &[&str]) -> Regex { Regex::new(&format!( r"(?x) \{{ - ({}) # 1: Placeholder labels - (?: # Start optional format spec (non-capturing) - : # Literal colon - (?: # Start optional fill/alignment spec (non-capturing) - ([^<^>])? # 2: Optional fill character (ignored) - ([<^>]) # 3: Alignment spec - )? # - (\d+) # 4: Width - (?: # Start optional precision (non-capturing) - \.(\d+) # 5: Precision - )? # - )? # + ({}) # 1: Placeholder labels + (?: # Start optional format spec (non-capturing) + : # Literal colon + (?: # Start optional fill/alignment spec (non-capturing) + ([^<^>])? # 2: Optional fill character (ignored) + ([<^>]) # 3: Alignment spec + )? # + (\d+)? # 4: Width (optional) + (?: # Start optional precision (non-capturing) + \.(\d+) # 5: Precision + )? # + (?: # Start optional format type (non-capturing) + _?([A-Za-z][0-9A-Za-z_-]*) # 6: Format type, optional leading _ + )? # + )? # \}} ", labels.join("|") @@ -102,6 +150,7 @@ pub fn make_placeholder_regex(labels: &[&str]) -> Regex { .unwrap() } +// The resulting vector is never empty pub fn parse_line_number_format<'a>( format_string: &'a str, placeholder_regex: &Regex, @@ -143,6 +192,10 @@ pub fn parse_line_number_format<'a>( panic!("Invalid precision in format string: {}", format_string) }) }), + fmt_type: captures + .get(6) + .map(|m| SmolStr::from(m.as_str())) + .unwrap_or_default(), suffix, suffix_len, }); @@ -271,6 +324,79 @@ mod tests { } } + #[test] + fn test_placeholder_with_notype() { + let regex = make_placeholder_regex(&["placeholder"]); + assert_eq!( + parse_line_number_format("{placeholder:^4}", ®ex, false), + vec![FormatStringPlaceholderData { + placeholder: Some(Placeholder::Str("placeholder")), + alignment_spec: Some(Align::Center), + width: Some(4), + ..Default::default() + }] + ); + } + + #[test] + fn test_placeholder_with_only_type_dash_number() { + let regex = make_placeholder_regex(&["placeholder"]); + assert_eq!( + parse_line_number_format("{placeholder:a_type-b-12}", ®ex, false), + vec![FormatStringPlaceholderData { + placeholder: Some(Placeholder::Str("placeholder")), + fmt_type: "a_type-b-12".into(), + ..Default::default() + }] + ); + } + + #[test] + fn test_placeholder_with_empty_formatting() { + let regex = make_placeholder_regex(&["placeholder"]); + assert_eq!( + parse_line_number_format("{placeholder:}", ®ex, false), + vec![FormatStringPlaceholderData { + placeholder: Some(Placeholder::Str("placeholder")), + ..Default::default() + }] + ); + } + + #[test] + fn test_placeholder_with_type_and_more() { + let regex = make_placeholder_regex(&["placeholder"]); + assert_eq!( + parse_line_number_format("prefix {placeholder:<15.14type} suffix", ®ex, false), + vec![FormatStringPlaceholderData { + prefix: "prefix ".into(), + placeholder: Some(Placeholder::Str("placeholder")), + alignment_spec: Some(Align::Left), + width: Some(15), + precision: Some(14), + fmt_type: "type".into(), + suffix: " suffix".into(), + prefix_len: 7, + suffix_len: 7, + }] + ); + + assert_eq!( + parse_line_number_format("prefix {placeholder:<15.14_type} suffix", ®ex, false), + vec![FormatStringPlaceholderData { + prefix: "prefix ".into(), + placeholder: Some(Placeholder::Str("placeholder")), + alignment_spec: Some(Align::Left), + width: Some(15), + precision: Some(14), + fmt_type: "type".into(), + suffix: " suffix".into(), + prefix_len: 7, + suffix_len: 7, + }] + ); + } + #[test] fn test_placeholder_regex() { let regex = make_placeholder_regex(&["placeholder"]); @@ -282,10 +408,84 @@ mod tests { alignment_spec: Some(Align::Left), width: Some(15), precision: Some(14), + fmt_type: SmolStr::default(), + suffix: " suffix".into(), + prefix_len: 7, + suffix_len: 7, + }] + ); + } + + #[test] + fn test_placeholder_regex_empty_placeholder() { + let regex = make_placeholder_regex(&[""]); + assert_eq!( + parse_line_number_format("prefix {:<15.14} suffix", ®ex, false), + vec![FormatStringPlaceholderData { + prefix: "prefix ".into(), + placeholder: Some(Placeholder::Str("")), + alignment_spec: Some(Align::Left), + width: Some(15), + precision: Some(14), + fmt_type: SmolStr::default(), + suffix: " suffix".into(), + prefix_len: 7, + suffix_len: 7, + }] + ); + } + #[test] + fn test_format_string_simple() { + let regex = make_placeholder_regex(&["foo"]); + let f = parse_line_number_format("prefix {foo:<15.14} suffix", ®ex, false); + + assert_eq!( + f, + vec![FormatStringPlaceholderData { + prefix: "prefix ".into(), + placeholder: Some(Placeholder::Str("foo")), + alignment_spec: Some(Align::Left), + width: Some(15), + precision: Some(14), + fmt_type: SmolStr::default(), + suffix: " suffix".into(), + prefix_len: 7, + suffix_len: 7, + }] + ); + let simple: Vec<_> = f + .into_iter() + .map(FormatStringPlaceholderData::into_simple) + .collect(); + assert_eq!( + simple, + vec![FormatStringSimple { + prefix: "prefix ".into(), + placeholder: None, + alignment_spec: Some(Align::Left), + width: Some(15), + precision: Some(14), + fmt_type: SmolStr::default(), suffix: " suffix".into(), prefix_len: 7, suffix_len: 7, }] ); } + + #[test] + fn test_line_number_format_only_string() { + let f = FormatStringSimple::only_string("abc"); + assert_eq!(f.suffix_len, 3); + } + + #[test] + fn test_parse_line_number_format_not_empty() { + let regex = make_placeholder_regex(&["abc"]); + assert!(!parse_line_number_format(" abc ", ®ex, false).is_empty()); + assert!(!parse_line_number_format("", ®ex, false).is_empty()); + let regex = make_placeholder_regex(&[""]); + assert!(!parse_line_number_format(" abc ", ®ex, false).is_empty()); + assert!(!parse_line_number_format("", ®ex, false).is_empty()); + } } -- cgit v1.2.3