@@ -5,7 +5,7 @@ use quote::ToTokens;
55use regex:: Regex ;
66use std:: {
77 borrow:: Cow ,
8- collections:: { HashMap , VecDeque } ,
8+ collections:: { HashMap , HashSet , VecDeque } ,
99 fmt:: { Result , Write } ,
1010} ;
1111use syn:: { spanned:: Spanned , token:: Brace , Expr } ;
@@ -848,92 +848,200 @@ impl<'a> Writer<'a> {
848848 static COMMENT_REGEX : Regex = Regex :: new( "\" [^\" ]*\" |(//.*)" ) . unwrap( ) ;
849849 }
850850
851- let pretty_expr = self . retrieve_formatted_expr ( & expr) . to_string ( ) ;
851+ let pretty = self . retrieve_formatted_expr ( & expr) . to_string ( ) ;
852+ let source = src_span. source_text ( ) . unwrap_or_default ( ) ;
853+ let mut src_lines = source. lines ( ) . peekable ( ) ;
852854
853- // Adding comments back to the formatted expression
854- let source_text = src_span. source_text ( ) . unwrap_or_default ( ) ;
855- let mut source_lines = source_text. lines ( ) . peekable ( ) ;
856- let mut output = String :: from ( "" ) ;
857- let mut printed_empty_line = false ;
855+ // Comments already in pretty output (from nested rsx!) - skip these from source
856+ let pretty_comments: HashSet < _ > = pretty
857+ . lines ( )
858+ . filter ( |l| l. trim ( ) . starts_with ( "//" ) )
859+ . map ( |l| l. trim ( ) )
860+ . collect ( ) ;
858861
859- if source_lines. peek ( ) . is_none ( ) {
860- output = pretty_expr;
862+ let mut out = String :: new ( ) ;
863+
864+ if src_lines. peek ( ) . is_none ( ) {
865+ out = pretty;
861866 } else {
862- for line in pretty_expr. lines ( ) {
863- let compacted_pretty_line = line. replace ( " " , "" ) . replace ( "," , "" ) ;
864- let trimmed_pretty_line = line. trim ( ) ;
867+ for line in pretty. lines ( ) {
868+ let trimmed = line. trim ( ) ;
869+ let compacted = line. replace ( " " , "" ) . replace ( "," , "" ) ;
870+
871+ // Pretty comments: consume matching source lines, preserve preceding empty lines
872+ if trimmed. starts_with ( "//" ) {
873+ if !out. is_empty ( ) {
874+ out. push ( '\n' ) ;
875+ }
876+ let mut had_empty = false ;
877+ while let Some ( s) = src_lines. peek ( ) {
878+ let t = s. trim ( ) ;
879+ if t. is_empty ( ) {
880+ had_empty = true ;
881+ src_lines. next ( ) ;
882+ } else if t == trimmed {
883+ src_lines. next ( ) ;
884+ break ;
885+ } else {
886+ break ;
887+ }
888+ }
889+ if had_empty {
890+ out. push ( '\n' ) ;
891+ }
892+ out. push_str ( line) ;
893+ continue ;
894+ }
865895
866- // Nested expressions might have comments already. We handle writing all of those
867- // at the outer level, so we skip them here
868- if trimmed_pretty_line. starts_with ( "//" ) {
896+ // Pretty empty lines: preserve and sync with source
897+ if trimmed. is_empty ( ) {
898+ if !out. is_empty ( ) {
899+ out. push ( '\n' ) ;
900+ }
901+ while src_lines
902+ . peek ( )
903+ . map ( |s| s. trim ( ) . is_empty ( ) )
904+ . unwrap_or ( false )
905+ {
906+ src_lines. next ( ) ;
907+ }
869908 continue ;
870909 }
871910
872- if !output . is_empty ( ) {
873- output . push ( '\n' ) ;
911+ if !out . is_empty ( ) {
912+ out . push ( '\n' ) ;
874913 }
875914
876- // pull down any source lines with whitespace until we hit a line that matches our current line.
877- while let Some ( src) = source_lines. peek ( ) {
878- let trimmed_src = src. trim ( ) ;
915+ // Scan source for comments/empty lines before the matching line
916+ let mut pending_comments = Vec :: new ( ) ;
917+ let mut had_empty = false ;
918+ let mut multiline: Option < Vec < & str > > = None ;
879919
880- // Write comments and empty lines as they are
881- if trimmed_src. starts_with ( "//" ) || trimmed_src. is_empty ( ) {
882- if !trimmed_src. is_empty ( ) {
883- // Match the whitespace of the incoming source line
884- for s in line. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) {
885- output. push ( s) ;
886- }
920+ while let Some ( src) = src_lines. peek ( ) {
921+ let src_trimmed = src. trim ( ) ;
887922
888- // Bump out the indent level if the line starts with a closing brace (ie we're at the end of a block)
889- if matches ! ( trimmed_pretty_line. chars( ) . next( ) , Some ( ')' | '}' | ']' ) ) {
890- output. push_str ( self . out . indent . indent_str ( ) ) ;
923+ if src_trimmed. is_empty ( ) || src_trimmed. starts_with ( "//" ) {
924+ if src_trimmed. is_empty ( ) {
925+ if pending_comments. is_empty ( ) {
926+ had_empty = true ;
891927 }
892-
893- printed_empty_line = false ;
894- output. push_str ( trimmed_src) ;
895- output. push ( '\n' ) ;
896- } else if !printed_empty_line {
897- output. push ( '\n' ) ;
898- printed_empty_line = true ;
928+ } else if !pretty_comments. contains ( src_trimmed) {
929+ pending_comments. push ( src_trimmed) ;
899930 }
900-
901- _ = source_lines. next ( ) ;
931+ src_lines. next ( ) ;
902932 continue ;
903933 }
904934
905- let compacted_src_line = src. replace ( " " , "" ) . replace ( "," , "" ) ;
935+ let src_compacted = src. replace ( " " , "" ) . replace ( "," , "" ) ;
906936
907- // If this source line matches our pretty line, we stop pulling down
908- if compacted_src_line . contains ( & compacted_pretty_line ) {
937+ // Exact match
938+ if src_compacted . contains ( & compacted ) {
909939 break ;
910940 }
911941
912- // Otherwise, consume this source line and keep going
913- _ = source_lines. next ( ) ;
942+ // Multi-line method chain (e.g., foo\n .bar()\n .baz())
943+ if !src_compacted. is_empty ( ) && compacted. starts_with ( & src_compacted) {
944+ let is_call = src_trimmed. ends_with ( '(' )
945+ || src_trimmed. ends_with ( ',' )
946+ || src_trimmed. ends_with ( '{' ) ;
947+ if !is_call {
948+ multiline = Some ( vec ! [ * src] ) ;
949+ break ;
950+ }
951+ }
952+
953+ // Non-matching line - clear pending and skip
954+ pending_comments. clear ( ) ;
955+ had_empty = false ;
956+ src_lines. next ( ) ;
957+ break ;
914958 }
915959
916- // Once all whitespace is written, write the pretty line
917- output. push_str ( line) ;
918- printed_empty_line = false ;
960+ // Output empty line if needed
961+ if had_empty {
962+ out. push ( '\n' ) ;
963+ }
919964
920- // And then pull the corresponding source line
921- let source_line = source_lines. next ( ) ;
965+ // Output pending comments
966+ for comment in & pending_comments {
967+ for c in line. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) {
968+ out. push ( c) ;
969+ }
970+ if matches ! ( trimmed. chars( ) . next( ) , Some ( ')' | '}' | ']' ) ) {
971+ out. push_str ( self . out . indent . indent_str ( ) ) ;
972+ }
973+ out. push_str ( comment) ;
974+ out. push ( '\n' ) ;
975+ }
922976
923- // And then write any inline comments
924- if let Some ( source_line) = source_line {
925- if let Some ( captures) = COMMENT_REGEX . with ( |f| f. captures ( source_line) ) {
926- if let Some ( comment) = captures. get ( 1 ) {
927- output. push_str ( " // " ) ;
928- output. push_str ( comment. as_str ( ) . replace ( "//" , "" ) . trim ( ) ) ;
977+ // Handle multi-line method chains
978+ if let Some ( mut ml) = multiline {
979+ src_lines. next ( ) ;
980+ let mut acc = ml[ 0 ] . replace ( " " , "" ) . replace ( "," , "" ) ;
981+
982+ while let Some ( src) = src_lines. peek ( ) {
983+ let t = src. trim ( ) ;
984+ if t. starts_with ( "//" ) {
985+ ml. push ( src) ;
986+ src_lines. next ( ) ;
987+ continue ;
988+ }
989+ if t. is_empty ( ) {
990+ src_lines. next ( ) ;
991+ continue ;
992+ }
993+
994+ acc. push_str ( & src. replace ( " " , "" ) . replace ( "," , "" ) ) ;
995+ ml. push ( src) ;
996+
997+ if acc. contains ( & compacted) {
998+ src_lines. next ( ) ;
999+ break ;
1000+ }
1001+
1002+ let cont = t. starts_with ( '.' )
1003+ || t. starts_with ( "&&" )
1004+ || t. starts_with ( "||" )
1005+ || matches ! ( t. chars( ) . next( ) , Some ( '+' | '-' | '*' | '/' | '?' ) ) ;
1006+
1007+ if cont || compacted. starts_with ( & acc) {
1008+ src_lines. next ( ) ;
1009+ continue ;
1010+ }
1011+ break ;
1012+ }
1013+
1014+ // Write multi-line with adjusted indentation
1015+ let base_indent = ml[ 0 ] . chars ( ) . take_while ( |c| c. is_whitespace ( ) ) . count ( ) ;
1016+ let target: String = line. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) . collect ( ) ;
1017+
1018+ for ( i, src_line) in ml. iter ( ) . enumerate ( ) {
1019+ let indent = src_line. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) . count ( ) ;
1020+ out. push_str ( & target) ;
1021+ for _ in 0 ..indent. saturating_sub ( base_indent) {
1022+ out. push ( ' ' ) ;
1023+ }
1024+ out. push_str ( src_line. trim ( ) ) ;
1025+ if i < ml. len ( ) - 1 {
1026+ out. push ( '\n' ) ;
1027+ }
1028+ }
1029+ } else {
1030+ // Single line - output pretty line and capture inline comments
1031+ out. push_str ( line) ;
1032+ if let Some ( src_line) = src_lines. next ( ) {
1033+ if let Some ( cap) = COMMENT_REGEX . with ( |r| r. captures ( src_line) ) {
1034+ if let Some ( c) = cap. get ( 1 ) {
1035+ out. push_str ( " // " ) ;
1036+ out. push_str ( c. as_str ( ) . replace ( "//" , "" ) . trim ( ) ) ;
1037+ }
9291038 }
9301039 }
9311040 }
9321041 }
9331042 }
9341043
935- self . write_mulitiline_tokens ( output) ?;
936-
1044+ self . write_mulitiline_tokens ( out) ?;
9371045 Ok ( ( ) )
9381046 }
9391047
0 commit comments