Skip to content

Commit 8b56d27

Browse files
authoredNov 24, 2022
fix(graphical): Fix panic with span extending past end of line (#221)
Fixes: #215 This also changes the behavior with spans including a CRLF line-ending. Before the panic bug was introduced, these were rendered with the CRLF being two visual columns wide. Now, any span extending past the EOL is treated as including one extra visual column.
1 parent c88f0b5 commit 8b56d27

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed
 

‎src/handlers/graphical.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,22 @@ impl GraphicalReportHandler {
621621
let line_range = line.offset..=(line.offset + line.length);
622622
assert!(line_range.contains(&offset));
623623

624-
let text = &line.text[..offset - line.offset];
625-
self.line_visual_char_width(text).sum()
624+
let text_index = offset - line.offset;
625+
let text = &line.text[..text_index.min(line.text.len())];
626+
let text_width = self.line_visual_char_width(text).sum();
627+
if text_index > line.text.len() {
628+
// Spans extending past the end of the line are always rendered as
629+
// one column past the end of the visible line.
630+
//
631+
// This doesn't necessarily correspond to a specific byte-offset,
632+
// since a span extending past the end of the line could contain:
633+
// - an actual \n character (1 byte)
634+
// - a CRLF (2 bytes)
635+
// - EOF (0 bytes)
636+
text_width + 1
637+
} else {
638+
text_width
639+
}
626640
}
627641

628642
/// Renders a line to the output formatter, replacing tabs with spaces.

‎tests/graphical.rs

+74
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,80 @@ fn single_line_higlight_offset_end_of_line() -> Result<(), MietteError> {
358358
Ok(())
359359
}
360360

361+
#[test]
362+
fn single_line_higlight_include_end_of_line() -> Result<(), MietteError> {
363+
#[derive(Debug, Diagnostic, Error)]
364+
#[error("oops!")]
365+
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
366+
struct MyBad {
367+
#[source_code]
368+
src: NamedSource,
369+
#[label("this bit here")]
370+
highlight: SourceSpan,
371+
}
372+
373+
let src = "source\n text\n here".to_string();
374+
let err = MyBad {
375+
src: NamedSource::new("bad_file.rs", src),
376+
highlight: (9, 5).into(),
377+
};
378+
let out = fmt_report(err.into());
379+
println!("Error: {}", out);
380+
let expected = r#"oops::my::bad
381+
382+
× oops!
383+
╭─[bad_file.rs:1:1]
384+
1 │ source
385+
2 │ text
386+
· ──┬──
387+
· ╰── this bit here
388+
3 │ here
389+
╰────
390+
help: try doing it better next time?
391+
"#
392+
.trim_start()
393+
.to_string();
394+
assert_eq!(expected, out);
395+
Ok(())
396+
}
397+
398+
#[test]
399+
fn single_line_higlight_include_end_of_line_crlf() -> Result<(), MietteError> {
400+
#[derive(Debug, Diagnostic, Error)]
401+
#[error("oops!")]
402+
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
403+
struct MyBad {
404+
#[source_code]
405+
src: NamedSource,
406+
#[label("this bit here")]
407+
highlight: SourceSpan,
408+
}
409+
410+
let src = "source\r\n text\r\n here".to_string();
411+
let err = MyBad {
412+
src: NamedSource::new("bad_file.rs", src),
413+
highlight: (10, 6).into(),
414+
};
415+
let out = fmt_report(err.into());
416+
println!("Error: {}", out);
417+
let expected = r#"oops::my::bad
418+
419+
× oops!
420+
╭─[bad_file.rs:1:1]
421+
1 │ source
422+
2 │ text
423+
· ──┬──
424+
· ╰── this bit here
425+
3 │ here
426+
╰────
427+
help: try doing it better next time?
428+
"#
429+
.trim_start()
430+
.to_string();
431+
assert_eq!(expected, out);
432+
Ok(())
433+
}
434+
361435
#[test]
362436
fn single_line_highlight_with_empty_span() -> Result<(), MietteError> {
363437
#[derive(Debug, Diagnostic, Error)]

0 commit comments

Comments
 (0)
Please sign in to comment.