@@ -192,22 +192,57 @@ impl RichError {
192192
193193impl fmt:: Display for RichError {
194194 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
195- fn get_line_col ( file : & str , offset : usize ) -> ( usize , usize ) {
196- let mut line = 1 ;
197- let mut col = 0 ;
195+ fn next_newline ( s : & str ) -> Option < ( usize , usize ) > {
196+ let mut it = s. char_indices ( ) . peekable ( ) ;
198197
199- let slice = file. get ( 0 ..offset) . unwrap_or_default ( ) ;
198+ while let Some ( ( i, ch) ) = it. next ( ) {
199+ // Treat CRLF as one logical newline.
200+ if ch == '\r' && matches ! ( it. peek( ) , Some ( ( _, c) ) if * c == '\n' ) {
201+ return Some ( ( i, ch. len_utf8 ( ) + '\n' . len_utf8 ( ) ) ) ;
202+ }
200203
201- for char in slice. chars ( ) {
202- if char. is_newline ( ) {
203- line += 1 ;
204- col = 0 ;
205- } else {
206- col += char. len_utf16 ( ) ;
204+ // Preserve support for LF and Unicode newline characters.
205+ if ch. is_newline ( ) {
206+ return Some ( ( i, ch. len_utf8 ( ) ) ) ;
207207 }
208208 }
209209
210- ( line, col + 1 )
210+ None
211+ }
212+
213+ fn get_line_col ( file : & str , offset : usize ) -> ( usize , usize ) {
214+ let s = file. get ( ..offset) . unwrap_or_default ( ) ;
215+ let mut line = 1usize ;
216+ let mut last_line_start = 0usize ;
217+ let mut rest = s;
218+ let mut consumed = 0usize ;
219+
220+ while let Some ( ( i, nl_len) ) = next_newline ( rest) {
221+ line += 1 ;
222+ consumed += i + nl_len;
223+ last_line_start = consumed;
224+ rest = & rest[ i + nl_len..] ;
225+ }
226+
227+ let col = 1 + s[ last_line_start..]
228+ . chars ( )
229+ . map ( char:: len_utf16)
230+ . sum :: < usize > ( ) ;
231+
232+ ( line, col)
233+ }
234+
235+ fn split_lines_preserving_crlf ( file : & str ) -> Vec < & str > {
236+ let mut out = Vec :: new ( ) ;
237+ let mut rest = file;
238+
239+ while let Some ( ( i, nl_len) ) = next_newline ( rest) {
240+ out. push ( & rest[ ..i] ) ;
241+ rest = & rest[ i + nl_len..] ;
242+ }
243+
244+ out. push ( rest) ;
245+ out
211246 }
212247
213248 match self . file {
@@ -222,24 +257,28 @@ impl fmt::Display for RichError {
222257
223258 writeln ! ( f, "{:width$} |" , " " , width = line_num_width) ?;
224259
225- let mut lines = file
226- . split ( |c : char | c. is_newline ( ) )
227- . skip ( start_line_index)
228- . peekable ( ) ;
260+ let split_lines = split_lines_preserving_crlf ( file) ;
261+ let mut lines = split_lines. into_iter ( ) . skip ( start_line_index) . peekable ( ) ;
229262
230263 let start_line_len = lines
231264 . peek ( )
232- . map_or ( 0 , |l| l. chars ( ) . map ( char:: len_utf16) . sum ( ) ) ;
265+ . map_or ( 0 , |l| l. chars ( ) . map ( char:: len_utf16) . sum :: < usize > ( ) ) ;
233266
234267 for ( relative_line_index, line_str) in lines. take ( n_spanned_lines) . enumerate ( ) {
235268 let line_num = start_line_index + relative_line_index + 1 ;
236- writeln ! ( f, "{line_num:line_num_width$} | {line_str}" ) ?;
269+ write ! ( f, "{line_num:line_num_width$} |" ) ?;
270+ if !line_str. is_empty ( ) {
271+ write ! ( f, " {line_str}" ) ?;
272+ }
273+ writeln ! ( f) ?;
237274 }
238275
239276 let is_multiline = end_line > start_line;
240277
241278 let ( underline_start, underline_length) = match is_multiline {
242- true => ( 0 , start_line_len) ,
279+ // For multiline spans, preserve the existing display style:
280+ // underline the full first displayed line.
281+ true => ( 1 , start_line_len) ,
243282 false => ( start_col, ( end_col - start_col) . max ( 1 ) ) ,
244283 } ;
245284 write ! ( f, "{:width$} |" , " " , width = line_num_width) ?;
@@ -764,6 +803,81 @@ let x: u32 = Left(
764803 assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
765804 }
766805
806+ #[ test]
807+ fn display_with_windows_crlf_newlines ( ) {
808+ let file = "let a: u8 = 65536;\r \n let b: u8 = 0;" ;
809+ let error = Error :: CannotParse ( "number too large to fit in target type" . to_string ( ) )
810+ . with_span ( Span :: new ( 12 , 17 ) )
811+ . with_file ( Arc :: from ( file) ) ;
812+
813+ let expected = r#"
814+ |
815+ 1 | let a: u8 = 65536;
816+ | ^^^^^ Cannot parse: number too large to fit in target type"# ;
817+
818+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
819+ }
820+
821+ #[ test]
822+ fn display_with_unix_lf_newlines ( ) {
823+ let file = "let a: u8 = 65536;\n let b: u8 = 0;" ;
824+ let error = Error :: CannotParse ( "number too large to fit in target type" . to_string ( ) )
825+ . with_span ( Span :: new ( 12 , 17 ) )
826+ . with_file ( Arc :: from ( file) ) ;
827+
828+ let expected = r#"
829+ |
830+ 1 | let a: u8 = 65536;
831+ | ^^^^^ Cannot parse: number too large to fit in target type"# ;
832+
833+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
834+ }
835+
836+ #[ test]
837+ fn display_with_mixed_newlines_on_second_line ( ) {
838+ let file = "line1\r \n line2\n line3" ;
839+ let error = Error :: CannotParse ( "err" . to_string ( ) )
840+ . with_span ( Span :: new ( 7 , 12 ) )
841+ . with_file ( Arc :: from ( file) ) ;
842+
843+ let expected = r#"
844+ |
845+ 2 | line2
846+ | ^^^^^ Cannot parse: err"# ;
847+
848+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
849+ }
850+
851+ #[ test]
852+ fn display_does_not_insert_extra_blank_line_for_crlf ( ) {
853+ let file = "a\r \n b" ;
854+ let error = Error :: CannotParse ( "err" . to_string ( ) )
855+ . with_span ( Span :: new ( 3 , 4 ) )
856+ . with_file ( Arc :: from ( file) ) ;
857+
858+ let expected = r#"
859+ |
860+ 2 | b
861+ | ^ Cannot parse: err"# ;
862+
863+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
864+ }
865+
866+ #[ test]
867+ fn display_handles_utf16_columns_after_newline ( ) {
868+ let file = "x\r \n 😀ab" ;
869+ let error = Error :: CannotParse ( "err" . to_string ( ) )
870+ . with_span ( Span :: new ( 7 , 9 ) )
871+ . with_file ( Arc :: from ( file) ) ;
872+
873+ let expected = r#"
874+ |
875+ 2 | 😀ab
876+ | ^^ Cannot parse: err"# ;
877+
878+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
879+ }
880+
767881 #[ test]
768882 fn display_span_as_point ( ) {
769883 let file = "fn main()" ;
@@ -787,9 +901,24 @@ let x: u32 = Left(
787901
788902 let expected = r#"
789903 |
790- 3 |
904+ 3 |
791905 | ^ Cannot parse: eof"# ;
792906
793907 assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
794908 }
909+
910+ #[ test]
911+ fn display_zero_length_span_shows_single_caret ( ) {
912+ let file = "let a: u8 = 1;" ;
913+ let error = Error :: CannotParse ( "err" . to_string ( ) )
914+ . with_span ( Span :: new ( 12 , 12 ) )
915+ . with_file ( Arc :: from ( file) ) ;
916+
917+ let expected = r#"
918+ |
919+ 1 | let a: u8 = 1;
920+ | ^ Cannot parse: err"# ;
921+
922+ assert_eq ! ( & expected[ 1 ..] , & error. to_string( ) ) ;
923+ }
795924}
0 commit comments