@@ -4,9 +4,9 @@ use std::iter::zip;
44use std:: ops:: Index ;
55
66use tombi_syntax:: SyntaxKind :: {
7- ARRAY_OF_TABLE , BARE_KEY , BASIC_STRING , BRACKET_END , BRACKET_START , COMMENT , DANGLING_COMMENT_GROUP ,
8- DOUBLE_BRACKET_START , EQUAL , INLINE_TABLE , KEY_VALUE , KEY_VALUE_GROUP , KEY_VALUE_WITH_COMMA_GROUP , KEYS ,
9- LINE_BREAK , LITERAL_STRING , TABLE , WHITESPACE ,
7+ ARRAY_OF_TABLE , BARE_KEY , BASIC_STRING , BRACE_START , BRACKET_END , BRACKET_START , COMMA , COMMENT ,
8+ DANGLING_COMMENT_GROUP , DOUBLE_BRACKET_START , EQUAL , INLINE_TABLE , KEY_VALUE , KEY_VALUE_GROUP ,
9+ KEY_VALUE_WITH_COMMA_GROUP , KEYS , LINE_BREAK , LITERAL_STRING , TABLE , WHITESPACE ,
1010} ;
1111use tombi_syntax:: { SyntaxElement , SyntaxKind , SyntaxNode } ;
1212
@@ -1200,46 +1200,148 @@ fn detect_schema<'a>(keys: &[String], schemas: &'a [InlineTableSchema]) -> Optio
12001200 . map ( |s| s. key_order )
12011201}
12021202
1203- fn reorder_single_inline_table ( node : & SyntaxNode , schemas : & [ InlineTableSchema ] ) {
1204- let kv_pairs: Vec < ( String , String ) > = node
1205- . children ( )
1206- . filter ( |n| n. kind ( ) == KEY_VALUE_WITH_COMMA_GROUP )
1207- . flat_map ( |group| {
1208- group
1209- . children ( )
1210- . filter ( |n| n. kind ( ) == KEY_VALUE )
1211- . map ( |kv| ( inline_table_key_name ( & kv) , kv. text ( ) . to_string ( ) . trim ( ) . to_string ( ) ) )
1212- . collect :: < Vec < _ > > ( )
1213- } )
1214- . collect ( ) ;
1203+ /// One key-value of an inline table together with the comments bound to it: `leading` holds
1204+ /// own-line comments that sit before the key, `trailing` the same-line comment after its comma.
1205+ /// Carrying both lets a key keep its comments when the keys get reordered.
1206+ struct InlineEntry {
1207+ key : String ,
1208+ text : String ,
1209+ leading : Vec < String > ,
1210+ trailing : Option < String > ,
1211+ }
1212+
1213+ /// Split a `KEY_VALUE` node into its `key = value` text and the own-line comments that precede
1214+ /// the key. Tombi stores those comments (and the multi-line layout) as leading trivia before the
1215+ /// `KEYS` child, so everything from `KEYS` onward is the value text and the rest is layout.
1216+ fn clean_key_value ( kv : & SyntaxNode ) -> ( String , Vec < String > ) {
1217+ let ( mut text, mut leading, mut started) = ( String :: new ( ) , Vec :: new ( ) , false ) ;
1218+ for child in kv. children_with_tokens ( ) {
1219+ if started {
1220+ text. push_str ( & child. to_string ( ) ) ;
1221+ } else if child. kind ( ) == KEYS {
1222+ started = true ;
1223+ text. push_str ( & child. to_string ( ) ) ;
1224+ } else if child. kind ( ) == COMMENT {
1225+ leading. push ( child. to_string ( ) . trim ( ) . to_string ( ) ) ;
1226+ }
1227+ }
1228+ ( text. trim ( ) . to_string ( ) , leading)
1229+ }
1230+
1231+ /// Collect the inline table's entries with their comments, or `None` when the table holds a
1232+ /// comment that is not bound to a key. Own-line comments before a key live in that key's leading
1233+ /// trivia and trailing comments live in the preceding `COMMA`, so both move with their key. A
1234+ /// comment that is a direct child of the inline table (a dangling comment before the closing
1235+ /// brace) belongs to no key and would be lost by the rebuild, so the caller leaves the table
1236+ /// untouched instead.
1237+ fn collect_inline_entries ( node : & SyntaxNode ) -> Option < Vec < InlineEntry > > {
1238+ // Comments before `BRACE_START` are the array entry's own leading trivia (the splice keeps
1239+ // them); a comment inside the braces that is not part of a key or comma (a dangling comment
1240+ // before the closing brace) would be lost by the rebuild, so leave the table untouched.
1241+ if node
1242+ . children_with_tokens ( )
1243+ . skip_while ( |c| c. kind ( ) != BRACE_START )
1244+ . any ( |c| matches ! ( c. kind( ) , COMMENT | DANGLING_COMMENT_GROUP ) )
1245+ {
1246+ return None ;
1247+ }
1248+ let mut entries: Vec < InlineEntry > = Vec :: new ( ) ;
1249+ for group in node. children ( ) . filter ( |n| n. kind ( ) == KEY_VALUE_WITH_COMMA_GROUP ) {
1250+ for child in group. children_with_tokens ( ) {
1251+ match child. kind ( ) {
1252+ KEY_VALUE => {
1253+ let kv = child. as_node ( ) . unwrap ( ) ;
1254+ let ( text, leading) = clean_key_value ( kv) ;
1255+ entries. push ( InlineEntry {
1256+ key : inline_table_key_name ( kv) ,
1257+ text,
1258+ leading,
1259+ trailing : None ,
1260+ } ) ;
1261+ }
1262+ COMMA => {
1263+ if let Some ( comment) = child
1264+ . as_node ( )
1265+ . and_then ( |comma| comma. children_with_tokens ( ) . find ( |c| c. kind ( ) == COMMENT ) )
1266+ {
1267+ entries
1268+ . last_mut ( )
1269+ . expect ( "a comma always follows a key-value in an inline table" )
1270+ . trailing = Some ( comment. to_string ( ) . trim ( ) . to_string ( ) ) ;
1271+ }
1272+ }
1273+ _ => { }
1274+ }
1275+ }
1276+ }
1277+ Some ( entries)
1278+ }
12151279
1216- if kv_pairs. len ( ) < 2 {
1280+ /// Render the reordered entries back into inline-table source. With no comments the compact
1281+ /// single-line form is kept; any comment forces the multi-line form, the only shape that can
1282+ /// carry a comment inside an inline table.
1283+ fn build_inline_table_text ( entries : & [ & InlineEntry ] ) -> String {
1284+ if entries. iter ( ) . all ( |e| e. leading . is_empty ( ) && e. trailing . is_none ( ) ) {
1285+ let joined = entries. iter ( ) . map ( |e| e. text . as_str ( ) ) . collect :: < Vec < _ > > ( ) . join ( ", " ) ;
1286+ return format ! ( "_x = {{ {joined} }}\n " ) ;
1287+ }
1288+ let mut out = String :: from ( "_x = {\n " ) ;
1289+ for ( idx, entry) in entries. iter ( ) . enumerate ( ) {
1290+ for comment in & entry. leading {
1291+ out. push_str ( " " ) ;
1292+ out. push_str ( comment) ;
1293+ out. push ( '\n' ) ;
1294+ }
1295+ out. push_str ( " " ) ;
1296+ out. push_str ( & entry. text ) ;
1297+ if idx + 1 < entries. len ( ) {
1298+ out. push ( ',' ) ;
1299+ }
1300+ if let Some ( comment) = & entry. trailing {
1301+ out. push ( ' ' ) ;
1302+ out. push_str ( comment) ;
1303+ }
1304+ out. push ( '\n' ) ;
1305+ }
1306+ out. push_str ( "}\n " ) ;
1307+ out
1308+ }
1309+
1310+ fn reorder_single_inline_table ( node : & SyntaxNode , schemas : & [ InlineTableSchema ] ) {
1311+ let Some ( entries) = collect_inline_entries ( node) else {
1312+ return ;
1313+ } ;
1314+ if entries. len ( ) < 2 {
12171315 return ;
12181316 }
12191317
1220- let keys: Vec < String > = kv_pairs . iter ( ) . map ( |( k , _ ) | k . clone ( ) ) . collect ( ) ;
1318+ let keys: Vec < String > = entries . iter ( ) . map ( |e| e . key . clone ( ) ) . collect ( ) ;
12211319 let Some ( schema) = detect_schema ( & keys, schemas) else {
12221320 return ;
12231321 } ;
12241322
12251323 let key_position = |k : & str | -> usize { schema. iter ( ) . position ( |s| * s == k) . unwrap_or ( usize:: MAX ) } ;
1226- let mut sorted = kv_pairs . clone ( ) ;
1227- sorted . sort_by_key ( |( k , _ ) | key_position ( k ) ) ;
1324+ let mut order : Vec < usize > = ( 0 ..entries . len ( ) ) . collect ( ) ;
1325+ order . sort_by_key ( |& i | key_position ( & entries [ i ] . key ) ) ;
12281326
1229- if sorted . iter ( ) . map ( | ( k , _ ) | k ) . eq ( kv_pairs . iter ( ) . map ( | ( k , _ ) | k ) ) {
1327+ if order . iter ( ) . enumerate ( ) . all ( | ( new , & old ) | new == old ) {
12301328 return ;
12311329 }
12321330
1233- let entries : Vec < & str > = sorted . iter ( ) . map ( |( _ , v ) | v . as_str ( ) ) . collect ( ) ;
1234- let rebuilt = format ! ( "_x = {{ {} }} \n " , entries . join ( ", " ) ) ;
1331+ let sorted : Vec < & InlineEntry > = order . iter ( ) . map ( |& i| & entries [ i ] ) . collect ( ) ;
1332+ let rebuilt = build_inline_table_text ( & sorted ) ;
12351333 let parsed = parse ( & rebuilt) ;
12361334 let new_children: Option < Vec < SyntaxElement > > = parsed
12371335 . descendants ( )
12381336 . find ( |n| n. kind ( ) == INLINE_TABLE )
12391337 . map ( |n| n. children_with_tokens ( ) . collect ( ) ) ;
12401338
12411339 if let Some ( children) = new_children {
1242- node. splice_children ( 0 ..node. children_with_tokens ( ) . count ( ) , children) ;
1340+ let original: Vec < SyntaxElement > = node. children_with_tokens ( ) . collect ( ) ;
1341+ let brace_idx = original. iter ( ) . position ( |c| c. kind ( ) == BRACE_START ) . unwrap_or ( 0 ) ;
1342+ let mut merged = original[ ..brace_idx] . to_vec ( ) ;
1343+ merged. extend ( children) ;
1344+ node. splice_children ( 0 ..original. len ( ) , merged) ;
12431345 }
12441346}
12451347
0 commit comments