From ab33e529875f5872f5b29808fe471ede7c3cb760 Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Fri, 27 Mar 2026 16:31:50 +0200 Subject: [PATCH 1/4] fix: remove `println!()` from test --- src/error.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 08320af..223c2b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -717,7 +717,6 @@ let x: u32 = Left( 1 | /*😀*/ let a: u8 = 65536; | ^^^^^ Cannot parse: number too large to fit in target type"#; - println!("{error}"); assert_eq!(&expected[1..], &error.to_string()); } } From 2adcc4f1a3a496ea99da37ea53a1bf772eb737a1 Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Fri, 27 Mar 2026 16:32:46 +0200 Subject: [PATCH 2/4] fix: multiline display with utf-16 symbols --- src/error.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 223c2b7..8537d2f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -223,7 +223,9 @@ impl fmt::Display for RichError { writeln!(f, "{:width$} |", " ", width = line_num_width)?; let mut lines = file.lines().skip(start_line_index).peekable(); - let start_line_len = lines.peek().map_or(0, |l| l.len()); + let start_line_len = lines + .peek() + .map_or(0, |l| l.chars().map(char::len_utf16).sum()); for (relative_line_index, line_str) in lines.take(n_spanned_lines).enumerate() { let line_num = start_line_index + relative_line_index + 1; @@ -719,4 +721,27 @@ let x: u32 = Left( assert_eq!(&expected[1..], &error.to_string()); } + + #[test] + fn multiline_display_with_utf16_chars() { + let file = r#"/*😀 this symbol should not break the rendering*/ +let a: u8 = 65536; +let x: u32 = Left( + Right(0) +);"#; + let error = Error::CannotParse("This span covers the entire file".to_string()) + .with_span(Span::from(file)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | /*😀 this symbol should not break the rendering*/ +2 | let a: u8 = 65536; +3 | let x: u32 = Left( +4 | Right(0) +5 | ); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot parse: This span covers the entire file"#; + + assert_eq!(&expected[1..], &error.to_string()); + } } From cec0e8ce9c0bd84254fa2876267feb9fb8282bfc Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Fri, 27 Mar 2026 16:35:00 +0200 Subject: [PATCH 3/4] fix: support for unicode separator --- src/error.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 8537d2f..dee01bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -222,7 +222,11 @@ impl fmt::Display for RichError { writeln!(f, "{:width$} |", " ", width = line_num_width)?; - let mut lines = file.lines().skip(start_line_index).peekable(); + let mut lines = file + .split(|c: char| c.is_newline()) + .skip(start_line_index) + .peekable(); + let start_line_len = lines .peek() .map_or(0, |l| l.chars().map(char::len_utf16).sum()); @@ -744,4 +748,19 @@ let x: u32 = Left( assert_eq!(&expected[1..], &error.to_string()); } + + #[test] + fn display_with_unicode_separator() { + let file = "let a: u8 = 65536;\u{2028}let b: u8 = 0;"; + let error = Error::CannotParse("number too large to fit in target type".to_string()) + .with_span(Span::new(12, 17)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | let a: u8 = 65536; + | ^^^^^ Cannot parse: number too large to fit in target type"#; + + assert_eq!(&expected[1..], &error.to_string()); + } } From d6b0b17010e932201687fe52d3d35e9249f91ef2 Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Fri, 27 Mar 2026 16:40:57 +0200 Subject: [PATCH 4/4] fix: display for Span with `start` == `end` --- src/error.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index dee01bf..b4d5a4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -240,7 +240,7 @@ impl fmt::Display for RichError { let (underline_start, underline_length) = match is_multiline { true => (0, start_line_len), - false => (start_col, end_col - start_col), + false => (start_col, (end_col - start_col).max(1)), }; write!(f, "{:width$} |", " ", width = line_num_width)?; write!(f, "{:width$}", " ", width = underline_start)?; @@ -763,4 +763,33 @@ let x: u32 = Left( assert_eq!(&expected[1..], &error.to_string()); } + + #[test] + fn display_span_as_point() { + let file = "fn main()"; + let error = Error::Grammar("Error span at (0,0)".to_string()) + .with_span(Span::new(0, 0)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | fn main() + | ^ Grammar error: Error span at (0,0)"#; + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_span_as_point_on_trailing_empty_line() { + let file = "fn main(){\n let a:\n"; + let error = Error::CannotParse("eof".to_string()) + .with_span(Span::new(file.len(), file.len())) + .with_file(Arc::from(file)); + + let expected = r#" + | +3 | + | ^ Cannot parse: eof"#; + + assert_eq!(&expected[1..], &error.to_string()); + } }