11use std:: borrow:: Cow ;
22use std:: collections:: BTreeMap ;
3+ use std:: num:: NonZeroU8 ;
34use std:: path:: Path ;
45
56use ruff_annotate_snippets:: {
@@ -828,6 +829,73 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
828829 path
829830}
830831
832+ #[ derive( Clone , Copy ) ]
833+ struct IndentWidth ( NonZeroU8 ) ;
834+
835+ impl IndentWidth {
836+ fn as_usize ( self ) -> usize {
837+ self . 0 . get ( ) as usize
838+ }
839+ }
840+
841+ impl Default for IndentWidth {
842+ fn default ( ) -> Self {
843+ Self ( NonZeroU8 :: new ( 4 ) . unwrap ( ) )
844+ }
845+ }
846+
847+ /// A measure of the width of a line of text.
848+ ///
849+ /// This is used to determine if a line is too long.
850+ /// It should be compared to a [`LineLength`].
851+ #[ derive( Clone , Copy ) ]
852+ struct LineWidthBuilder {
853+ /// The width of the line.
854+ width : usize ,
855+ /// The column of the line.
856+ /// This is used to calculate the width of tabs.
857+ column : usize ,
858+ /// The tab size to use when calculating the width of tabs.
859+ tab_size : IndentWidth ,
860+ }
861+
862+ impl LineWidthBuilder {
863+ fn get ( & self ) -> usize {
864+ self . width
865+ }
866+
867+ /// Creates a new `LineWidth` with the given tab size.
868+ fn new ( tab_size : IndentWidth ) -> Self {
869+ LineWidthBuilder {
870+ width : 0 ,
871+ column : 0 ,
872+ tab_size,
873+ }
874+ }
875+
876+ /// Adds the given character to the line width.
877+ #[ must_use]
878+ fn add_char ( mut self , c : char ) -> Self {
879+ let tab_size: usize = self . tab_size . as_usize ( ) ;
880+ match c {
881+ '\t' => {
882+ let tab_offset = tab_size - ( self . column % tab_size) ;
883+ self . width += tab_offset;
884+ self . column += tab_offset;
885+ }
886+ '\n' | '\r' => {
887+ self . width = 0 ;
888+ self . column = 0 ;
889+ }
890+ _ => {
891+ self . width += unicode_width:: UnicodeWidthChar :: width ( c) . unwrap_or ( 0 ) ;
892+ self . column += 1 ;
893+ }
894+ }
895+ self
896+ }
897+ }
898+
831899/// Given some source code and annotation ranges, this routine replaces tabs
832900/// with ASCII whitespace, and unprintable characters with printable
833901/// representations of them.
@@ -840,8 +908,7 @@ fn replace_whitespace_and_unprintable<'r>(
840908) -> EscapedSourceCode < ' r > {
841909 let mut result = String :: new ( ) ;
842910 let mut last_end = 0 ;
843- let mut column = 0 ;
844- const TAB_SIZE : usize = 4 ;
911+ let mut line_width = LineWidthBuilder :: new ( IndentWidth :: default ( ) ) ;
845912
846913 // Updates the annotation ranges given by the caller whenever a single byte (at `index` in
847914 // `source`) is replaced with `len` bytes.
@@ -872,24 +939,19 @@ fn replace_whitespace_and_unprintable<'r>(
872939 } ;
873940
874941 for ( index, c) in source. char_indices ( ) {
942+ let old_width = line_width. get ( ) ;
943+ line_width = line_width. add_char ( c) ;
944+
875945 if matches ! ( c, '\t' ) {
876- let tab_offset = TAB_SIZE - ( column % TAB_SIZE ) ;
877- column += tab_offset;
878- let tab_width = u32:: try_from ( tab_offset) . expect ( "small width because of tab size" ) ;
946+ let tab_width = u32:: try_from ( line_width. get ( ) - old_width)
947+ . expect ( "small width because of tab size" ) ;
879948 result. push_str ( & source[ last_end..index] ) ;
880949 for _ in 0 ..tab_width {
881950 result. push ( ' ' ) ;
882951 }
883952 last_end = index + 1 ;
884953 update_ranges ( index, tab_width) ;
885- } else {
886- match c {
887- '\n' | '\r' => column = 0 ,
888- _ => column += 1 ,
889- }
890- }
891-
892- if let Some ( printable) = unprintable_replacement ( c) {
954+ } else if let Some ( printable) = unprintable_replacement ( c) {
893955 result. push_str ( & source[ last_end..index] ) ;
894956 result. push ( printable) ;
895957 last_end = index + 1 ;
0 commit comments