move and turn into spans
This commit is contained in:
parent
bedaa49754
commit
9f401bda53
13 changed files with 1262 additions and 774 deletions
|
|
@ -5,3 +5,5 @@
|
||||||
# It is recommended to check this file in to source control so that
|
# It is recommended to check this file in to source control so that
|
||||||
# everyone who runs the test benefits from these saved cases.
|
# everyone who runs the test benefits from these saved cases.
|
||||||
cc 255ec5cdb18e04e16d465fb94cbf15040fb1d5d704c53226ce3a2e9333c6de7c # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: None, location: None } }) }], trailing_space: Space("") }
|
cc 255ec5cdb18e04e16d465fb94cbf15040fb1d5d704c53226ce3a2e9333c6de7c # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: None, location: None } }) }], trailing_space: Space("") }
|
||||||
|
cc 809c7a1b555b4e553cc8855c8a27de7a87e94c0d22b55ae9d7f6d4dff5f6cde0 # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: Text("a"), delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(" "), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: Some("\\"), location: None } }) }, Segment { leading_space: Space(" "), token: True }], trailing_space: Space("") } }) }], trailing_space: Space("") }
|
||||||
|
cc 0b39a0bcfdada4d65ebbcbc80ed000c6b9cc1b60e7de6ffd004a7ee7db350d6c # shrinks to original = Segments { segments: [Segment { leading_space: Space(""), token: Delimited(Delimited { prefix: Text("A"), delimiter: Paren, contents: Segments { segments: [Segment { leading_space: Space(""), token: Path(Path { drive_excluding_colon: None, segments: [], filename: FileName { leading_separator: Slash, segment: "", ext_excluding_dot: Some(":"), location: None } }) }], trailing_space: Space(" ") } }) }], trailing_space: Space("") }
|
||||||
|
|
|
||||||
7
proptest-regressions/format_debug_output/proptesting.txt
Normal file
7
proptest-regressions/format_debug_output/proptesting.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# 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 1dac24f74cdeb63f61d662876f276058bc71481f1df552aeea1293e22b682d59 # shrinks to original = "¡{}"
|
||||||
|
|
@ -1,756 +0,0 @@
|
||||||
use std::{
|
|
||||||
borrow::Cow,
|
|
||||||
fmt::{self, Display},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use proptest::prelude::*;
|
|
||||||
|
|
||||||
use proptest_derive::Arbitrary;
|
|
||||||
use winnow::{Parser, error::ParserError};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum Separator {
|
|
||||||
Eq,
|
|
||||||
Colon,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Separator {
|
|
||||||
fn parser<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> Result<Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
alt((
|
|
||||||
literal('=').value(Self::Eq),
|
|
||||||
literal(':').value(Self::Colon),
|
|
||||||
))
|
|
||||||
.parse_next(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Separator {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Separator::Eq => write!(f, "="),
|
|
||||||
Separator::Colon => write!(f, ":"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Arbitrary, PartialEq)]
|
|
||||||
pub enum QuoteType {
|
|
||||||
Single,
|
|
||||||
Double,
|
|
||||||
Backtick,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuoteType {
|
|
||||||
fn parse<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> Result<Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
alt((
|
|
||||||
literal('\'').value(Self::Single),
|
|
||||||
literal('\"').value(Self::Double),
|
|
||||||
literal('`').value(Self::Backtick),
|
|
||||||
))
|
|
||||||
.parse_next(input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for QuoteType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
QuoteType::Single => write!(f, "\""),
|
|
||||||
QuoteType::Double => write!(f, "\'"),
|
|
||||||
QuoteType::Backtick => write!(f, "`"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Arbitrary, PartialEq)]
|
|
||||||
pub enum Delimiter {
|
|
||||||
Paren,
|
|
||||||
Bracket,
|
|
||||||
Brace,
|
|
||||||
Angle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Delimiter {
|
|
||||||
fn fmt_start(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Delimiter::Paren => write!(f, "("),
|
|
||||||
Delimiter::Bracket => write!(f, "["),
|
|
||||||
Delimiter::Brace => write!(f, "{{"),
|
|
||||||
Delimiter::Angle => write!(f, "<"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_end(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Delimiter::Paren => write!(f, ")"),
|
|
||||||
Delimiter::Bracket => write!(f, "]"),
|
|
||||||
Delimiter::Brace => write!(f, "}}"),
|
|
||||||
Delimiter::Angle => write!(f, ">"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct AnyString<'a> {
|
|
||||||
prefix: Cow<'a, str>,
|
|
||||||
ty: QuoteType,
|
|
||||||
contents: Cow<'a, str>,
|
|
||||||
num_hashtags: usize,
|
|
||||||
suffix: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnyString<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
let prefix = "\\w*";
|
|
||||||
let ty = any::<QuoteType>();
|
|
||||||
let contents = "\\w*";
|
|
||||||
let num_hashtags = 0usize..3;
|
|
||||||
let suffix = "\\w*";
|
|
||||||
|
|
||||||
(prefix, ty, contents, num_hashtags, suffix).prop_map(
|
|
||||||
|(prefix, ty, contents, num_hashtags, suffix)| Self {
|
|
||||||
prefix: prefix.into(),
|
|
||||||
ty,
|
|
||||||
contents: contents.into(),
|
|
||||||
num_hashtags,
|
|
||||||
suffix: suffix.into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> AnyString<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
// let (prefix, num_hashtags, quote) =
|
|
||||||
let preamble = (
|
|
||||||
take_while(0.., |b: char| !b.is_whitespace()),
|
|
||||||
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
|
|
||||||
alt((
|
|
||||||
'`'.value(QuoteType::Backtick),
|
|
||||||
'\''.value(QuoteType::Single),
|
|
||||||
'\"'.value(QuoteType::Double),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
preamble.flat_map(
|
|
||||||
|(prefix, num_hashtags, quote): (&'a str, usize, QuoteType)| {
|
|
||||||
let end = (
|
|
||||||
match quote {
|
|
||||||
QuoteType::Single => '\'',
|
|
||||||
QuoteType::Double => '\"',
|
|
||||||
QuoteType::Backtick => '`',
|
|
||||||
},
|
|
||||||
repeat::<_, _, Cow<'a, str>, _, _>(num_hashtags..=num_hashtags, literal("#")),
|
|
||||||
);
|
|
||||||
|
|
||||||
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
|
|
||||||
let suffix = take_while(0.., |b: char| !b.is_whitespace());
|
|
||||||
|
|
||||||
(contents, suffix).map(move |(contents, suffix): (Cow<'a, str>, &'a str)| Self {
|
|
||||||
prefix: prefix.into(),
|
|
||||||
ty: quote,
|
|
||||||
contents,
|
|
||||||
num_hashtags,
|
|
||||||
suffix: suffix.into(),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for AnyString<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.prefix)?;
|
|
||||||
for _ in 0..self.num_hashtags {
|
|
||||||
write!(f, "#")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", self.ty)?;
|
|
||||||
write!(f, "{}", self.contents)?;
|
|
||||||
|
|
||||||
for _ in 0..self.num_hashtags {
|
|
||||||
write!(f, "#")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", self.suffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Space<'a>(Cow<'a, str>);
|
|
||||||
|
|
||||||
impl Space<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
" *".prop_map(|spaces| Self(spaces.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Space<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{prelude::*, token::*};
|
|
||||||
|
|
||||||
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Space<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Arbitrary)]
|
|
||||||
pub enum PathSep {
|
|
||||||
Slash,
|
|
||||||
Backslash,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathSep {
|
|
||||||
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*};
|
|
||||||
|
|
||||||
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PathSep {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
PathSep::Slash => write!(f, "/"),
|
|
||||||
PathSep::Backslash => write!(f, "\\"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct PathSegment<'a> {
|
|
||||||
leading_separator: PathSep,
|
|
||||||
segment: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathSegment<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
(any::<PathSep>(), "\\w*").prop_map(|(leading_separator, segment)| Self {
|
|
||||||
leading_separator,
|
|
||||||
segment: segment.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for PathSegment<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.leading_separator)?;
|
|
||||||
write!(f, "{}", self.segment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct FileLocation<'a> {
|
|
||||||
line: Cow<'a, str>,
|
|
||||||
offset: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileLocation<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
use proptest::option::*;
|
|
||||||
("[0-9]{0,4}", of("[0-9]{0,4}")).prop_map(|(line, offset)| Self {
|
|
||||||
line: line.into(),
|
|
||||||
offset: offset.map(Into::into),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for FileLocation<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.line)?;
|
|
||||||
if let Some(offset) = &self.offset {
|
|
||||||
write!(f, "{offset}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct FileName<'a> {
|
|
||||||
leading_separator: PathSep,
|
|
||||||
segment: Cow<'a, str>,
|
|
||||||
ext_excluding_dot: Option<Cow<'a, str>>,
|
|
||||||
location: Option<FileLocation<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileName<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
use proptest::option::*;
|
|
||||||
(
|
|
||||||
any::<PathSep>(),
|
|
||||||
"\\w*",
|
|
||||||
of(".{0,3}"),
|
|
||||||
of(FileLocation::arb()),
|
|
||||||
)
|
|
||||||
.prop_map(
|
|
||||||
|(leading_separator, segment, ext_excluding_dot, location)| Self {
|
|
||||||
leading_separator,
|
|
||||||
segment: segment.into(),
|
|
||||||
ext_excluding_dot: ext_excluding_dot.map(Into::into),
|
|
||||||
location,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FileName<'a> {
|
|
||||||
fn parse(segment: PathSegment<'a>) -> Self {
|
|
||||||
fn rsplit<'a>(
|
|
||||||
input: Cow<'a, str>,
|
|
||||||
delimiter: char,
|
|
||||||
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
|
|
||||||
match input {
|
|
||||||
Cow::Borrowed(s) => s
|
|
||||||
.rsplit_once(delimiter)
|
|
||||||
.map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))),
|
|
||||||
Cow::Owned(s) => s
|
|
||||||
.rsplit_once(delimiter)
|
|
||||||
.map(|(a, b)| (Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (rest, location) = if let Some((before, offset)) = rsplit(segment.segment.clone(), ':')
|
|
||||||
{
|
|
||||||
if let Some((before, line)) = rsplit(before.clone(), ':') {
|
|
||||||
(
|
|
||||||
before,
|
|
||||||
Some(FileLocation {
|
|
||||||
line,
|
|
||||||
offset: Some(offset),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
before,
|
|
||||||
Some(FileLocation {
|
|
||||||
line: offset,
|
|
||||||
offset: None,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(segment.segment, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (new_segment, ext_excluding_dot) =
|
|
||||||
if let Some((segment, ext_excluding_dot)) = rsplit(rest.clone(), '.') {
|
|
||||||
(segment, Some(ext_excluding_dot))
|
|
||||||
} else {
|
|
||||||
(rest, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
leading_separator: segment.leading_separator,
|
|
||||||
segment: new_segment,
|
|
||||||
ext_excluding_dot,
|
|
||||||
location,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for FileName<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.leading_separator)?;
|
|
||||||
write!(f, "{}", self.segment)?;
|
|
||||||
if let Some(ext) = &self.ext_excluding_dot {
|
|
||||||
write!(f, ".{ext}")?;
|
|
||||||
}
|
|
||||||
if let Some(loc) = &self.location {
|
|
||||||
write!(f, "{loc}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Path<'a> {
|
|
||||||
drive_excluding_colon: Option<char>,
|
|
||||||
segments: Vec<PathSegment<'a>>,
|
|
||||||
filename: FileName<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Path<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
use proptest::{char::*, collection::*, option::*};
|
|
||||||
(
|
|
||||||
of(range('A', 'Z')),
|
|
||||||
vec(PathSegment::arb(), 0..3),
|
|
||||||
FileName::arb(),
|
|
||||||
)
|
|
||||||
.prop_map(|(drive_excluding_colon, segments, filename)| Self {
|
|
||||||
drive_excluding_colon,
|
|
||||||
segments,
|
|
||||||
filename,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Path<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
let till_next_sep = repeat_till(
|
|
||||||
0..,
|
|
||||||
any::<&'a str, E>.verify(|i: &char| !(*i).is_whitespace()),
|
|
||||||
PathSep::parse(),
|
|
||||||
)
|
|
||||||
.map(|(segment, _)| segment);
|
|
||||||
|
|
||||||
let sep_and_next =
|
|
||||||
(PathSep::parse(), till_next_sep).map(|(leading_separator, segment)| PathSegment {
|
|
||||||
leading_separator,
|
|
||||||
segment,
|
|
||||||
});
|
|
||||||
let drive = opt((
|
|
||||||
any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z')),
|
|
||||||
':',
|
|
||||||
))
|
|
||||||
.map(|i| i.map(|(letter, _): (char, char)| letter));
|
|
||||||
let drive_and_segments = (
|
|
||||||
drive,
|
|
||||||
repeat_till(
|
|
||||||
1..,
|
|
||||||
sep_and_next,
|
|
||||||
any.verify(|i: &char| (*i).is_whitespace()),
|
|
||||||
)
|
|
||||||
.map(|(segments, _): (Vec<PathSegment>, _)| {
|
|
||||||
let (rest, last) = {
|
|
||||||
let mut segments = segments;
|
|
||||||
let last = segments.pop().unwrap();
|
|
||||||
(segments, last)
|
|
||||||
};
|
|
||||||
|
|
||||||
(rest, FileName::parse(last))
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
drive_and_segments.map(|(drive, (segments, filename))| Self {
|
|
||||||
drive_excluding_colon: drive,
|
|
||||||
segments,
|
|
||||||
filename,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Path<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
if let Some(drive) = &self.drive_excluding_colon {
|
|
||||||
write!(f, "{drive}:")?;
|
|
||||||
}
|
|
||||||
for segment in &self.segments {
|
|
||||||
write!(f, "{segment}:")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", self.filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Number<'a>(Cow<'a, str>);
|
|
||||||
|
|
||||||
impl Number<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
prop_oneof![
|
|
||||||
any::<i64>().prop_map(|number| Self(number.to_string().into())),
|
|
||||||
any::<f64>().prop_map(|number| Self(number.to_string().into()))
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Number<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{ascii::*, combinator::*, prelude::*};
|
|
||||||
|
|
||||||
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())).map(|i: &str| Self(i.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Number<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Anything that doesn't contain spaces, and that can be a prefix of `Delimited`.
|
|
||||||
/// i.e. an english word, or rust `::`-separated Path
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Atom<'a> {
|
|
||||||
Text(Cow<'a, str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Atom<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Atom::Text(text) => write!(f, "{text}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Atom<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
"\\w*".prop_map(|i| Self::Text(i.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Atom<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
alt((repeat(0.., any.verify(|i: &char| !(*i).is_whitespace())).map(Self::Text),))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Token<'a> {
|
|
||||||
True,
|
|
||||||
False,
|
|
||||||
None,
|
|
||||||
|
|
||||||
Path(Path<'a>),
|
|
||||||
String(AnyString<'a>),
|
|
||||||
Number(Number<'a>),
|
|
||||||
|
|
||||||
// TODO: RustPath
|
|
||||||
Separated {
|
|
||||||
before: Box<Token<'a>>,
|
|
||||||
space_before: Space<'a>,
|
|
||||||
separator: Separator,
|
|
||||||
after: Box<Segment<'a>>,
|
|
||||||
},
|
|
||||||
Delimited(Delimited<'a>),
|
|
||||||
|
|
||||||
Atom(Atom<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb() -> impl Strategy<Value = Self> {
|
|
||||||
let leaf = prop_oneof![
|
|
||||||
Just(Self::True),
|
|
||||||
Just(Self::False),
|
|
||||||
Just(Self::None),
|
|
||||||
Path::arb().prop_map(Self::Path),
|
|
||||||
AnyString::arb().prop_map(Self::String),
|
|
||||||
Number::arb().prop_map(Self::Number),
|
|
||||||
Atom::arb().prop_map(Self::Atom),
|
|
||||||
];
|
|
||||||
|
|
||||||
leaf.prop_recursive(4, 64, 16, |token| {
|
|
||||||
Delimited::arb(token).prop_map(Self::Delimited).boxed()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Token<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*};
|
|
||||||
|
|
||||||
let delimited: Box<dyn Parser<&'a str, Self, E>> =
|
|
||||||
Box::new(Delimited::parse().map(Self::Delimited));
|
|
||||||
|
|
||||||
alt((
|
|
||||||
"true".value(Self::True),
|
|
||||||
"false".value(Self::False),
|
|
||||||
"None".value(Self::None),
|
|
||||||
Path::parse().map(Self::Path),
|
|
||||||
AnyString::parse().map(Self::String),
|
|
||||||
Number::parse().map(Self::Number),
|
|
||||||
delimited,
|
|
||||||
Atom::parse().map(Self::Atom),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Token<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Token::Number(number) => write!(f, "{number}"),
|
|
||||||
Token::True => write!(f, "true"),
|
|
||||||
Token::False => write!(f, "false"),
|
|
||||||
Token::None => write!(f, "None"),
|
|
||||||
Token::Atom(atom) => write!(f, "{atom}"),
|
|
||||||
Token::Path(path) => write!(f, "{path}"),
|
|
||||||
Token::Separated {
|
|
||||||
before,
|
|
||||||
space_before,
|
|
||||||
separator,
|
|
||||||
after,
|
|
||||||
} => write!(f, "{before}{space_before}{separator}{after}"),
|
|
||||||
Token::Delimited(delimited) => write!(f, "{delimited}"),
|
|
||||||
Token::String(s) => write!(f, "{s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Delimited<'a> {
|
|
||||||
prefix: Atom<'a>,
|
|
||||||
delimiter: Delimiter,
|
|
||||||
contents: Segments<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Delimited<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
|
||||||
(Atom::arb(), any::<Delimiter>(), Segments::arb(token)).prop_map(
|
|
||||||
|(prefix, delimiter, contents)| Self {
|
|
||||||
prefix,
|
|
||||||
delimiter,
|
|
||||||
contents,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Delimited<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::{combinator::*, prelude::*, token::*};
|
|
||||||
|
|
||||||
(
|
|
||||||
Atom::parse(),
|
|
||||||
alt((
|
|
||||||
literal('(').map(|_| literal(')').value(Delimiter::Paren)),
|
|
||||||
literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
|
|
||||||
literal('{').map(|_| literal('}').value(Delimiter::Brace)),
|
|
||||||
))
|
|
||||||
.flat_map(|end| Segments::parse(end)),
|
|
||||||
)
|
|
||||||
.map(|(prefix, (contents, delimiter))| Self {
|
|
||||||
prefix,
|
|
||||||
delimiter,
|
|
||||||
contents,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Delimited<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.prefix)?;
|
|
||||||
self.delimiter.fmt_start(f)?;
|
|
||||||
write!(f, "{}", self.contents)?;
|
|
||||||
self.delimiter.fmt_end(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Segment<'a> {
|
|
||||||
leading_space: Space<'a>,
|
|
||||||
token: Token<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Segment<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
|
||||||
(Space::arb(), token).prop_map(|(leading_space, token)| Self {
|
|
||||||
leading_space,
|
|
||||||
token,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Segment<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
||||||
use winnow::prelude::*;
|
|
||||||
|
|
||||||
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
|
|
||||||
leading_space,
|
|
||||||
token,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Segment<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.leading_space)?;
|
|
||||||
write!(f, "{}", self.token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Segments<'a> {
|
|
||||||
segments: Vec<Segment<'a>>,
|
|
||||||
trailing_space: Space<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Segments<'static> {
|
|
||||||
#[cfg(test)]
|
|
||||||
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
|
||||||
use proptest::collection::*;
|
|
||||||
|
|
||||||
(vec(Segment::arb(token), 1..10), Space::arb()).prop_map(|(segments, trailing_space)| {
|
|
||||||
Self {
|
|
||||||
segments,
|
|
||||||
trailing_space,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Segments<'a> {
|
|
||||||
fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
|
|
||||||
end: impl Parser<&'a str, End, E>,
|
|
||||||
) -> impl Parser<&'a str, (Self, End), E> {
|
|
||||||
use winnow::{combinator::*, prelude::*};
|
|
||||||
|
|
||||||
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|
|
||||||
|(segments, (trailing_space, end)): (Vec<_>, _)| {
|
|
||||||
(
|
|
||||||
Self {
|
|
||||||
segments,
|
|
||||||
trailing_space,
|
|
||||||
},
|
|
||||||
end,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Display for Segments<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
for segment in &self.segments {
|
|
||||||
write!(f, "{segment}")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", self.trailing_space)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
|
|
||||||
use winnow::combinator::eof;
|
|
||||||
Segments::parse(eof::<&str, winnow::error::EmptyError>)
|
|
||||||
.map(|(segments, _)| segments)
|
|
||||||
.parse(i)
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use proptest::proptest;
|
|
||||||
|
|
||||||
use crate::format_debug_output::{Segments, Token, parse_input};
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test]
|
|
||||||
fn test_some_function(original in Segments::arb(Token::arb())) {
|
|
||||||
let stringified = original.to_string();
|
|
||||||
let parsed = parse_input(&stringified).unwrap();
|
|
||||||
assert_eq!(parsed, original, "left:\n `{parsed}`\nright: `{stringified}`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
119
src/format_debug_output/ast.rs
Normal file
119
src/format_debug_output/ast.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
use proptest_derive::Arbitrary;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum Separator {
|
||||||
|
Eq,
|
||||||
|
Colon,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Arbitrary, PartialEq)]
|
||||||
|
pub enum QuoteType {
|
||||||
|
Single,
|
||||||
|
Double,
|
||||||
|
Backtick,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Arbitrary, PartialEq)]
|
||||||
|
pub enum Delimiter {
|
||||||
|
Paren,
|
||||||
|
Bracket,
|
||||||
|
Brace,
|
||||||
|
Angle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct AnyString<'a> {
|
||||||
|
pub prefix: Cow<'a, str>,
|
||||||
|
pub ty: QuoteType,
|
||||||
|
pub contents: Cow<'a, str>,
|
||||||
|
pub num_hashtags: usize,
|
||||||
|
pub suffix: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Space<'a>(pub Cow<'a, str>);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Arbitrary)]
|
||||||
|
pub enum PathSep {
|
||||||
|
Slash,
|
||||||
|
Backslash,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct PathSegment<'a> {
|
||||||
|
pub leading_separator: PathSep,
|
||||||
|
pub segment: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct FileLocation<'a> {
|
||||||
|
pub line: Cow<'a, str>,
|
||||||
|
pub offset: Option<Cow<'a, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct FileName<'a> {
|
||||||
|
pub leading_separator: PathSep,
|
||||||
|
pub segment: Cow<'a, str>,
|
||||||
|
pub ext_excluding_dot: Option<Cow<'a, str>>,
|
||||||
|
pub location: Option<FileLocation<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Path<'a> {
|
||||||
|
pub drive_excluding_colon: Option<char>,
|
||||||
|
pub segments: Vec<PathSegment<'a>>,
|
||||||
|
pub filename: FileName<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Number<'a>(pub Cow<'a, str>);
|
||||||
|
|
||||||
|
/// Anything that doesn't contain spaces, and that can be a prefix of `Delimited`.
|
||||||
|
/// i.e. an english word, or rust `::`-separated Path
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Atom<'a> {
|
||||||
|
Text(Cow<'a, str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Token<'a> {
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
None,
|
||||||
|
|
||||||
|
Path(Path<'a>),
|
||||||
|
String(AnyString<'a>),
|
||||||
|
Number(Number<'a>),
|
||||||
|
|
||||||
|
// TODO: RustPath
|
||||||
|
Separated {
|
||||||
|
before: Box<Token<'a>>,
|
||||||
|
space_before: Space<'a>,
|
||||||
|
separator: Separator,
|
||||||
|
after: Box<Segment<'a>>,
|
||||||
|
},
|
||||||
|
Delimited(Delimited<'a>),
|
||||||
|
|
||||||
|
Atom(Atom<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Delimited<'a> {
|
||||||
|
pub prefix: Atom<'a>,
|
||||||
|
pub delimiter: Delimiter,
|
||||||
|
pub contents: Segments<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Segment<'a> {
|
||||||
|
pub leading_space: Space<'a>,
|
||||||
|
pub token: Token<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Segments<'a> {
|
||||||
|
pub segments: Vec<Segment<'a>>,
|
||||||
|
pub trailing_space: Space<'a>,
|
||||||
|
}
|
||||||
176
src/format_debug_output/display.rs
Normal file
176
src/format_debug_output/display.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
use super::ast::*;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
impl Display for Separator {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Separator::Eq => write!(f, "="),
|
||||||
|
Separator::Colon => write!(f, ":"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for QuoteType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
QuoteType::Single => write!(f, "\'"),
|
||||||
|
QuoteType::Double => write!(f, "\""),
|
||||||
|
QuoteType::Backtick => write!(f, "`"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Delimiter {
|
||||||
|
fn fmt_start(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Delimiter::Paren => write!(f, "("),
|
||||||
|
Delimiter::Bracket => write!(f, "["),
|
||||||
|
Delimiter::Brace => write!(f, "{{"),
|
||||||
|
Delimiter::Angle => write!(f, "<"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_end(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Delimiter::Paren => write!(f, ")"),
|
||||||
|
Delimiter::Bracket => write!(f, "]"),
|
||||||
|
Delimiter::Brace => write!(f, "}}"),
|
||||||
|
Delimiter::Angle => write!(f, ">"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for AnyString<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.prefix)?;
|
||||||
|
for _ in 0..self.num_hashtags {
|
||||||
|
write!(f, "#")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", self.ty)?;
|
||||||
|
write!(f, "{}", self.contents)?;
|
||||||
|
write!(f, "{}", self.ty)?;
|
||||||
|
|
||||||
|
for _ in 0..self.num_hashtags {
|
||||||
|
write!(f, "#")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", self.suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Space<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PathSep {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
PathSep::Slash => write!(f, "/"),
|
||||||
|
PathSep::Backslash => write!(f, "\\"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for PathSegment<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.leading_separator)?;
|
||||||
|
write!(f, "{}", self.segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for FileLocation<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, ":{}", self.line)?;
|
||||||
|
if let Some(offset) = &self.offset {
|
||||||
|
write!(f, ":{offset}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for FileName<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.leading_separator)?;
|
||||||
|
write!(f, "{}", self.segment)?;
|
||||||
|
if let Some(ext) = &self.ext_excluding_dot {
|
||||||
|
write!(f, ".{ext}")?;
|
||||||
|
}
|
||||||
|
if let Some(loc) = &self.location {
|
||||||
|
write!(f, "{loc}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Path<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(drive) = &self.drive_excluding_colon {
|
||||||
|
write!(f, "{drive}:")?;
|
||||||
|
}
|
||||||
|
for segment in &self.segments {
|
||||||
|
write!(f, "{segment}")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", self.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Number<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Atom<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Atom::Text(text) => write!(f, "{text}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Token<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::Number(number) => write!(f, "{number}"),
|
||||||
|
Token::True => write!(f, "true"),
|
||||||
|
Token::False => write!(f, "false"),
|
||||||
|
Token::None => write!(f, "None"),
|
||||||
|
Token::Atom(atom) => write!(f, "{atom}"),
|
||||||
|
Token::Path(path) => write!(f, "{path}"),
|
||||||
|
Token::Separated {
|
||||||
|
before,
|
||||||
|
space_before,
|
||||||
|
separator,
|
||||||
|
after,
|
||||||
|
} => write!(f, "{before}{space_before}{separator}{after}"),
|
||||||
|
Token::Delimited(delimited) => write!(f, "{delimited}"),
|
||||||
|
Token::String(s) => write!(f, "{s}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Delimited<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.prefix)?;
|
||||||
|
self.delimiter.fmt_start(f)?;
|
||||||
|
write!(f, "{}", self.contents)?;
|
||||||
|
self.delimiter.fmt_end(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Segment<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.leading_space)?;
|
||||||
|
write!(f, "{}", self.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Segments<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for segment in &self.segments {
|
||||||
|
write!(f, "{segment}")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", self.trailing_space)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/format_debug_output/mod.rs
Normal file
10
src/format_debug_output/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
pub mod ast;
|
||||||
|
mod display;
|
||||||
|
mod parse;
|
||||||
|
mod spans;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod proptesting;
|
||||||
|
|
||||||
|
pub use parse::parse_input;
|
||||||
|
pub use spans::{Config, Kind as SpanKind, Span, into_spans};
|
||||||
320
src/format_debug_output/parse.rs
Normal file
320
src/format_debug_output/parse.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use super::ast::*;
|
||||||
|
use winnow::{Parser, combinator::trace, error::ParserError};
|
||||||
|
|
||||||
|
impl<'a> AnyString<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*, token::*};
|
||||||
|
|
||||||
|
// let (prefix, num_hashtags, quote) =
|
||||||
|
let preamble = (
|
||||||
|
take_while(0.., |b: char| !b.is_whitespace()),
|
||||||
|
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
|
||||||
|
alt((
|
||||||
|
'`'.value(QuoteType::Backtick),
|
||||||
|
'\''.value(QuoteType::Single),
|
||||||
|
'\"'.value(QuoteType::Double),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"string",
|
||||||
|
preamble.flat_map(
|
||||||
|
|(prefix, num_hashtags, quote): (&'a str, usize, QuoteType)| {
|
||||||
|
let end = (
|
||||||
|
match quote {
|
||||||
|
QuoteType::Single => '\'',
|
||||||
|
QuoteType::Double => '\"',
|
||||||
|
QuoteType::Backtick => '`',
|
||||||
|
},
|
||||||
|
repeat::<_, _, Cow<'a, str>, _, _>(
|
||||||
|
num_hashtags..=num_hashtags,
|
||||||
|
literal("#"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
|
||||||
|
let suffix = take_while(0.., |b: char| !b.is_whitespace());
|
||||||
|
|
||||||
|
(contents, suffix).map(move |(contents, suffix): (Cow<'a, str>, &'a str)| {
|
||||||
|
Self {
|
||||||
|
prefix: prefix.into(),
|
||||||
|
ty: quote,
|
||||||
|
contents,
|
||||||
|
num_hashtags,
|
||||||
|
suffix: suffix.into(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Space<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{prelude::*, token::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"space",
|
||||||
|
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathSep {
|
||||||
|
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"pathsep",
|
||||||
|
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Separator {
|
||||||
|
fn parser<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*, token::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"separator",
|
||||||
|
alt((
|
||||||
|
literal('=').value(Self::Eq),
|
||||||
|
literal(':').value(Self::Colon),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FileName<'a> {
|
||||||
|
fn parse(segment: PathSegment<'a>) -> Self {
|
||||||
|
fn rsplit<'a>(
|
||||||
|
input: Cow<'a, str>,
|
||||||
|
delimiter: char,
|
||||||
|
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
|
||||||
|
match input {
|
||||||
|
Cow::Borrowed(s) => s
|
||||||
|
.rsplit_once(delimiter)
|
||||||
|
.map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))),
|
||||||
|
Cow::Owned(s) => s
|
||||||
|
.rsplit_once(delimiter)
|
||||||
|
.map(|(a, b)| (Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rest, location) = if let Some((before, offset)) = rsplit(segment.segment.clone(), ':')
|
||||||
|
{
|
||||||
|
if let Some((before, line)) = rsplit(before.clone(), ':') {
|
||||||
|
(
|
||||||
|
before,
|
||||||
|
Some(FileLocation {
|
||||||
|
line,
|
||||||
|
offset: Some(offset),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
before,
|
||||||
|
Some(FileLocation {
|
||||||
|
line: offset,
|
||||||
|
offset: None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(segment.segment, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (new_segment, ext_excluding_dot) =
|
||||||
|
if let Some((segment, ext_excluding_dot)) = rsplit(rest.clone(), '.') {
|
||||||
|
(segment, Some(ext_excluding_dot))
|
||||||
|
} else {
|
||||||
|
(rest, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
leading_separator: segment.leading_separator,
|
||||||
|
segment: new_segment,
|
||||||
|
ext_excluding_dot,
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Path<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*, token::*};
|
||||||
|
|
||||||
|
let till_next_sep = repeat_till(
|
||||||
|
0..,
|
||||||
|
any::<&'a str, E>.verify(|i: &char| !(*i).is_whitespace()),
|
||||||
|
peek(alt((
|
||||||
|
PathSep::parse().value(()),
|
||||||
|
any::<&'a str, E>
|
||||||
|
.verify(|i: &char| (*i).is_whitespace())
|
||||||
|
.value(()),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.map(|(segment, _)| segment);
|
||||||
|
|
||||||
|
let sep_and_next =
|
||||||
|
(PathSep::parse(), till_next_sep).map(|(leading_separator, segment)| PathSegment {
|
||||||
|
leading_separator,
|
||||||
|
segment,
|
||||||
|
});
|
||||||
|
let drive = opt((
|
||||||
|
any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z')),
|
||||||
|
':',
|
||||||
|
))
|
||||||
|
.map(|i| i.map(|(letter, _): (char, char)| letter));
|
||||||
|
let drive_and_segments = (
|
||||||
|
drive,
|
||||||
|
repeat_till(
|
||||||
|
1..,
|
||||||
|
sep_and_next,
|
||||||
|
peek(any.verify(|i: &char| (*i).is_whitespace())),
|
||||||
|
)
|
||||||
|
.map(|(segments, _): (Vec<PathSegment>, _)| {
|
||||||
|
let (rest, last) = {
|
||||||
|
let mut segments = segments;
|
||||||
|
let last = segments.pop().unwrap();
|
||||||
|
(segments, last)
|
||||||
|
};
|
||||||
|
|
||||||
|
(rest, FileName::parse(last))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"path",
|
||||||
|
drive_and_segments.map(|(drive, (segments, filename))| Self {
|
||||||
|
drive_excluding_colon: drive,
|
||||||
|
segments,
|
||||||
|
filename,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Atom<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str>>(
|
||||||
|
except_chars: &'static [char],
|
||||||
|
) -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*, token::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"atom",
|
||||||
|
alt((repeat(
|
||||||
|
1..,
|
||||||
|
any.verify(move |i: &char| !(*i).is_whitespace() && !except_chars.contains(i)),
|
||||||
|
)
|
||||||
|
.map(Self::Text),)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Token<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*};
|
||||||
|
|
||||||
|
let delimited: Box<dyn Parser<&'a str, Self, E>> =
|
||||||
|
Box::new(Delimited::parse().map(Self::Delimited));
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"token",
|
||||||
|
alt((
|
||||||
|
"true".value(Self::True),
|
||||||
|
"false".value(Self::False),
|
||||||
|
"None".value(Self::None),
|
||||||
|
Path::parse().map(Self::Path),
|
||||||
|
AnyString::parse().map(Self::String),
|
||||||
|
Number::parse().map(Self::Number),
|
||||||
|
delimited,
|
||||||
|
Atom::parse(&[]).map(Self::Atom),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Delimited<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{combinator::*, prelude::*, token::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"delimited",
|
||||||
|
(
|
||||||
|
Atom::parse(&['(', '[', '{']),
|
||||||
|
alt((
|
||||||
|
literal('(').map(|_| literal(')').value(Delimiter::Paren)),
|
||||||
|
literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
|
||||||
|
literal('{').map(|_| literal('}').value(Delimiter::Brace)),
|
||||||
|
))
|
||||||
|
.flat_map(|end| Segments::parse(end)),
|
||||||
|
)
|
||||||
|
.map(|(prefix, (contents, delimiter))| Self {
|
||||||
|
prefix,
|
||||||
|
delimiter,
|
||||||
|
contents,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Segments<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
|
||||||
|
end: impl Parser<&'a str, End, E>,
|
||||||
|
) -> impl Parser<&'a str, (Self, End), E> {
|
||||||
|
use winnow::{combinator::*, prelude::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"segments",
|
||||||
|
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|
||||||
|
|(segments, (trailing_space, end)): (Vec<_>, _)| {
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
segments,
|
||||||
|
trailing_space,
|
||||||
|
},
|
||||||
|
end,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Number<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::{ascii::*, combinator::*, prelude::*};
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"number",
|
||||||
|
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take()))
|
||||||
|
.map(|i: &str| Self(i.into())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Segment<'a> {
|
||||||
|
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
||||||
|
use winnow::prelude::*;
|
||||||
|
|
||||||
|
trace(
|
||||||
|
"segment",
|
||||||
|
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
|
||||||
|
leading_space,
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
|
||||||
|
use winnow::combinator::eof;
|
||||||
|
Segments::parse(eof::<&str, winnow::error::EmptyError>)
|
||||||
|
.map(|(segments, _)| segments)
|
||||||
|
.parse(i)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
190
src/format_debug_output/proptesting.rs
Normal file
190
src/format_debug_output/proptesting.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use super::ast::*;
|
||||||
|
use crate::format_debug_output::{Config, into_spans};
|
||||||
|
use crate::format_debug_output::{ast::*, parse_input};
|
||||||
|
use proptest::prelude::*;
|
||||||
|
use proptest::proptest;
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn proptest_from_segments(original in Segments::arb(Token::arb())) {
|
||||||
|
let stringified = original.to_string();
|
||||||
|
let parsed = parse_input(&stringified).unwrap();
|
||||||
|
let stringified_again = parsed.to_string();
|
||||||
|
assert_eq!(stringified, stringified_again, "parsed: `{parsed:#?}`");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proptest_from_random_text(original in ".*") {
|
||||||
|
let parsed = parse_input(&original).unwrap();
|
||||||
|
let stringified_again = parsed.to_string();
|
||||||
|
assert_eq!(original, stringified_again, "parsed: `{parsed:#?}`");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proptest_into_spans(original in Segments::arb(Token::arb())) {
|
||||||
|
let stringified = original.to_string();
|
||||||
|
let spans = into_spans(original, Config {collapse_space: false});
|
||||||
|
let spans_concatenated = spans.into_iter().map(|i| i.text).collect::<String>();
|
||||||
|
assert_eq!(stringified, spans_concatenated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnyString<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
let prefix = "\\w*";
|
||||||
|
let ty = any::<QuoteType>();
|
||||||
|
let contents = "\\w*";
|
||||||
|
let num_hashtags = 0usize..3;
|
||||||
|
let suffix = "\\w*";
|
||||||
|
|
||||||
|
(prefix, ty, contents, num_hashtags, suffix).prop_map(
|
||||||
|
|(prefix, ty, contents, num_hashtags, suffix)| Self {
|
||||||
|
prefix: prefix.into(),
|
||||||
|
ty,
|
||||||
|
contents: contents.into(),
|
||||||
|
num_hashtags,
|
||||||
|
suffix: suffix.into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathSegment<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
(any::<PathSep>(), "\\w*").prop_map(|(leading_separator, segment)| Self {
|
||||||
|
leading_separator,
|
||||||
|
segment: segment.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileLocation<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
use proptest::option::*;
|
||||||
|
("[0-9]{0,4}", of("[0-9]{0,4}")).prop_map(|(line, offset)| Self {
|
||||||
|
line: line.into(),
|
||||||
|
offset: offset.map(Into::into),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Space<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
" *".prop_map(|spaces| Self(spaces.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileName<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
use proptest::option::*;
|
||||||
|
(
|
||||||
|
any::<PathSep>(),
|
||||||
|
"\\w*",
|
||||||
|
of(".{0,3}"),
|
||||||
|
of(FileLocation::arb()),
|
||||||
|
)
|
||||||
|
.prop_map(
|
||||||
|
|(leading_separator, segment, ext_excluding_dot, location)| Self {
|
||||||
|
leading_separator,
|
||||||
|
segment: segment.into(),
|
||||||
|
ext_excluding_dot: ext_excluding_dot.map(Into::into),
|
||||||
|
location,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
use proptest::{char::*, collection::*, option::*};
|
||||||
|
(
|
||||||
|
of(range('A', 'Z')),
|
||||||
|
vec(PathSegment::arb(), 0..3),
|
||||||
|
FileName::arb(),
|
||||||
|
)
|
||||||
|
.prop_map(|(drive_excluding_colon, segments, filename)| Self {
|
||||||
|
drive_excluding_colon,
|
||||||
|
segments,
|
||||||
|
filename,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Number<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
prop_oneof![
|
||||||
|
any::<i64>().prop_map(|number| Self(number.to_string().into())),
|
||||||
|
any::<f64>().prop_map(|number| Self(number.to_string().into()))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Atom<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
"[a-zA-Z]+".prop_map(|i| Self::Text(i.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb() -> impl Strategy<Value = Self> {
|
||||||
|
let leaf = prop_oneof![
|
||||||
|
Just(Self::True),
|
||||||
|
Just(Self::False),
|
||||||
|
Just(Self::None),
|
||||||
|
Path::arb().prop_map(Self::Path),
|
||||||
|
AnyString::arb().prop_map(Self::String),
|
||||||
|
Number::arb().prop_map(Self::Number),
|
||||||
|
Atom::arb().prop_map(Self::Atom),
|
||||||
|
];
|
||||||
|
|
||||||
|
leaf.prop_recursive(4, 64, 16, |token| {
|
||||||
|
Delimited::arb(token).prop_map(Self::Delimited).boxed()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Delimited<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||||
|
(Atom::arb(), any::<Delimiter>(), Segments::arb(token)).prop_map(
|
||||||
|
|(prefix, delimiter, contents)| Self {
|
||||||
|
prefix,
|
||||||
|
delimiter,
|
||||||
|
contents,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Segment<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||||
|
(Space::arb(), token).prop_map(|(leading_space, token)| Self {
|
||||||
|
leading_space,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Segments<'static> {
|
||||||
|
#[cfg(test)]
|
||||||
|
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
|
||||||
|
use proptest::collection::*;
|
||||||
|
|
||||||
|
(vec(Segment::arb(token), 1..10), Space::arb()).prop_map(|(segments, trailing_space)| {
|
||||||
|
Self {
|
||||||
|
segments,
|
||||||
|
trailing_space,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
235
src/format_debug_output/spans.rs
Normal file
235
src/format_debug_output/spans.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
use super::ast::*;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Kind {
|
||||||
|
/// Parentheses e.g.
|
||||||
|
///
|
||||||
|
/// Stores the delimiter depth, for e.g. rainbow delimiters.
|
||||||
|
Delimiter(usize),
|
||||||
|
/// Separators like `=`, ':' and ','
|
||||||
|
Separator,
|
||||||
|
/// Numbers
|
||||||
|
Number,
|
||||||
|
/// Known literals, like `true`, `false`, `None`, `Ok`, `Err`
|
||||||
|
Literal,
|
||||||
|
/// Strings
|
||||||
|
String,
|
||||||
|
/// Paths
|
||||||
|
Path,
|
||||||
|
/// Spaces (returns original number of spaces)
|
||||||
|
Space(usize),
|
||||||
|
|
||||||
|
/// Constructor: the prefix of a delimited block.
|
||||||
|
/// i.e. `Some` in `Some(3)`
|
||||||
|
Constructor,
|
||||||
|
|
||||||
|
/// String prefix, suffix, hashtags, etc
|
||||||
|
StringSurroundings,
|
||||||
|
|
||||||
|
/// Any other text (the default)
|
||||||
|
Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Span<'a> {
|
||||||
|
pub text: Cow<'a, str>,
|
||||||
|
pub kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub collapse_space: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Context<'a> {
|
||||||
|
config: Config,
|
||||||
|
res: Vec<Span<'a>>,
|
||||||
|
depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Context<'a> {
|
||||||
|
pub fn push(&mut self, text: impl Into<Cow<'a, str>>, kind: Kind) {
|
||||||
|
self.res.push(Span {
|
||||||
|
text: text.into(),
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {}
|
||||||
|
|
||||||
|
pub fn into_spans<'a>(ast: impl IntoSpans<'a>, config: Config) -> Vec<Span<'a>> {
|
||||||
|
let mut cx = Context {
|
||||||
|
config,
|
||||||
|
res: Vec::new(),
|
||||||
|
depth: 0,
|
||||||
|
};
|
||||||
|
ast.into_spans(&mut cx);
|
||||||
|
cx.res
|
||||||
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub trait IntoSpansImpl<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> IntoSpans<'a> for T where T: IntoSpansImpl<'a> {}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Separator {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
match self {
|
||||||
|
Separator::Eq => cx.push("=", Kind::Separator),
|
||||||
|
Separator::Colon => cx.push(":", Kind::Separator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for QuoteType {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
match self {
|
||||||
|
QuoteType::Single => cx.push("'", Kind::Separator),
|
||||||
|
QuoteType::Double => cx.push("\"", Kind::Separator),
|
||||||
|
QuoteType::Backtick => cx.push("`", Kind::Separator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for AnyString<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
let Self {
|
||||||
|
prefix,
|
||||||
|
ty,
|
||||||
|
contents,
|
||||||
|
num_hashtags,
|
||||||
|
suffix,
|
||||||
|
} = self;
|
||||||
|
cx.push(prefix, Kind::StringSurroundings);
|
||||||
|
for _ in 0..num_hashtags {
|
||||||
|
cx.push("#", Kind::StringSurroundings)
|
||||||
|
}
|
||||||
|
|
||||||
|
ty.into_spans(cx);
|
||||||
|
cx.push(contents, Kind::String);
|
||||||
|
ty.into_spans(cx);
|
||||||
|
|
||||||
|
for _ in 0..num_hashtags {
|
||||||
|
cx.push("#", Kind::StringSurroundings)
|
||||||
|
}
|
||||||
|
cx.push(suffix, Kind::StringSurroundings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Path<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
cx.push(self.to_string(), Kind::Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Number<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
cx.push(self.0, Kind::Number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Atom<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
match self {
|
||||||
|
Atom::Text(text) => cx.push(text, Kind::Text),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Space<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
match self.0.len() {
|
||||||
|
0 => {}
|
||||||
|
1 => cx.push(self.0, Kind::Space(1)),
|
||||||
|
n if cx.config.collapse_space => cx.push(" ", Kind::Space(n)),
|
||||||
|
n => cx.push(self.0, Kind::Space(n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Token<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
match self {
|
||||||
|
Token::True => cx.push("true", Kind::Literal),
|
||||||
|
Token::False => cx.push("false", Kind::Literal),
|
||||||
|
Token::None => cx.push("None", Kind::Literal),
|
||||||
|
Token::Path(path) => path.into_spans(cx),
|
||||||
|
Token::String(string) => string.into_spans(cx),
|
||||||
|
Token::Number(number) => number.into_spans(cx),
|
||||||
|
Token::Separated {
|
||||||
|
before,
|
||||||
|
space_before,
|
||||||
|
separator,
|
||||||
|
after,
|
||||||
|
} => todo!(),
|
||||||
|
Token::Delimited(delimited) => {
|
||||||
|
delimited.into_spans(cx);
|
||||||
|
}
|
||||||
|
Token::Atom(atom) => {
|
||||||
|
atom.into_spans(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Segment<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
let Self {
|
||||||
|
leading_space,
|
||||||
|
token,
|
||||||
|
} = self;
|
||||||
|
leading_space.into_spans(cx);
|
||||||
|
token.into_spans(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Segments<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
let Self {
|
||||||
|
segments,
|
||||||
|
trailing_space,
|
||||||
|
} = self;
|
||||||
|
for segment in segments {
|
||||||
|
segment.into_spans(cx);
|
||||||
|
}
|
||||||
|
trailing_space.into_spans(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoSpansImpl<'a> for Delimited<'a> {
|
||||||
|
fn into_spans(self, cx: &mut Context<'a>) {
|
||||||
|
let Self {
|
||||||
|
prefix,
|
||||||
|
delimiter,
|
||||||
|
contents,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
match prefix {
|
||||||
|
Atom::Text(text) => cx.push(text, Kind::Constructor),
|
||||||
|
}
|
||||||
|
|
||||||
|
match delimiter {
|
||||||
|
Delimiter::Paren => cx.push("(", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Bracket => cx.push("[", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Brace => cx.push("{", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Angle => cx.push("<", Kind::Delimiter(cx.depth)),
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.depth += 1;
|
||||||
|
contents.into_spans(cx);
|
||||||
|
cx.depth -= 1;
|
||||||
|
|
||||||
|
match delimiter {
|
||||||
|
Delimiter::Paren => cx.push(")", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Bracket => cx.push("]", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Brace => cx.push("}", Kind::Delimiter(cx.depth)),
|
||||||
|
Delimiter::Angle => cx.push(">", Kind::Delimiter(cx.depth)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod format_debug_output;
|
pub mod format_debug_output;
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
|
use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crossterm::{
|
||||||
},
|
},
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use ratatui_themes::{Theme, ThemeName};
|
use ratatui_themes::{Color, Theme, ThemeName};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, DirEntry},
|
fs::{self, DirEntry},
|
||||||
io::{self, Stdout},
|
io::{self, Stdout},
|
||||||
|
|
@ -493,6 +493,7 @@ impl App {
|
||||||
|
|
||||||
fn styles(&self) -> Styles {
|
fn styles(&self) -> Styles {
|
||||||
let palette = self.theme.palette();
|
let palette = self.theme.palette();
|
||||||
|
|
||||||
let default = Style::new().fg(palette.fg).bg(palette.bg);
|
let default = Style::new().fg(palette.fg).bg(palette.bg);
|
||||||
let highlighted = Style::new().fg(palette.accent).bg(palette.selection);
|
let highlighted = Style::new().fg(palette.accent).bg(palette.selection);
|
||||||
let border = Style::new().fg(palette.fg).bg(palette.bg);
|
let border = Style::new().fg(palette.fg).bg(palette.bg);
|
||||||
|
|
@ -505,6 +506,10 @@ impl App {
|
||||||
border,
|
border,
|
||||||
border_highlighted,
|
border_highlighted,
|
||||||
error,
|
error,
|
||||||
|
|
||||||
|
literal: palette.secondary,
|
||||||
|
faded: palette.muted,
|
||||||
|
delimiter: palette.accent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -529,6 +534,10 @@ pub struct Styles {
|
||||||
border: Style,
|
border: Style,
|
||||||
border_highlighted: Style,
|
border_highlighted: Style,
|
||||||
error: Style,
|
error: Style,
|
||||||
|
|
||||||
|
literal: Color,
|
||||||
|
faded: Color,
|
||||||
|
delimiter: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &mut App {
|
impl Widget for &mut App {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl Widget for Styled<'_, &Items<'_>> {
|
||||||
};
|
};
|
||||||
let line_text = entry.line_text(prefix, self.filters);
|
let line_text = entry.line_text(prefix, self.filters);
|
||||||
|
|
||||||
let mut line = line_text.styled(&self.styles);
|
let mut line = line_text.styled(self.styles);
|
||||||
if idx == self.selected_offset
|
if idx == self.selected_offset
|
||||||
&& let InputState::None
|
&& let InputState::None
|
||||||
| InputState::Target(InputTarget::This)
|
| InputState::Target(InputTarget::This)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ratatui::text::{Line, Span, Text};
|
use ratatui::text::{Line, Span, Text};
|
||||||
|
|
||||||
use crate::tui::widgets::styled::Styled;
|
use crate::{
|
||||||
|
format_debug_output::{Config, SpanKind, into_spans, parse_input},
|
||||||
|
tui::widgets::styled::Styled,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Highlighted {
|
pub enum Highlighted {
|
||||||
None,
|
None,
|
||||||
All,
|
All,
|
||||||
|
|
@ -37,6 +43,124 @@ impl LineText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HighlightedSpan<'a> {
|
||||||
|
span: crate::format_debug_output::Span<'a>,
|
||||||
|
highlighted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cow_split_at<'a>(inp: Cow<'a, str>, offset: usize) -> (Cow<'a, str>, Cow<'a, str>) {
|
||||||
|
match inp {
|
||||||
|
Cow::Borrowed(t) => {
|
||||||
|
let (a, b) = t.split_at(offset);
|
||||||
|
(Cow::Borrowed(a), Cow::Borrowed(b))
|
||||||
|
}
|
||||||
|
Cow::Owned(t) => {
|
||||||
|
let (a, b) = t.split_at(offset);
|
||||||
|
(Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_len(span: &crate::format_debug_output::Span<'_>) -> usize {
|
||||||
|
match span.kind {
|
||||||
|
SpanKind::Space(n) => n,
|
||||||
|
_ => span.text.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_spans<'a>(
|
||||||
|
i: impl Iterator<Item = crate::format_debug_output::Span<'a>>,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
) -> impl Iterator<Item = HighlightedSpan<'a>> {
|
||||||
|
let mut curr_offset = 0;
|
||||||
|
|
||||||
|
i.flat_map(move |span| {
|
||||||
|
// entirely before
|
||||||
|
if curr_offset + span.text.len() <= start {
|
||||||
|
curr_offset += span_len(&span);
|
||||||
|
return vec![HighlightedSpan {
|
||||||
|
span,
|
||||||
|
highlighted: false,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
let crate::format_debug_output::Span { text, kind } = span;
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
// over the start, push what's before and continue
|
||||||
|
let text = if curr_offset < start && curr_offset + text.len() > start {
|
||||||
|
let until_start = start - curr_offset;
|
||||||
|
let (before, after) = cow_split_at(text, until_start);
|
||||||
|
|
||||||
|
let before_span = crate::format_debug_output::Span { kind, text: before };
|
||||||
|
|
||||||
|
let l = span_len(&before_span);
|
||||||
|
if l != 0 {
|
||||||
|
curr_offset += l;
|
||||||
|
|
||||||
|
res.push(HighlightedSpan {
|
||||||
|
span: before_span,
|
||||||
|
highlighted: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
after
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// entirely within
|
||||||
|
if curr_offset + text.len() <= end {
|
||||||
|
let span = crate::format_debug_output::Span { kind, text };
|
||||||
|
curr_offset += span_len(&span);
|
||||||
|
res.push(HighlightedSpan {
|
||||||
|
span,
|
||||||
|
highlighted: true,
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// over the end boundary?
|
||||||
|
let text = if curr_offset < end && curr_offset + text.len() > end {
|
||||||
|
let until_start = end - curr_offset;
|
||||||
|
let (before, after) = cow_split_at(text, until_start);
|
||||||
|
|
||||||
|
let before_span = crate::format_debug_output::Span { kind, text: before };
|
||||||
|
|
||||||
|
curr_offset += span_len(&before_span);
|
||||||
|
res.push(HighlightedSpan {
|
||||||
|
span: before_span,
|
||||||
|
highlighted: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
after
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
let after = crate::format_debug_output::Span { kind, text };
|
||||||
|
|
||||||
|
curr_offset += span_len(&after);
|
||||||
|
res.push(HighlightedSpan {
|
||||||
|
span: after,
|
||||||
|
highlighted: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<Line<'static>> for Styled<'_, LineText> {
|
impl Into<Line<'static>> for Styled<'_, LineText> {
|
||||||
fn into(self) -> Line<'static> {
|
fn into(self) -> Line<'static> {
|
||||||
let mut spans = Vec::new();
|
let mut spans = Vec::new();
|
||||||
|
|
@ -45,21 +169,73 @@ impl Into<Line<'static>> for Styled<'_, LineText> {
|
||||||
spans.push(Span::from("┃ "));
|
spans.push(Span::from("┃ "));
|
||||||
spans.push(Span::from(self.inner.tree));
|
spans.push(Span::from(self.inner.tree));
|
||||||
|
|
||||||
match self.inner.highlighted {
|
if let Ok(parsed) = parse_input(&self.inner.message) {
|
||||||
Highlighted::None => {
|
let spans = into_spans(
|
||||||
spans.push(Span::from(self.inner.message).style(self.styles.default))
|
parsed,
|
||||||
}
|
Config {
|
||||||
Highlighted::All => {
|
collapse_space: true,
|
||||||
spans.push(Span::from(self.inner.message).style(self.styles.highlighted))
|
},
|
||||||
}
|
);
|
||||||
Highlighted::Range { from, to } => spans.extend_from_slice(&[
|
|
||||||
Span::from(self.inner.message[..from].to_string()).style(self.styles.default),
|
|
||||||
Span::from(self.inner.message[from..to].to_string()).style(self.styles.highlighted),
|
|
||||||
Span::from(self.inner.message[to..].to_string()).style(self.styles.default),
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
Line::from(spans)
|
let spans: Vec<_> = match self.inner.highlighted {
|
||||||
|
Highlighted::None => spans
|
||||||
|
.into_iter()
|
||||||
|
.map(|span| HighlightedSpan {
|
||||||
|
span,
|
||||||
|
highlighted: false,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Highlighted::All => spans
|
||||||
|
.into_iter()
|
||||||
|
.map(|span| HighlightedSpan {
|
||||||
|
span,
|
||||||
|
highlighted: true,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Highlighted::Range { from, to } => {
|
||||||
|
highlight_spans(spans.into_iter(), from, to).collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let spans: Vec<_> = spans
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|HighlightedSpan {
|
||||||
|
span: crate::format_debug_output::Span { text, kind },
|
||||||
|
highlighted,
|
||||||
|
}| {
|
||||||
|
let span = Span::from(text.into_owned());
|
||||||
|
|
||||||
|
let style = if highlighted {
|
||||||
|
self.styles.highlighted
|
||||||
|
} else {
|
||||||
|
self.styles.default
|
||||||
|
};
|
||||||
|
|
||||||
|
let style = match kind {
|
||||||
|
SpanKind::Delimiter(_) => style.fg(self.styles.delimiter).bold(),
|
||||||
|
SpanKind::Separator => style.fg(self.styles.faded),
|
||||||
|
SpanKind::Number => style.fg(self.styles.literal).underlined(),
|
||||||
|
SpanKind::Literal => style.fg(self.styles.literal).underlined(),
|
||||||
|
SpanKind::String => style.fg(self.styles.literal).italic(),
|
||||||
|
SpanKind::Path => style.fg(self.styles.literal).italic(),
|
||||||
|
SpanKind::Space(_) => style,
|
||||||
|
SpanKind::Constructor => style.fg(self.styles.literal),
|
||||||
|
SpanKind::StringSurroundings => style.fg(self.styles.faded),
|
||||||
|
SpanKind::Text => style,
|
||||||
|
};
|
||||||
|
|
||||||
|
span.style(style)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Line::from(spans)
|
||||||
|
} else {
|
||||||
|
Line::from(vec![
|
||||||
|
Span::from(self.inner.message).style(self.styles.default),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue