@@ -311,6 +311,9 @@ pub(crate) struct FormattingError {
311
311
is_comment : bool ,
312
312
is_string : bool ,
313
313
pub ( crate ) line_buffer : String ,
314
+ // Number of spaces a tab represents when computing visual columns.
315
+ // Needed to translate visual column based overflow ranges into byte indices.
316
+ tab_spaces : usize ,
314
317
}
315
318
316
319
impl FormattingError {
@@ -321,6 +324,8 @@ impl FormattingError {
321
324
kind,
322
325
is_string : false ,
323
326
line_buffer : psess. span_to_first_line_string ( span) ,
327
+ // Default; actual value only matters for LineOverflow emitted via push_err
328
+ tab_spaces : 4 ,
324
329
}
325
330
}
326
331
@@ -347,7 +352,7 @@ impl FormattingError {
347
352
// (space, target)
348
353
pub ( crate ) fn format_len ( & self ) -> ( usize , usize ) {
349
354
match self . kind {
350
- ErrorKind :: LineOverflow ( found, max) => ( max , found - max) ,
355
+ ErrorKind :: LineOverflow ( found, max) => self . line_overflow_byte_range ( found, max) ,
351
356
ErrorKind :: TrailingWhitespace
352
357
| ErrorKind :: DeprecatedAttr
353
358
| ErrorKind :: BadAttr
@@ -365,6 +370,40 @@ impl FormattingError {
365
370
_ => unreachable ! ( ) ,
366
371
}
367
372
}
373
+
374
+ // Compute (start_byte, length) tuple for a LineOverflow error converting visual columns
375
+ // (tabs expanded to `tab_spaces`) into byte indices within the stored line buffer.
376
+ fn line_overflow_byte_range ( & self , found : usize , max : usize ) -> ( usize , usize ) {
377
+ if max >= found || self . line_buffer . is_empty ( ) {
378
+ return ( 0 , 0 ) ;
379
+ }
380
+
381
+ let mut visual_col = 0 ;
382
+ let mut start_byte = None ;
383
+ let mut end_byte = None ;
384
+ for ( idx, ch) in self . line_buffer . char_indices ( ) {
385
+ let ch_width = if ch == '\t' { self . tab_spaces } else { 1 } ;
386
+ let next_col = visual_col + ch_width;
387
+ if start_byte. is_none ( ) && next_col > max {
388
+ start_byte = Some ( idx) ;
389
+ } else if start_byte. is_none ( ) && next_col == max {
390
+ // Start will be at next character (if any)
391
+ }
392
+
393
+ if end_byte. is_none ( ) && next_col >= found {
394
+ end_byte = Some ( idx + ch. len_utf8 ( ) ) ;
395
+ break ;
396
+ }
397
+
398
+ visual_col = next_col;
399
+ }
400
+
401
+ let start = start_byte. unwrap_or_else ( || self . line_buffer . len ( ) . saturating_sub ( 1 ) ) ;
402
+ let end = end_byte. unwrap_or ( self . line_buffer . len ( ) ) ;
403
+ let len = end. saturating_sub ( start) . max ( 1 ) ;
404
+
405
+ ( start. min ( self . line_buffer . len ( ) . saturating_sub ( 1 ) ) , len)
406
+ }
368
407
}
369
408
370
409
pub ( crate ) type FormatErrorMap = HashMap < FileName , Vec < FormattingError > > ;
@@ -600,6 +639,7 @@ impl<'a> FormatLines<'a> {
600
639
is_comment,
601
640
is_string,
602
641
line_buffer : self . line_buffer . clone ( ) ,
642
+ tab_spaces : self . config . tab_spaces ( ) ,
603
643
} ) ;
604
644
}
605
645
0 commit comments