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