diff --git a/logparse/proptest-regressions/proptesting.txt b/logparse/proptest-regressions/proptesting.txt new file mode 100644 index 0000000..bcf3209 --- /dev/null +++ b/logparse/proptest-regressions/proptesting.txt @@ -0,0 +1,9 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 8d278209bf1e44a21adb1c2c2930f04078cf7b3b5199f663320adfae874257cc # shrinks to original = "ยก" +cc 692ed6d9acb3a9744c4315d6ca58ee887c49c1a06e41772eeda09f94beda02a4 # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [PathSegment { leading_separator: None, segment: "\u{16af0}" }], filename: FileName { leading_separator: None, segment: "", ext_excluding_dot: None, location: None } }) }], trailing_space: Space("") } +cc 29a5047217c3612e9cf198c1f353d9ba94fdf8dafb7f8bf7b3561b0e82731d01 # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: None, delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(""), token: True }], trailing_space: Space("") } }) }, Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: None, delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(""), token: True }], trailing_space: Space("") } }) }, Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: None, delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(""), token: True }], trailing_space: Space(" ") } }) }], trailing_space: Space("") } diff --git a/logparse/src/ast.rs b/logparse/src/ast.rs index 71315ff..c413531 100644 --- a/logparse/src/ast.rs +++ b/logparse/src/ast.rs @@ -49,6 +49,7 @@ pub struct AnyString<'a> { #[derive(Clone, Debug, PartialEq)] pub struct Space<'a>(pub Cow<'a, str>); +/// See [`Token::Path`]. #[derive(Copy, Clone, Debug, PartialEq, Arbitrary)] #[allow(missing_docs)] pub enum PathSep { @@ -58,7 +59,7 @@ pub enum PathSep { Backslash, } -/// A segment of a path, with a leading separator. +/// A segment of a path, with a leading separator. See [`Token::Path`]. #[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] pub struct PathSegment<'a> { @@ -98,7 +99,11 @@ pub struct Path<'a> { /// See [`Token::Number`]. #[derive(Clone, Debug, PartialEq)] -pub struct Number<'a>(pub Cow<'a, str>); +#[allow(missing_docs)] +pub struct Number<'a> { + pub number: Cow<'a, str>, + pub suffix_without_underscore: Option>, +} /// Anything that doesn't contain spaces, and that can be a prefix of `Delimited`. /// i.e. an english word, or rust `::`-separated Path @@ -151,6 +156,28 @@ pub enum Token<'a> { /// ``` None, + /// A lifetime, a la rust. The rules for this are pretty restrictive. + /// A lifetime must be, a single quote, followed by no more than 3 ascii lowercase alphabetic characters, + /// followed by *not* a closing quote, and any nonalphabetic character. Like a comma, + /// whitespace, eof, etc. + /// + /// ``` + /// # logparse::generate_ast_recognizer!(is_lt, Token::Lifetime(_)); + /// + /// assert!(is_lt("'a")); + /// assert!(is_lt("'tcx")); + /// + /// // some counterexamples + /// + /// assert!(!is_lt("'verylong")); + /// assert!(!is_lt("'foo'")); + /// assert!(!is_lt("'a'")); + /// assert!(!is_lt("'a longer single-quoted string'")); + /// assert!(!is_lt("a")); + /// assert!(!is_lt("13")); + /// `` + Lifetime(Cow<'a, str>), + /// A path, anything that looks vaguely path-like. /// For example: /// @@ -208,6 +235,8 @@ pub enum Token<'a> { /// There must not be any alphabetic character after the number, without a space inbetween. /// That's to guard against finding numbers inside hashes for example. /// + /// A suffix is allowed, however, as long as its separated by an underscore. + /// /// For example: /// /// ```rust @@ -219,6 +248,9 @@ pub enum Token<'a> { /// assert!(is_number("-1")); /// assert!(is_number("-1.5")); /// + /// // with suffix + /// assert!(is_number("10_usize")); + /// /// // some counterexamples /// assert!(!is_number("`mrow!`")); /// assert!(!is_number("true")); diff --git a/logparse/src/display.rs b/logparse/src/display.rs index 728f489..2bb2bc0 100644 --- a/logparse/src/display.rs +++ b/logparse/src/display.rs @@ -126,7 +126,17 @@ impl<'a> Display for Path<'a> { impl<'a> Display for Number<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + let Self { + number, + suffix_without_underscore, + } = self; + write!(f, "{number}")?; + if let Some(suffix) = suffix_without_underscore { + write!(f, "_")?; + write!(f, "{suffix}")?; + } + + Ok(()) } } @@ -155,6 +165,7 @@ impl<'a> Display for Token<'a> { } => write!(f, "{before}{space_before}{separator}{after}"), Token::Delimited(delimited) => write!(f, "{delimited}"), Token::String(s) => write!(f, "{s}"), + Token::Lifetime(s) => write!(f, "'{s}"), } } } diff --git a/logparse/src/parse.rs b/logparse/src/parse.rs index fd24dd9..78dac59 100644 --- a/logparse/src/parse.rs +++ b/logparse/src/parse.rs @@ -130,12 +130,14 @@ impl<'a> FileName<'a> { } } - let (new_segment, ext_excluding_dot) = - if let Some((segment, ext_excluding_dot)) = rsplit(segment.segment.clone(), '.') { - (segment, Some(ext_excluding_dot)) - } else { - (segment.segment, None) - }; + let (new_segment, ext_excluding_dot) = if let Some((segment, ext_excluding_dot)) = + rsplit(segment.segment.clone(), '.') + && !ext_excluding_dot.is_empty() + { + (segment, Some(ext_excluding_dot)) + } else { + (segment.segment, None) + }; Self { leading_separator: segment.leading_separator, @@ -187,7 +189,7 @@ impl<'a> Path<'a> { }); let drive = opt(( - any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z' | 'a' ..= 'z')), + any::<&'a str, E>.verify(|x: &char| x.is_ascii_alphabetic()), ':', )) .map(|i| i.map(|(letter, _): (char, char)| letter)); @@ -219,6 +221,13 @@ impl<'a> Path<'a> { } }) .verify(|i| { + // just ".." isn't valid + if i.segments.is_empty() + && (i.filename.segment == ".." || i.filename.segment == ".") + { + return false; + } + !i.segments.is_empty() || i.filename.ext_excluding_dot.is_some() || !matches!(i.filename.leading_separator, PathSep::None) @@ -228,20 +237,25 @@ impl<'a> Path<'a> { } impl<'a> Atom<'a> { - fn parse, T: 'a>( - terminated_by: impl Parser<&'a str, T, E>, + fn parse, T: 'a, P: Parser<&'a str, T, E>>( + terminated_by: impl (Fn() -> P) + 'a, ) -> impl Parser<&'a str, Self, E> { use winnow::{combinator::*, prelude::*, token::*}; - let text = repeat::<_, _, Cow<'a, str>, _, _>( - 1.., - ( - peek(not(terminated_by)), - any::<&str, _>.verify(move |i: &char| !(*i).is_whitespace()), + let text = ( + not(peek(terminated_by())), + repeat_till::<_, _, Cow<'a, str>, _, _, _, _>( + 1.., + any::<&str, _>, + peek(alt(( + terminated_by().value(()), + any::<&str, _>.verify(|i| i.is_whitespace()).value(()), + eof::<&str, _>.value(()), + ))), ) - .map(|(_, i)| i), + .map(|(i, _)| Self::Text(i)), ) - .map(Self::Text); + .map(|(_, x)| x); trace("atom", alt((text,))) } @@ -249,11 +263,30 @@ impl<'a> Atom<'a> { impl<'a> Token<'a> { fn parse_without_separator + 'a>() -> impl Parser<&'a str, Self, E> { - use winnow::{combinator::*, prelude::*}; + use winnow::{combinator::*, prelude::*, token::*}; let delimited: Box> = Box::new(Delimited::parse().map(Self::Delimited)); + let lifetime = ( + "'", + alt(( + repeat::<_, _, Cow<'a, str>, _, _>( + 1..=3, + any.verify(|i: &char| i.is_ascii_lowercase() || *i == '_'), + ) + .take(), + "{erased}", + )), + peek(alt(( + any::<&str, _> + .verify(|i: &char| !i.is_alphabetic() && *i != '\'') + .value(()), + eof::<&str, _>.value(()), + ))), + ) + .map(|(_, lifetime, _)| Token::Lifetime(lifetime.into())); + trace( "token-without-sep", alt(( @@ -262,10 +295,18 @@ impl<'a> Token<'a> { "None".value(Self::None), Number::parse().map(Self::Number), Path::parse().map(Self::Path), + lifetime, AnyString::parse().map(Self::String), delimited, - Atom::parse(alt((Separator::parse().value(""), ")", "]", "}", ">"))) - .map(Self::Atom), + Atom::parse(|| { + alt(( + Separator::parse().value(""), + alt((")", "]", "}", ">")), + alt(("(", "[", "{", "<")), + alt(("`", "'", "\"")), + )) + }) + .map(Self::Atom), )), ) } @@ -300,7 +341,7 @@ impl<'a> Token<'a> { before } }), - Atom::parse(fail::<_, (), _>).map(Self::Atom), + Atom::parse(|| fail::<_, (), _>).map(Self::Atom), )), )) } @@ -314,7 +355,14 @@ impl<'a> Delimited<'a> { "delimited", ( opt(( - Atom::parse(alt(("(", "[", "{", "<", Separator::parse().value("")))), + Atom::parse(|| { + alt(( + Separator::parse().value(""), + alt((")", "]", "}", ">")), + alt(("(", "[", "{", "<")), + alt(("`", "'", "\"")), + )) + }), Space::parse(), )), alt(( @@ -365,9 +413,21 @@ impl<'a> Number<'a> { "number", ( alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())), + opt(( + "_", + repeat::<_, _, Cow<'a, str>, _, _>( + 1.., + any::<&'a str, E>.verify(|x| x.is_alphanumeric()), + ) + .take(), + ) + .map(|(_, suffix): (_, &str)| suffix)), peek(not(any::<&'a str, E>.verify(|x: &char| x.is_alphabetic()))), ) - .map(|(i, _): (&str, _)| Self(i.into())), + .map(|(number, suffix, _): (&str, _, _)| Self { + number: number.into(), + suffix_without_underscore: suffix.map(Into::into), + }), ) } } @@ -418,6 +478,152 @@ mod tests { parse_input(input).unwrap() } + #[test] + fn parse_twodots() { + assert_debug_snapshot!(parse(r#".."#), @r#" + Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + "..", + ), + ), + }, + ], + trailing_space: Space( + "", + ), + } + "#); + } + + #[test] + fn parse_dot() { + assert_debug_snapshot!(parse(r#"."#), @r#" + Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + ".", + ), + ), + }, + ], + trailing_space: Space( + "", + ), + } + "#); + } + + #[test] + fn parse_parent() { + assert_debug_snapshot!(parse_path_only(r#"../foo.rs"#), @r#" + Path { + drive_excluding_colon: None, + segments: [ + PathSegment { + leading_separator: None, + segment: "..", + }, + ], + filename: FileName { + leading_separator: Slash, + segment: "foo", + ext_excluding_dot: Some( + "rs", + ), + location: None, + }, + } + "#); + } + + #[test] + fn parse_cwd() { + assert_debug_snapshot!(parse_path_only(r#"./foo.rs"#), @r#" + Path { + drive_excluding_colon: None, + segments: [ + PathSegment { + leading_separator: None, + segment: ".", + }, + ], + filename: FileName { + leading_separator: Slash, + segment: "foo", + ext_excluding_dot: Some( + "rs", + ), + location: None, + }, + } + "#); + } + + #[test] + fn parse_cwd_in_path() { + assert_debug_snapshot!(parse_path_only(r#"foo/./foo.rs"#), @r#" + Path { + drive_excluding_colon: None, + segments: [ + PathSegment { + leading_separator: None, + segment: "foo", + }, + PathSegment { + leading_separator: Slash, + segment: ".", + }, + ], + filename: FileName { + leading_separator: Slash, + segment: "foo", + ext_excluding_dot: Some( + "rs", + ), + location: None, + }, + } + "#); + } + + #[test] + fn parse_parent_in_path() { + assert_debug_snapshot!(parse_path_only(r#"foo/../foo.rs"#), @r#" + Path { + drive_excluding_colon: None, + segments: [ + PathSegment { + leading_separator: None, + segment: "foo", + }, + PathSegment { + leading_separator: Slash, + segment: "..", + }, + ], + filename: FileName { + leading_separator: Slash, + segment: "foo", + ext_excluding_dot: Some( + "rs", + ), + location: None, + }, + } + "#); + } + #[test] fn parse_path() { assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs"#), @r#" @@ -499,6 +705,89 @@ mod tests { "#) } + #[test] + fn parse_delimited_separated() { + assert_debug_snapshot!(parse(r#"a = Struct { b = 3 }"#), @r#" + Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "a", + ), + ), + space_before: Space( + " ", + ), + separator: Eq, + after: Segment { + leading_space: Space( + " ", + ), + token: Delimited( + Delimited { + prefix: Some( + ( + Text( + "Struct", + ), + Space( + " ", + ), + ), + ), + delimiter: Brace, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + " ", + ), + token: Separated { + before: Atom( + Text( + "b", + ), + ), + space_before: Space( + " ", + ), + separator: Eq, + after: Segment { + leading_space: Space( + " ", + ), + token: Number( + Number { + number: "3", + suffix_without_underscore: None, + }, + ), + }, + }, + }, + ], + trailing_space: Space( + " ", + ), + }, + }, + ), + }, + }, + }, + ], + trailing_space: Space( + "", + ), + } + "#) + } + #[test] fn parse_text() { assert_debug_snapshot!(parse(r#"abc"#), @r#" @@ -766,4 +1055,560 @@ mod tests { } "#); } + + #[test] + fn parse_ex1() { + assert_debug_snapshot!(parse(r#"a::>(a = 3_usize, b = 3_usize)"#), @r#" + Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "a", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: None, + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "b", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "c", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: Some( + ( + Text( + "Generalizer", + ), + Space( + "", + ), + ), + ), + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Lifetime( + "_", + ), + }, + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + ",", + ), + ), + }, + Segment { + leading_space: Space( + " ", + ), + token: Lifetime( + "_", + ), + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + }, + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: None, + delimiter: Paren, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "a", + ), + ), + space_before: Space( + " ", + ), + separator: Eq, + after: Segment { + leading_space: Space( + " ", + ), + token: Number( + Number { + number: "3", + suffix_without_underscore: Some( + "usize", + ), + }, + ), + }, + }, + }, + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + ",", + ), + ), + }, + Segment { + leading_space: Space( + " ", + ), + token: Separated { + before: Atom( + Text( + "b", + ), + ), + space_before: Space( + " ", + ), + separator: Eq, + after: Segment { + leading_space: Space( + " ", + ), + token: Number( + Number { + number: "3", + suffix_without_underscore: Some( + "usize", + ), + }, + ), + }, + }, + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + ], + trailing_space: Space( + "", + ), + } + "#); + } + + #[test] + fn parse_ex2() { + assert_debug_snapshot!(parse(r#"super_combine_consts::, rustc_middle::ty::context::TyCtxt<'_>>>(?1c, ?2c)"#), @r#" + Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "super_combine_consts", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: None, + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "rustc_type_ir", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "relate", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "solver_relating", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: Some( + ( + Text( + "SolverRelating", + ), + Space( + "", + ), + ), + ), + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Lifetime( + "_", + ), + }, + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + ",", + ), + ), + }, + Segment { + leading_space: Space( + " ", + ), + token: Separated { + before: Atom( + Text( + "rustc_infer", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "infer", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: Some( + ( + Text( + "InferCtxt", + ), + Space( + "", + ), + ), + ), + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Lifetime( + "_", + ), + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + }, + }, + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + ",", + ), + ), + }, + Segment { + leading_space: Space( + " ", + ), + token: Separated { + before: Atom( + Text( + "rustc_middle", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "ty", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Separated { + before: Atom( + Text( + "context", + ), + ), + space_before: Space( + "", + ), + separator: DoubleColon, + after: Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: Some( + ( + Text( + "TyCtxt", + ), + Space( + "", + ), + ), + ), + delimiter: Angle, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Lifetime( + "_", + ), + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + }, + }, + }, + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + }, + }, + }, + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + }, + }, + Segment { + leading_space: Space( + "", + ), + token: Delimited( + Delimited { + prefix: None, + delimiter: Paren, + contents: Segments { + segments: [ + Segment { + leading_space: Space( + "", + ), + token: Atom( + Text( + "?1c,", + ), + ), + }, + Segment { + leading_space: Space( + " ", + ), + token: Atom( + Text( + "?2c", + ), + ), + }, + ], + trailing_space: Space( + "", + ), + }, + }, + ), + }, + ], + trailing_space: Space( + "", + ), + } + "#) + } } diff --git a/logparse/src/proptesting.rs b/logparse/src/proptesting.rs index 816283c..cbc164d 100644 --- a/logparse/src/proptesting.rs +++ b/logparse/src/proptesting.rs @@ -119,10 +119,18 @@ impl Path<'static> { impl Number<'static> { #[cfg(test)] fn arb() -> impl Strategy { - prop_oneof![ - any::().prop_map(|number| Self(number.to_string().into())), - any::().prop_map(|number| Self(number.to_string().into())) - ] + use proptest::option::*; + ( + prop_oneof![ + any::().prop_map(|number| number.to_string()), + any::().prop_map(|number| number.to_string()) + ], + of("[a-zA-Z0-9]"), + ) + .prop_map(|(a, b)| Self { + number: a.into(), + suffix_without_underscore: b.map(Into::into), + }) } } diff --git a/logparse/src/spans.rs b/logparse/src/spans.rs index 59844a7..b696069 100644 --- a/logparse/src/spans.rs +++ b/logparse/src/spans.rs @@ -14,6 +14,8 @@ pub enum SpanKind { Number, /// Known literals, like `true`, `false`, `None`, `Ok`, `Err` Literal, + /// Lifetimes (`'foo`) + Lifetime, /// Strings String, /// Paths @@ -25,8 +27,9 @@ pub enum SpanKind { /// i.e. `Some` in `Some(3)` Constructor, - /// String prefix, suffix, hashtags, etc - StringSurroundings, + /// String prefix, suffix, hashtags, etc. + /// Also number suffix + Surroundings, /// Any other text (the default) Text, @@ -114,9 +117,9 @@ mod private { num_hashtags, suffix, } = self; - cx.push(prefix, SpanKind::StringSurroundings); + cx.push(prefix, SpanKind::Surroundings); for _ in 0..num_hashtags { - cx.push("#", SpanKind::StringSurroundings) + cx.push("#", SpanKind::Surroundings) } ty.into_spans(cx); @@ -124,9 +127,9 @@ mod private { ty.into_spans(cx); for _ in 0..num_hashtags { - cx.push("#", SpanKind::StringSurroundings) + cx.push("#", SpanKind::Surroundings) } - cx.push(suffix, SpanKind::StringSurroundings); + cx.push(suffix, SpanKind::Surroundings); } } @@ -138,7 +141,12 @@ mod private { impl<'a> IntoSpansImpl<'a> for Number<'a> { fn into_spans(self, cx: &mut Context<'a>) { - cx.push(self.0, SpanKind::Number) + cx.push(self.number, SpanKind::Number); + + if let Some(suffix) = self.suffix_without_underscore { + cx.push("_", SpanKind::Surroundings); + cx.push(suffix, SpanKind::Surroundings); + } } } @@ -187,6 +195,10 @@ mod private { Token::Atom(atom) => { atom.into_spans(cx); } + Token::Lifetime(lifetime) => { + cx.push("'", SpanKind::Surroundings); + cx.push(lifetime, SpanKind::Lifetime); + } } } } @@ -228,10 +240,10 @@ mod private { cx.push( text, match delimiter { - Delimiter::Bracket => SpanKind::Constructor, + Delimiter::Brace => SpanKind::Constructor, Delimiter::Paren if space.0.is_empty() => SpanKind::Constructor, Delimiter::Paren => SpanKind::Text, - Delimiter::Brace => SpanKind::Text, + Delimiter::Bracket => SpanKind::Text, Delimiter::Angle if space.0.is_empty() => SpanKind::Constructor, Delimiter::Angle => SpanKind::Text, }, @@ -336,7 +348,7 @@ mod tests { ), ( "unsized_coercion", - Constructor, + Text, ), ( "[", @@ -398,7 +410,7 @@ mod tests { ), ( "", - StringSurroundings, + Surroundings, ), ( "\"", @@ -414,7 +426,7 @@ mod tests { ), ( "", - StringSurroundings, + Surroundings, ), ( ")", @@ -476,7 +488,7 @@ mod tests { ), ( "core", - Constructor, + Text, ), ( "[", @@ -572,7 +584,7 @@ mod tests { ), ( "core", - Constructor, + Text, ), ( "[", @@ -670,7 +682,7 @@ mod tests { ), ( "unsized_coercion", - Constructor, + Text, ), ( "[", @@ -884,7 +896,7 @@ mod tests { ), ( "alloc", - Constructor, + Text, ), ( "[", diff --git a/src/tui/filter.rs b/src/tui/filter.rs index 7762efa..db2e9f0 100644 --- a/src/tui/filter.rs +++ b/src/tui/filter.rs @@ -76,9 +76,7 @@ impl Matcher { .spans() .find(span, name) .is_some_and(|v| value.matches(v)), - Matcher::Message { value } => { - entry.message_or_name().is_some_and(|v| value.matches(&v)) - } + Matcher::Message { value } => entry.message_or_name().is_some_and(|v| value.matches(v)), } } diff --git a/src/tui/log_viewer/filters.rs b/src/tui/log_viewer/filters.rs index f776067..d7bbac8 100644 --- a/src/tui/log_viewer/filters.rs +++ b/src/tui/log_viewer/filters.rs @@ -30,7 +30,7 @@ impl Filters { if let Some(path) = &path && path.exists() { - match File::open(&path) { + match File::open(path) { Ok(f) => match serde_json::from_reader(f) { Ok(i) => { return Self { @@ -73,7 +73,7 @@ impl Filters { .create(true) .write(true) .truncate(true) - .open(&path) + .open(path) { Ok(f) => { if let Err(e) = serde_json::to_writer(f, self) { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index f0e8779..cd2eaf2 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -699,30 +699,6 @@ impl Widget for &mut App { FieldTree::new(lv, footer_focused) .styled_mut(&styles) .render(footer_area, buf); - - // let items = lv.footer_fields(); - // lv.last_fields_offset = footer_area.y as usize; - // lv.last_fields_height = items.len(); - // - // let width = 20; - // let builder = ListBuilder::new(|cx| { - // let Some((k, v)) = &items.get(cx.index) else { - // return (Paragraph::new(""), 1); - // }; - // - // let mut res = - // Paragraph::new(format!("{k:width$} {v}")).wrap(Wrap { trim: false }); - // - // if cx.is_selected { - // res = res.style(styles.highlighted); - // } - // - // let height = res.line_count(footer_area.width) as u16; - // (res, height) - // }); - // - // let list = ListView::new(builder, items.len()).style(styles.default); - // StatefulWidget::render(list, footer_area, buf, &mut lv.footer_list); } Tab::Empty => {} Tab::Help => { diff --git a/src/tui/reader.rs b/src/tui/reader.rs index 401ce32..b4ab2a8 100644 --- a/src/tui/reader.rs +++ b/src/tui/reader.rs @@ -424,7 +424,7 @@ mod tests { println!("undo"); f.undo(); c.update_with_parents(&f); - assert_eq!(c.curr().message_or_name(), Some("nest")); + assert_eq!(c.curr().message_or_name(), Some("enter")); assert!(c.next(&f)); assert_eq!(c.curr().message_or_name(), Some("bar")); assert!(!c.next(&f)); diff --git a/src/tui/widgets/items.rs b/src/tui/widgets/items.rs index 8fc568c..c57237c 100644 --- a/src/tui/widgets/items.rs +++ b/src/tui/widgets/items.rs @@ -97,7 +97,7 @@ impl Widget for Styled<'_, &Items<'_>> { .selected() .as_ref() .and_then(|i| i.message_or_name()) - .is_some_and(|m| &m == msg) + .is_some_and(|m| m == msg) { line.highlight(Highlighted::All); } diff --git a/src/tui/widgets/line_text.rs b/src/tui/widgets/line_text.rs index 4913bcb..12c67a9 100644 --- a/src/tui/widgets/line_text.rs +++ b/src/tui/widgets/line_text.rs @@ -165,12 +165,13 @@ pub fn style_span(kind: SpanKind, style: Style, styles: &Styles) -> Style { SpanKind::Delimiter(_) => style.fg(styles.delimiter).bold(), SpanKind::Separator => style.fg(styles.faded), SpanKind::Number => style.fg(styles.literal), - SpanKind::Literal => style.fg(styles.literal).dim(), + SpanKind::Literal => style.fg(styles.literal), SpanKind::String => style.fg(styles.string), SpanKind::Path => style.fg(styles.literal).underlined(), SpanKind::Space(_) => style, SpanKind::Constructor => style.fg(styles.literal), - SpanKind::StringSurroundings => style.fg(styles.faded), + SpanKind::Surroundings => style.fg(styles.faded), + SpanKind::Lifetime => style.fg(styles.faded), SpanKind::Text => style, } }