tamer: diagnose::report::SourceLine: Separate variants for each line

Now `SourceLine` _does_ actually correspond to a line of output, which will
allow for better formatting (e.g. collapsing padding) and, importantly,
proper management of gutters.

Note that the seemingly unnecessary `SectionSourceLine` allows for a subtle
consistent formatting for all variants' gutters in `SectionLine`, which will
allow us to hoist that rendering out in the next commit.  The other option
was to include a trailing space for padding and marks, but that is not only
sloppy and undesirable, but asking for confusion, especially in editors (like
mine) that trim trailing whitespace.

DEV-12151
main
Mike Gerwitz 2022-04-28 22:43:31 -04:00
parent fd1c6430a8
commit 4e03a367a5
1 changed files with 77 additions and 74 deletions

View File

@ -304,23 +304,26 @@ where
let nlines = src.len();
body.extend(src.into_iter().enumerate().filter_map(
|(i, srcline)| {
let label =
if i == nlines - 1 { olabel.take() } else { None };
src.into_iter().enumerate().for_each(|(i, srcline)| {
let label =
if i == nlines - 1 { olabel.take() } else { None };
if let Some(col) = srcline.column() {
Some(SectionLine::SourceLine(SectionSourceLine {
src: srcline,
mark: LineMark { col, level, label },
}))
} else {
label.map(|l| {
SectionLine::Footnote(SpanLabel(level, l))
})
}
},
));
if let Some(col) = srcline.column() {
body.extend(vec![
SectionLine::SourceLinePadding,
SectionLine::SourceLine(srcline.into()),
SectionLine::SourceLineMark(LineMark {
col,
level,
label,
}),
]);
} else {
body.extend(label.map(|l| {
SectionLine::Footnote(SpanLabel(level, l))
}));
}
});
(span, level)
}
@ -498,24 +501,23 @@ impl<'d> Display for SpanLabel<'d> {
}
}
/// A possibly-annotated line of output.
///
/// Note that a section line doesn't necessarily correspond to a single line
/// of output on a terminal;
/// lines are likely to be annotated.
/// Line of output in a [`Section`] body.
#[derive(Debug, PartialEq, Eq)]
enum SectionLine<'d> {
SourceLine(SectionSourceLine<'d>),
SourceLinePadding,
SourceLine(SectionSourceLine),
SourceLineMark(LineMark<'d>),
Footnote(SpanLabel<'d>),
}
impl<'d> SectionLine<'d> {
fn into_footnote(self) -> Option<Self> {
match self {
Self::SourceLine(SectionSourceLine {
mark: LineMark { level, label, .. },
..
}) => label.map(|l| Self::Footnote(SpanLabel(level, l))),
Self::SourceLinePadding => None,
Self::SourceLine(..) => None,
Self::SourceLineMark(LineMark { level, label, .. }) => {
label.map(|l| Self::Footnote(SpanLabel(level, l)))
}
Self::Footnote(..) => Some(self),
}
@ -525,24 +527,26 @@ impl<'d> SectionLine<'d> {
impl<'d> Display for SectionLine<'d> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::SourceLine(line) => line.fmt(f),
Self::SourceLinePadding => write!(f, " |\n"),
Self::SourceLine(line) => write!(f, " |{line}\n"),
Self::SourceLineMark(mark) => write!(f, " |{mark}\n"),
Self::Footnote(label) => write!(f, "{label}\n"),
}
}
}
/// A line representing possibly-annotated source code.
#[derive(Debug, PartialEq, Eq)]
struct SectionSourceLine<'d> {
src: SourceLine,
mark: LineMark<'d>,
struct SectionSourceLine(SourceLine);
impl From<SourceLine> for SectionSourceLine {
fn from(line: SourceLine) -> Self {
Self(line)
}
}
impl<'d> Display for SectionSourceLine<'d> {
impl Display for SectionSourceLine {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, " |\n")?;
write!(f, " | {src}\n", src = self.src)?;
write!(f, " |{}", self.mark)
write!(f, " {line}", line = self.0)
}
}
@ -575,7 +579,7 @@ impl<'d> Display for LineMark<'d> {
write!(f, " {level}: {label}", level = self.level)?;
}
write!(f, "\n")
Ok(())
}
}
@ -765,23 +769,21 @@ mod test {
// Derived from label.
level: Level::Note,
body: vec![
SectionLine::SourceLine(SectionSourceLine {
src: src_lines[0].clone(),
mark: LineMark {
level: Level::Note,
col: col_1,
// Label goes on the last source line.
label: None,
}
SectionLine::SourceLinePadding,
SectionLine::SourceLine(src_lines[0].clone().into()),
SectionLine::SourceLineMark(LineMark {
level: Level::Note,
col: col_1,
// Label goes on the last source line.
label: None,
}),
SectionLine::SourceLine(SectionSourceLine {
src: src_lines[1].clone(),
mark: LineMark {
level: Level::Note,
col: col_2,
// Label at last source line
label: Some("test label".into()),
}
SectionLine::SourceLinePadding,
SectionLine::SourceLine(src_lines[1].clone().into()),
SectionLine::SourceLineMark(LineMark {
level: Level::Note,
col: col_2,
// Label at last source line
label: Some("test label".into()),
}),
],
}
@ -882,20 +884,29 @@ mod test {
}
#[test]
fn section_src_line_with_label_into_footnote() {
fn section_src_line_into_footnote() {
assert_eq!(
SectionLine::SourceLine(SectionSourceLine {
src: SourceLine::new_stub(
SectionLine::SourceLine(
SourceLine::new_stub(
1.unwrap_into(),
None,
DUMMY_SPAN,
"discarded".into()
),
mark: LineMark {
level: Level::Help,
col: Column::Before(1.unwrap_into()),
label: Some("kept label".into())
}
)
.into()
)
.into_footnote(),
None
);
}
#[test]
fn section_mark_with_label_into_footnote() {
assert_eq!(
SectionLine::SourceLineMark(LineMark {
level: Level::Help,
col: Column::Before(1.unwrap_into()),
label: Some("kept label".into())
})
.into_footnote(),
Some(SectionLine::Footnote(SpanLabel(
@ -906,20 +917,12 @@ mod test {
}
#[test]
fn section_src_line_without_label_into_footnote() {
fn section_mark_without_label_into_footnote() {
assert_eq!(
SectionLine::SourceLine(SectionSourceLine {
src: SourceLine::new_stub(
1.unwrap_into(),
None,
DUMMY_SPAN,
"discarded".into()
),
mark: LineMark {
level: Level::Help,
col: Column::Before(1.unwrap_into()),
label: None,
}
SectionLine::SourceLineMark(LineMark {
level: Level::Help,
col: Column::Before(1.unwrap_into()),
label: None,
})
.into_footnote(),
None