This commit is contained in:
Valentin B. 2026-06-03 13:58:11 +09:00 committed by GitHub
commit 62bbfa5b61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 36 deletions

View File

@ -740,7 +740,7 @@ impl GraphicalReportHandler {
max_gutter,
line,
&labels,
LabelRenderMode::SingleLine,
LabelRenderMode::MultiLineEndPending,
)?;
self.render_single_line_highlights(
f,
@ -930,32 +930,41 @@ impl GraphicalReportHandler {
let applicable = highlights.iter().filter(|hl| line.span_applies_gutter(hl));
for (i, hl) in applicable.enumerate() {
if !line.span_line_only(hl) && line.span_ends(hl) {
if render_mode == LabelRenderMode::MultiLineRest {
// this is to make multiline labels work. We want to make the right amount
// of horizontal space for them, but not actually draw the lines
let horizontal_space = max_gutter.saturating_sub(i) + 2;
for _ in 0..horizontal_space {
gutter.push(' ');
match render_mode {
LabelRenderMode::MultiLineRest => {
// this is to make multiline labels work. We want to make the right amount
// of horizontal space for them, but not actually draw the lines
let horizontal_space = max_gutter.saturating_sub(i) + 2;
for _ in 0..horizontal_space {
gutter.push(' ');
}
// account for one more horizontal space, since in multiline mode
// we also add in the vertical line before the label like this:
// 2 │ ╭─▶ text
// 3 │ ├─▶ here
// · ╰──┤ these two lines
// · │ are the problem
// ^this
gutter_cols += horizontal_space + 1;
}
// account for one more horizontal space, since in multiline mode
// we also add in the vertical line before the label like this:
// 2 │ ╭─▶ text
// 3 │ ├─▶ here
// · ╰──┤ these two lines
// · │ are the problem
// ^this
gutter_cols += horizontal_space + 1;
} else {
let num_repeat = max_gutter.saturating_sub(i) + 2;
LabelRenderMode::MultiLineEndPending => {
// we're rendering continuation lines for single-line highlights on a line
// where a multiline span ends
// render │ to show the span is still visually "open" until we render its own label with ╰──
gutter.push_str(&chars.vbar.style(hl.style).to_string());
gutter_cols += 1;
}
LabelRenderMode::SingleLine | LabelRenderMode::MultiLineFirst => {
let num_repeat = max_gutter.saturating_sub(i) + 2;
gutter.push_str(&chars.lbot.style(hl.style).to_string());
gutter.push_str(&chars.lbot.style(hl.style).to_string());
gutter.push_str(
&chars
.hbar
.to_string()
.repeat(
num_repeat
gutter.push_str(
&chars
.hbar
.to_string()
.repeat(
num_repeat
// if we are rendering a multiline label, then leave a bit of space for the
// rcross character
- if render_mode == LabelRenderMode::MultiLineFirst {
@ -963,16 +972,17 @@ impl GraphicalReportHandler {
} else {
0
},
)
.style(hl.style)
.to_string(),
);
)
.style(hl.style)
.to_string(),
);
// we count 1 for the lbot char, and then a few more, the same number
// as we just repeated for. For each repeat we only add 1, even though
// due to ansi escape codes the number of bytes in the string could grow
// a lot each time.
gutter_cols += num_repeat + 1;
// we count 1 for the lbot char, and then a few more, the same number
// as we just repeated for. For each repeat we only add 1, even though
// due to ansi escape codes the number of bytes in the string could grow
// a lot each time.
gutter_cols += num_repeat + 1;
}
}
break;
} else {
@ -1244,7 +1254,7 @@ impl GraphicalReportHandler {
max_gutter,
line,
all_highlights,
LabelRenderMode::SingleLine,
LabelRenderMode::MultiLineEndPending,
)?;
let mut curr_offset = 1usize;
for (offset_hl, vbar_offset) in vbar_offsets {
@ -1257,7 +1267,7 @@ impl GraphicalReportHandler {
curr_offset += 1;
} else {
let lines = match render_mode {
LabelRenderMode::SingleLine => format!(
LabelRenderMode::SingleLine | LabelRenderMode::MultiLineEndPending => format!(
"{}{} {}",
chars.lbot,
chars.hbar.to_string().repeat(2),
@ -1285,7 +1295,7 @@ impl GraphicalReportHandler {
render_mode: LabelRenderMode,
) -> fmt::Result {
match render_mode {
LabelRenderMode::SingleLine => {
LabelRenderMode::SingleLine | LabelRenderMode::MultiLineEndPending => {
writeln!(f, "{} {}", self.theme.characters.hbar.style(style), label)?;
}
LabelRenderMode::MultiLineFirst => {
@ -1382,6 +1392,9 @@ enum LabelRenderMode {
MultiLineFirst,
/// we're rendering the rest of a multiline label
MultiLineRest,
/// we're rendering continuation lines for single-line highlights on a line where
/// a multi-line span ends - render │ instead of ╰── for ending spans
MultiLineEndPending,
}
#[derive(Debug)]

View File

@ -489,6 +489,54 @@ if true {
assert_eq!(expected, out);
}
#[test]
fn multiline_span_end_same_line_as_singleline() {
#[derive(Error, Debug, Diagnostic)]
#[error("oops!")]
#[diagnostic(severity(Error))]
struct MyBad {
#[source_code]
src: NamedSource<&'static str>,
#[label("multiline label")]
multiline: SourceSpan,
#[label("singleline label")]
singleline: SourceSpan,
}
let src = "\
function test(
bool a,
) internal returns (uint256 helloWorld, Test someTest) {
// oh hai
}
";
let err = MyBad {
src: NamedSource::new("issue", src),
// multiline span from "function" to end of ")"
multiline: (0, 38).into(),
// singleline span on "uint256 helloWorld"
singleline: (55, 18).into(),
};
let out = fmt_report(err.into());
println!("Error: {}", out);
// The │ should continue on the underline and label lines until the multiline span's
// own label is rendered with ╰────
let expected = r#"
× oops!
[issue:1:1]
1 function test(
2 bool a,
3 ) internal returns (uint256 helloWorld, Test someTest) {
·
· singleline label
· multiline label
4 // oh hai
"#
.trim_start_matches('\n');
assert_eq!(expected, out);
}
#[test]
fn single_line_highlight_span_full_line() {
#[derive(Error, Debug, Diagnostic)]