yerba 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +492 -15
  3. data/ext/yerba/extconf.rb +87 -30
  4. data/ext/yerba/include/yerba.h +168 -0
  5. data/ext/yerba/yerba.c +752 -0
  6. data/lib/yerba/collection.rb +31 -0
  7. data/lib/yerba/document.rb +59 -0
  8. data/lib/yerba/formatting.rb +18 -0
  9. data/lib/yerba/location.rb +5 -0
  10. data/lib/yerba/map.rb +166 -0
  11. data/lib/yerba/scalar.rb +85 -0
  12. data/lib/yerba/sequence.rb +182 -0
  13. data/lib/yerba/version.rb +1 -1
  14. data/lib/yerba.rb +30 -4
  15. data/rust/Cargo.lock +378 -2
  16. data/rust/Cargo.toml +5 -1
  17. data/rust/build.rs +11 -0
  18. data/rust/cbindgen.toml +27 -0
  19. data/rust/src/commands/apply.rs +5 -0
  20. data/rust/src/commands/blank_lines.rs +58 -0
  21. data/rust/src/commands/check.rs +5 -0
  22. data/rust/src/commands/delete.rs +35 -0
  23. data/rust/src/commands/get.rs +194 -0
  24. data/rust/src/commands/init.rs +89 -0
  25. data/rust/src/commands/insert.rs +106 -0
  26. data/rust/src/commands/mate.rs +55 -0
  27. data/rust/src/commands/mod.rs +349 -0
  28. data/rust/src/commands/move_item.rs +54 -0
  29. data/rust/src/commands/move_key.rs +87 -0
  30. data/rust/src/commands/quote_style.rs +62 -0
  31. data/rust/src/commands/remove.rs +35 -0
  32. data/rust/src/commands/rename.rs +37 -0
  33. data/rust/src/commands/set.rs +59 -0
  34. data/rust/src/commands/sort.rs +52 -0
  35. data/rust/src/commands/sort_keys.rs +62 -0
  36. data/rust/src/commands/version.rs +8 -0
  37. data/rust/src/document.rs +798 -340
  38. data/rust/src/error.rs +0 -5
  39. data/rust/src/ffi.rs +991 -0
  40. data/rust/src/json.rs +49 -90
  41. data/rust/src/lib.rs +9 -2
  42. data/rust/src/main.rs +55 -839
  43. data/rust/src/selector.rs +241 -0
  44. data/rust/src/syntax.rs +97 -21
  45. data/rust/src/yaml_writer.rs +89 -0
  46. data/rust/src/yerbafile.rs +34 -122
  47. data/yerba.gemspec +4 -0
  48. metadata +33 -1
data/rust/src/document.rs CHANGED
@@ -11,18 +11,11 @@ use crate::error::YerbaError;
11
11
  use crate::QuoteStyle;
12
12
 
13
13
  use crate::syntax::{
14
- extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key, is_yaml_non_string,
15
- preceding_whitespace_indent, preceding_whitespace_token, removal_range, unescape_double_quoted,
16
- unescape_single_quoted,
14
+ extract_scalar, extract_scalar_text, find_entry_by_key, find_scalar_token, format_scalar_value, is_map_key,
15
+ is_yaml_non_string, preceding_whitespace_indent, preceding_whitespace_token, removal_range, unescape_double_quoted,
16
+ unescape_single_quoted, ScalarValue,
17
17
  };
18
18
 
19
- #[derive(Debug)]
20
- pub struct FindResult {
21
- pub text: String,
22
- pub line: usize,
23
- pub end_line: usize,
24
- }
25
-
26
19
  #[derive(Debug, Clone)]
27
20
  pub struct SortField {
28
21
  pub path: String,
@@ -66,6 +59,8 @@ pub enum InsertPosition {
66
59
  Last,
67
60
  Before(String),
68
61
  After(String),
62
+ BeforeCondition(String),
63
+ AfterCondition(String),
69
64
  FromSortOrder(Vec<String>),
70
65
  }
71
66
 
@@ -98,56 +93,79 @@ impl Document {
98
93
  return self.get_all(dot_path).into_iter().next();
99
94
  }
100
95
 
101
- let keys: Vec<&str> = dot_path.split('.').collect();
102
- let current_node = self.navigate_to_path(&keys).ok()?;
96
+ let current_node = self.navigate(dot_path).ok()?;
103
97
 
104
98
  extract_scalar_text(&current_node)
105
99
  }
106
100
 
107
101
  pub fn get_all(&self, dot_path: &str) -> Vec<String> {
108
102
  self
109
- .navigate_to_many(dot_path)
103
+ .navigate_all(dot_path)
110
104
  .iter()
111
105
  .filter_map(extract_scalar_text)
112
106
  .collect()
113
107
  }
114
108
 
115
- pub fn find_items(&self, dot_path: &str, condition: &str) -> Vec<FindResult> {
116
- let source = self.root.text().to_string();
117
- let nodes = self.navigate_to_many(dot_path);
109
+ pub fn get_typed(&self, dot_path: &str) -> Option<ScalarValue> {
110
+ if crate::selector::Selector::parse(dot_path).has_wildcard() {
111
+ return self.get_all_typed(dot_path).into_iter().next();
112
+ }
113
+
114
+ let current_node = self.navigate(dot_path).ok()?;
115
+
116
+ if current_node
117
+ .descendants()
118
+ .any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
119
+ {
120
+ return None;
121
+ }
122
+
123
+ extract_scalar(&current_node)
124
+ }
118
125
 
119
- nodes
126
+ pub fn get_all_typed(&self, dot_path: &str) -> Vec<ScalarValue> {
127
+ self
128
+ .navigate_all(dot_path)
120
129
  .iter()
121
- .filter(|node| self.evaluate_condition_on_node(node, condition))
122
- .map(|node| {
123
- let start_offset: usize = node.text_range().start().into();
124
- let end_offset: usize = node.text_range().end().into();
125
-
126
- FindResult {
127
- text: node.text().to_string(),
128
- line: byte_offset_to_line(&source, start_offset),
129
- end_line: byte_offset_to_line(&source, end_offset),
130
- }
130
+ .filter(|node| {
131
+ !node
132
+ .descendants()
133
+ .any(|child| child.kind() == SyntaxKind::BLOCK_MAP || child.kind() == SyntaxKind::BLOCK_SEQ)
131
134
  })
135
+ .filter_map(extract_scalar)
132
136
  .collect()
133
137
  }
134
138
 
135
- pub fn find_all(&self, dot_path: &str) -> Vec<FindResult> {
136
- let source = self.root.text().to_string();
139
+ pub fn get_value(&self, dot_path: &str) -> Option<serde_yaml::Value> {
140
+ if dot_path.is_empty() {
141
+ return Some(node_to_yaml_value(&self.root));
142
+ }
143
+
144
+ let nodes = self.navigate_all(dot_path);
137
145
 
146
+ if nodes.is_empty() {
147
+ return None;
148
+ }
149
+
150
+ if nodes.len() == 1 {
151
+ return Some(node_to_yaml_value(&nodes[0]));
152
+ }
153
+
154
+ let values: Vec<serde_yaml::Value> = nodes.iter().map(node_to_yaml_value).collect();
155
+
156
+ Some(serde_yaml::Value::Sequence(values))
157
+ }
158
+
159
+ pub fn get_values(&self, dot_path: &str) -> Vec<serde_yaml::Value> {
160
+ self.navigate_all(dot_path).iter().map(node_to_yaml_value).collect()
161
+ }
162
+
163
+ pub fn filter(&self, dot_path: &str, condition: &str) -> Vec<serde_yaml::Value> {
138
164
  self
139
- .navigate_to_many(dot_path)
165
+ .navigate_all(dot_path)
140
166
  .iter()
141
- .map(|node| {
142
- let start_offset: usize = node.text_range().start().into();
143
- let end_offset: usize = node.text_range().end().into();
144
-
145
- FindResult {
146
- text: node.text().to_string(),
147
- line: byte_offset_to_line(&source, start_offset),
148
- end_line: byte_offset_to_line(&source, end_offset),
149
- }
150
- })
167
+ .filter(|node| self.evaluate_condition_on_node(node, condition))
168
+ .map(node_to_yaml_value)
151
169
  .collect()
152
170
  }
153
171
 
@@ -159,9 +177,13 @@ impl Document {
159
177
  None => return false,
160
178
  };
161
179
 
162
- let left_path = left.strip_prefix('.').unwrap_or(&left);
163
- let target_nodes = navigate_from_node(node, left_path);
180
+ let path = crate::selector::Selector::parse(&left);
164
181
 
182
+ if !path.is_relative() {
183
+ return false;
184
+ }
185
+
186
+ let target_nodes = navigate_from_node(node, &path.to_selector_string());
165
187
  let values: Vec<String> = target_nodes.iter().filter_map(extract_scalar_text).collect();
166
188
 
167
189
  match operator {
@@ -207,7 +229,7 @@ impl Document {
207
229
 
208
230
  pub fn exists(&self, dot_path: &str) -> bool {
209
231
  if dot_path.contains('[') {
210
- return !self.navigate_to_many(dot_path).is_empty();
232
+ return !self.navigate_all(dot_path).is_empty();
211
233
  }
212
234
 
213
235
  self.get(dot_path).is_some()
@@ -221,17 +243,21 @@ impl Document {
221
243
  None => return false,
222
244
  };
223
245
 
224
- let full_path = if let Some(relative_key) = left.strip_prefix('.') {
246
+ let path = crate::selector::Selector::parse(&left);
247
+
248
+ let full_path = if path.is_relative() {
249
+ let path_string = path.to_selector_string();
250
+
225
251
  if parent_path.is_empty() {
226
- relative_key.to_string()
252
+ path_string
227
253
  } else {
228
- format!("{}.{}", parent_path, relative_key)
254
+ format!("{}.{}", parent_path, path_string)
229
255
  }
230
256
  } else {
231
- left.to_string()
257
+ path.to_selector_string()
232
258
  };
233
259
 
234
- let has_brackets = full_path.contains('[');
260
+ let has_brackets = crate::selector::Selector::parse(&full_path).has_brackets();
235
261
 
236
262
  match operator {
237
263
  "==" => {
@@ -291,8 +317,7 @@ impl Document {
291
317
  }
292
318
 
293
319
  pub fn get_sequence_values(&self, dot_path: &str) -> Vec<String> {
294
- let keys: Vec<&str> = dot_path.split('.').collect();
295
- let current_node = match self.navigate_to_path(&keys) {
320
+ let current_node = match self.navigate(dot_path) {
296
321
  Ok(node) => node,
297
322
  Err(_) => return Vec::new(),
298
323
  };
@@ -309,8 +334,24 @@ impl Document {
309
334
  }
310
335
 
311
336
  pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
312
- let keys: Vec<&str> = dot_path.split('.').collect();
313
- let current_node = self.navigate_to_path(&keys)?;
337
+ let current_node = self.navigate(dot_path)?;
338
+
339
+ if let Some(block_scalar) = current_node
340
+ .descendants()
341
+ .find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR)
342
+ {
343
+ let new_text = if value.is_empty() {
344
+ "\"\"".to_string()
345
+ } else if value.contains('\n') {
346
+ format!("|-\n {}", value.replace('\n', "\n "))
347
+ } else {
348
+ format!("\"{}\"", value.replace('"', "\\\""))
349
+ };
350
+
351
+ let range = block_scalar.text_range();
352
+
353
+ return self.apply_edit(range, &new_text);
354
+ }
314
355
 
315
356
  let scalar_token =
316
357
  find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
@@ -320,14 +361,64 @@ impl Document {
320
361
  self.replace_token(&scalar_token, &new_text)
321
362
  }
322
363
 
364
+ pub fn set_scalar_style(&mut self, dot_path: &str, style: &QuoteStyle) -> Result<(), YerbaError> {
365
+ let current_node = self.navigate(dot_path)?;
366
+ let scalar_token =
367
+ find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
368
+
369
+ let current_kind = scalar_token.kind();
370
+ let target_kind = style.to_syntax_kind();
371
+
372
+ if current_kind == target_kind {
373
+ return Ok(());
374
+ }
375
+
376
+ let raw_value = match current_kind {
377
+ SyntaxKind::DOUBLE_QUOTED_SCALAR => {
378
+ let text = scalar_token.text();
379
+ unescape_double_quoted(&text[1..text.len() - 1])
380
+ }
381
+
382
+ SyntaxKind::SINGLE_QUOTED_SCALAR => {
383
+ let text = scalar_token.text();
384
+ unescape_single_quoted(&text[1..text.len() - 1])
385
+ }
386
+
387
+ SyntaxKind::PLAIN_SCALAR => scalar_token.text().to_string(),
388
+
389
+ _ => return Ok(()),
390
+ };
391
+
392
+ let new_text = format_scalar_value(&raw_value, target_kind);
393
+
394
+ self.replace_token(&scalar_token, &new_text)
395
+ }
396
+
397
+ pub fn set_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
398
+ let current_node = self.navigate(dot_path)?;
399
+
400
+ if let Some(block_scalar) = current_node
401
+ .descendants()
402
+ .find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR)
403
+ {
404
+ let range = block_scalar.text_range();
405
+ return self.apply_edit(range, value);
406
+ }
407
+
408
+ let scalar_token =
409
+ find_scalar_token(&current_node).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
410
+
411
+ self.replace_token(&scalar_token, value)
412
+ }
413
+
323
414
  pub fn append(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
324
415
  self.insert_into(dot_path, value, InsertPosition::Last)
325
416
  }
326
417
 
327
418
  pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
328
- let keys: Vec<&str> = dot_path.split('.').collect();
419
+ Self::validate_path(dot_path)?;
329
420
 
330
- if let Ok(current_node) = self.navigate_to_path(&keys) {
421
+ if let Ok(current_node) = self.navigate(dot_path) {
331
422
  if current_node.descendants().find_map(BlockSeq::cast).is_some() {
332
423
  return self.insert_sequence_item(dot_path, value, position);
333
424
  }
@@ -339,8 +430,7 @@ impl Document {
339
430
  }
340
431
 
341
432
  fn insert_sequence_item(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
342
- let keys: Vec<&str> = dot_path.split('.').collect();
343
- let current_node = self.navigate_to_path(&keys)?;
433
+ let current_node = self.navigate(dot_path)?;
344
434
 
345
435
  let sequence = current_node
346
436
  .descendants()
@@ -359,7 +449,37 @@ impl Document {
359
449
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
360
450
  .unwrap_or_default();
361
451
 
362
- let new_item = format!("- {}", value);
452
+ let new_item = if value.contains('\n') {
453
+ let item_indent = format!("{} ", indent);
454
+ let lines: Vec<&str> = value.split('\n').collect();
455
+
456
+ let min_indent = lines
457
+ .iter()
458
+ .skip(1)
459
+ .filter(|line| !line.trim().is_empty())
460
+ .map(|line| line.len() - line.trim_start().len())
461
+ .min()
462
+ .unwrap_or(0);
463
+
464
+ let indented: Vec<String> = lines
465
+ .iter()
466
+ .enumerate()
467
+ .map(|(index, line)| {
468
+ if index == 0 {
469
+ line.to_string()
470
+ } else if line.trim().is_empty() {
471
+ String::new()
472
+ } else {
473
+ let relative = &line[min_indent..];
474
+ format!("{}{}", item_indent, relative)
475
+ }
476
+ })
477
+ .collect();
478
+
479
+ format!("- {}", indented.join("\n"))
480
+ } else {
481
+ format!("- {}", value)
482
+ };
363
483
 
364
484
  match position {
365
485
  InsertPosition::Last => {
@@ -421,6 +541,30 @@ impl Document {
421
541
  self.insert_after_node(target_entry.syntax(), &new_text)
422
542
  }
423
543
 
544
+ InsertPosition::BeforeCondition(condition) => {
545
+ let target_entry = entries
546
+ .iter()
547
+ .find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
548
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, condition)))?;
549
+
550
+ let target_range = target_entry.syntax().text_range();
551
+ let replacement = format!("{}\n{}", new_item, indent);
552
+ let insert_range = TextRange::new(target_range.start(), target_range.start());
553
+
554
+ self.apply_edit(insert_range, &replacement)
555
+ }
556
+
557
+ InsertPosition::AfterCondition(condition) => {
558
+ let target_entry = entries
559
+ .iter()
560
+ .find(|entry| self.evaluate_condition_on_node(entry.syntax(), &condition))
561
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, condition)))?;
562
+
563
+ let new_text = format!("\n{}{}", indent, new_item);
564
+
565
+ self.insert_after_node(target_entry.syntax(), &new_text)
566
+ }
567
+
424
568
  InsertPosition::FromSortOrder(_) => {
425
569
  let last_entry = entries.last().unwrap();
426
570
  let new_text = format!("\n{}{}", indent, new_item);
@@ -437,8 +581,7 @@ impl Document {
437
581
  value: &str,
438
582
  position: InsertPosition,
439
583
  ) -> Result<(), YerbaError> {
440
- let keys: Vec<&str> = dot_path.split('.').collect();
441
- let current_node = self.navigate_to_path(&keys)?;
584
+ let current_node = self.navigate(dot_path)?;
442
585
 
443
586
  let map = current_node
444
587
  .descendants()
@@ -513,6 +656,10 @@ impl Document {
513
656
  self.insert_after_node(target_entry.syntax(), &new_text)
514
657
  }
515
658
 
659
+ InsertPosition::BeforeCondition(_) | InsertPosition::AfterCondition(_) => {
660
+ self.insert_map_key(dot_path, key, value, InsertPosition::Last)
661
+ }
662
+
516
663
  InsertPosition::FromSortOrder(order) => {
517
664
  let new_key_position = order.iter().position(|ordered_key| ordered_key == key);
518
665
 
@@ -542,6 +689,9 @@ impl Document {
542
689
  }
543
690
 
544
691
  pub fn rename(&mut self, source_path: &str, destination_path: &str) -> Result<(), YerbaError> {
692
+ Self::validate_path(source_path)?;
693
+ Self::validate_path(destination_path)?;
694
+
545
695
  let source_parent = source_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
546
696
 
547
697
  let destination_parent = destination_path
@@ -555,9 +705,8 @@ impl Document {
555
705
  .unwrap_or(destination_path);
556
706
 
557
707
  if source_parent == destination_parent {
558
- let keys: Vec<&str> = source_path.split('.').collect();
559
- let parent_node = self.navigate_to_path(&keys[..keys.len() - 1])?;
560
- let source_key = keys.last().unwrap();
708
+ let (parent_path, source_key) = source_path.rsplit_once('.').unwrap_or(("", source_path));
709
+ let parent_node = self.navigate(parent_path)?;
561
710
 
562
711
  let map = parent_node
563
712
  .descendants()
@@ -588,9 +737,10 @@ impl Document {
588
737
  }
589
738
 
590
739
  pub fn delete(&mut self, dot_path: &str) -> Result<(), YerbaError> {
591
- let keys: Vec<&str> = dot_path.split('.').collect();
592
- let parent_node = self.navigate_to_path(&keys[..keys.len() - 1])?;
593
- let last_key = keys.last().unwrap();
740
+ Self::validate_path(dot_path)?;
741
+
742
+ let (parent_path, last_key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
743
+ let parent_node = self.navigate(parent_path)?;
594
744
 
595
745
  let map = parent_node
596
746
  .descendants()
@@ -603,8 +753,7 @@ impl Document {
603
753
  }
604
754
 
605
755
  pub fn remove(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
606
- let keys: Vec<&str> = dot_path.split('.').collect();
607
- let current_node = self.navigate_to_path(&keys)?;
756
+ let current_node = self.navigate(dot_path)?;
608
757
 
609
758
  let sequence = current_node
610
759
  .descendants()
@@ -625,13 +774,31 @@ impl Document {
625
774
  self.remove_node(target_entry.syntax())
626
775
  }
627
776
 
777
+ pub fn remove_at(&mut self, dot_path: &str, index: usize) -> Result<(), YerbaError> {
778
+ Self::validate_path(dot_path)?;
779
+
780
+ let current_node = self.navigate(dot_path)?;
781
+
782
+ let sequence = current_node
783
+ .descendants()
784
+ .find_map(BlockSeq::cast)
785
+ .ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
786
+
787
+ let entries: Vec<_> = sequence.entries().collect();
788
+
789
+ if index >= entries.len() {
790
+ return Err(YerbaError::IndexOutOfBounds(index, entries.len()));
791
+ }
792
+
793
+ self.remove_node(entries[index].syntax())
794
+ }
795
+
628
796
  pub fn move_item(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
629
797
  if from == to {
630
798
  return Ok(());
631
799
  }
632
800
 
633
- let keys: Vec<&str> = dot_path.split('.').collect();
634
- let current_node = self.navigate_to_path(&keys)?;
801
+ let current_node = self.navigate(dot_path)?;
635
802
 
636
803
  let sequence = current_node
637
804
  .descendants()
@@ -640,14 +807,7 @@ impl Document {
640
807
 
641
808
  let entries: Vec<_> = sequence.entries().collect();
642
809
 
643
- self.reorder_entries(
644
- &entries,
645
- from,
646
- to,
647
- |entry| entry.syntax().text().to_string(),
648
- |entry| preceding_whitespace_indent(entry.syntax()),
649
- sequence.syntax().text_range(),
650
- )
810
+ self.reorder_entries(sequence.syntax(), &entries, from, to)
651
811
  }
652
812
 
653
813
  pub fn move_key(&mut self, dot_path: &str, from: usize, to: usize) -> Result<(), YerbaError> {
@@ -655,8 +815,7 @@ impl Document {
655
815
  return Ok(());
656
816
  }
657
817
 
658
- let keys: Vec<&str> = dot_path.split('.').collect();
659
- let current_node = self.navigate_to_path(&keys)?;
818
+ let current_node = self.navigate(dot_path)?;
660
819
 
661
820
  let map = current_node
662
821
  .descendants()
@@ -665,19 +824,11 @@ impl Document {
665
824
 
666
825
  let entries: Vec<_> = map.entries().collect();
667
826
 
668
- self.reorder_entries(
669
- &entries,
670
- from,
671
- to,
672
- |entry| entry.syntax().text().to_string(),
673
- |entry| preceding_whitespace_indent(entry.syntax()),
674
- map.syntax().text_range(),
675
- )
827
+ self.reorder_entries(map.syntax(), &entries, from, to)
676
828
  }
677
829
 
678
830
  pub fn resolve_key_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
679
- let keys: Vec<&str> = dot_path.split('.').collect();
680
- let current_node = self.navigate_to_path(&keys)?;
831
+ let current_node = self.navigate(dot_path)?;
681
832
 
682
833
  let map = current_node
683
834
  .descendants()
@@ -709,8 +860,7 @@ impl Document {
709
860
  }
710
861
 
711
862
  pub fn resolve_sequence_index(&self, dot_path: &str, reference: &str) -> Result<usize, YerbaError> {
712
- let keys: Vec<&str> = dot_path.split('.').collect();
713
- let current_node = self.navigate_to_path(&keys)?;
863
+ let current_node = self.navigate(dot_path)?;
714
864
 
715
865
  let sequence = current_node
716
866
  .descendants()
@@ -727,6 +877,15 @@ impl Document {
727
877
  return Ok(index);
728
878
  }
729
879
 
880
+ if crate::selector::Selector::parse(reference).is_relative() {
881
+ return sequence
882
+ .entries()
883
+ .enumerate()
884
+ .find(|(_index, entry)| self.evaluate_condition_on_node(entry.syntax(), reference))
885
+ .map(|(index, _entry)| index)
886
+ .ok_or_else(|| YerbaError::PathNotFound(format!("{} condition '{}'", dot_path, reference)));
887
+ }
888
+
730
889
  sequence
731
890
  .entries()
732
891
  .enumerate()
@@ -752,8 +911,7 @@ impl Document {
752
911
  return self.validate_each_sort_keys(seq_path, key_order);
753
912
  }
754
913
 
755
- let keys: Vec<&str> = dot_path.split('.').collect();
756
- let current_node = self.navigate_to_path(&keys)?;
914
+ let current_node = self.navigate(dot_path)?;
757
915
 
758
916
  let map = current_node
759
917
  .descendants()
@@ -784,8 +942,7 @@ impl Document {
784
942
  return self.sort_each_keys(seq_path, key_order);
785
943
  }
786
944
 
787
- let keys: Vec<&str> = dot_path.split('.').collect();
788
- let current_node = self.navigate_to_path(&keys)?;
945
+ let current_node = self.navigate(dot_path)?;
789
946
 
790
947
  let map = current_node
791
948
  .descendants()
@@ -798,21 +955,23 @@ impl Document {
798
955
  return Ok(());
799
956
  }
800
957
 
801
- let entry_data: Vec<(String, String)> = entries
958
+ let (groups, range) = collect_groups_with_range(map.syntax());
959
+
960
+ let mut keyed: Vec<(String, EntryGroup)> = entries
802
961
  .iter()
803
- .map(|entry| {
962
+ .zip(groups)
963
+ .map(|(entry, group)| {
804
964
  let key_name = entry
805
965
  .key()
806
966
  .and_then(|key_node| extract_scalar_text(key_node.syntax()))
807
967
  .unwrap_or_default();
808
- let text = entry.syntax().text().to_string();
809
- (key_name, text)
968
+ (key_name, group)
810
969
  })
811
970
  .collect();
812
971
 
813
- let mut sorted = entry_data.clone();
972
+ let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
814
973
 
815
- sorted.sort_by(|(key_a, _), (key_b, _)| {
974
+ keyed.sort_by(|(key_a, _), (key_b, _)| {
816
975
  let position_a = key_order.iter().position(|&key| key == key_a);
817
976
  let position_b = key_order.iter().position(|&key| key == key_b);
818
977
 
@@ -821,16 +980,17 @@ impl Document {
821
980
  (Some(_), None) => std::cmp::Ordering::Less,
822
981
  (None, Some(_)) => std::cmp::Ordering::Greater,
823
982
  (None, None) => {
824
- let original_a = entry_data.iter().position(|(key, _)| key == key_a).unwrap();
825
- let original_b = entry_data.iter().position(|(key, _)| key == key_b).unwrap();
983
+ let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
984
+ let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
826
985
  original_a.cmp(&original_b)
827
986
  }
828
987
  }
829
988
  });
830
989
 
831
- if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
832
- == entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
833
- {
990
+ let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
991
+ let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
992
+
993
+ if sorted_keys == orig_refs {
834
994
  return Ok(());
835
995
  }
836
996
 
@@ -839,15 +999,14 @@ impl Document {
839
999
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
840
1000
  .unwrap_or_default();
841
1001
 
842
- let map_text = rebuild_entries(sorted.iter().map(|(_key, text)| text.as_str()), &indent);
843
- let map_range = map.syntax().text_range();
1002
+ let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
1003
+ let map_text = rebuild_from_groups(&sorted_groups, &indent);
844
1004
 
845
- self.apply_edit(map_range, &map_text)
1005
+ self.apply_edit(range, &map_text)
846
1006
  }
847
1007
 
848
1008
  pub fn sort_each_keys(&mut self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
849
- let keys: Vec<&str> = dot_path.split('.').collect();
850
- let current_node = self.navigate_to_path(&keys)?;
1009
+ let current_node = self.navigate(dot_path)?;
851
1010
 
852
1011
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
853
1012
  Some(sequence) => sequence,
@@ -870,21 +1029,23 @@ impl Document {
870
1029
  continue;
871
1030
  }
872
1031
 
873
- let entry_data: Vec<(String, String)> = entries
1032
+ let (groups, group_range) = collect_groups_with_range(map.syntax());
1033
+
1034
+ let mut keyed: Vec<(String, EntryGroup)> = entries
874
1035
  .iter()
875
- .map(|entry| {
1036
+ .zip(groups)
1037
+ .map(|(entry, group)| {
876
1038
  let key_name = entry
877
1039
  .key()
878
1040
  .and_then(|key_node| extract_scalar_text(key_node.syntax()))
879
1041
  .unwrap_or_default();
880
- let text = entry.syntax().text().to_string();
881
- (key_name, text)
1042
+ (key_name, group)
882
1043
  })
883
1044
  .collect();
884
1045
 
885
- let mut sorted = entry_data.clone();
1046
+ let original_keys: Vec<String> = keyed.iter().map(|(key, _)| key.clone()).collect();
886
1047
 
887
- sorted.sort_by(|(key_a, _), (key_b, _)| {
1048
+ keyed.sort_by(|(key_a, _), (key_b, _)| {
888
1049
  let position_a = key_order.iter().position(|&key| key == key_a);
889
1050
  let position_b = key_order.iter().position(|&key| key == key_b);
890
1051
 
@@ -894,17 +1055,18 @@ impl Document {
894
1055
  (None, Some(_)) => std::cmp::Ordering::Greater,
895
1056
 
896
1057
  (None, None) => {
897
- let original_a = entry_data.iter().position(|(key, _)| key == key_a).unwrap();
898
- let original_b = entry_data.iter().position(|(key, _)| key == key_b).unwrap();
1058
+ let original_a = original_keys.iter().position(|key| key == key_a).unwrap();
1059
+ let original_b = original_keys.iter().position(|key| key == key_b).unwrap();
899
1060
 
900
1061
  original_a.cmp(&original_b)
901
1062
  }
902
1063
  }
903
1064
  });
904
1065
 
905
- if sorted.iter().map(|(key, _)| key).collect::<Vec<_>>()
906
- == entry_data.iter().map(|(key, _)| key).collect::<Vec<_>>()
907
- {
1066
+ let sorted_keys: Vec<&str> = keyed.iter().map(|(key, _)| key.as_str()).collect();
1067
+ let orig_refs: Vec<&str> = original_keys.iter().map(|key| key.as_str()).collect();
1068
+
1069
+ if sorted_keys == orig_refs {
908
1070
  continue;
909
1071
  }
910
1072
 
@@ -913,9 +1075,9 @@ impl Document {
913
1075
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
914
1076
  .unwrap_or_default();
915
1077
 
916
- let map_text = rebuild_entries(sorted.iter().map(|(_key, text)| text.as_str()), &indent);
917
-
918
- edits.push((map.syntax().text_range(), map_text));
1078
+ let sorted_groups: Vec<EntryGroup> = keyed.into_iter().map(|(_, group)| group).collect();
1079
+ let map_text = rebuild_from_groups(&sorted_groups, &indent);
1080
+ edits.push((group_range, map_text));
919
1081
  }
920
1082
 
921
1083
  if edits.is_empty() {
@@ -942,8 +1104,7 @@ impl Document {
942
1104
  }
943
1105
 
944
1106
  pub fn validate_each_sort_keys(&self, dot_path: &str, key_order: &[&str]) -> Result<(), YerbaError> {
945
- let keys: Vec<&str> = dot_path.split('.').collect();
946
- let current_node = self.navigate_to_path(&keys)?;
1107
+ let current_node = self.navigate(dot_path)?;
947
1108
 
948
1109
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
949
1110
  Some(sequence) => sequence,
@@ -974,13 +1135,17 @@ impl Document {
974
1135
  }
975
1136
  }
976
1137
 
977
- pub fn sort_items(&mut self, dot_path: &str, sort_fields: &[SortField]) -> Result<(), YerbaError> {
1138
+ pub fn sort_items(
1139
+ &mut self,
1140
+ dot_path: &str,
1141
+ sort_fields: &[SortField],
1142
+ case_sensitive: bool,
1143
+ ) -> Result<(), YerbaError> {
978
1144
  if dot_path.contains("[].") {
979
- return self.sort_each_items(dot_path, sort_fields);
1145
+ return self.sort_each_items(dot_path, sort_fields, case_sensitive);
980
1146
  }
981
1147
 
982
- let keys: Vec<&str> = dot_path.split('.').collect();
983
- let current_node = self.navigate_to_path(&keys)?;
1148
+ let current_node = self.navigate(dot_path)?;
984
1149
 
985
1150
  let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
986
1151
  Some(sequence) => sequence,
@@ -993,9 +1158,12 @@ impl Document {
993
1158
  return Ok(());
994
1159
  }
995
1160
 
996
- let mut sortable: Vec<(Vec<String>, String)> = entries
1161
+ let (groups, range) = collect_groups_with_range(sequence.syntax());
1162
+
1163
+ let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
997
1164
  .iter()
998
- .map(|entry| {
1165
+ .zip(groups)
1166
+ .map(|(entry, group)| {
999
1167
  let sort_values = if sort_fields.is_empty() {
1000
1168
  vec![entry
1001
1169
  .flow()
@@ -1011,18 +1179,22 @@ impl Document {
1011
1179
  .collect()
1012
1180
  };
1013
1181
 
1014
- (sort_values, entry.syntax().text().to_string())
1182
+ (sort_values, group)
1015
1183
  })
1016
1184
  .collect();
1017
1185
 
1018
- let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
1186
+ let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1019
1187
 
1020
1188
  sortable.sort_by(|(values_a, _), (values_b, _)| {
1021
1189
  for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
1022
1190
  let value_a = &values_a[index];
1023
1191
  let value_b = &values_b[index];
1024
1192
 
1025
- let ordering = value_a.cmp(value_b);
1193
+ let ordering = if case_sensitive {
1194
+ value_a.cmp(value_b)
1195
+ } else {
1196
+ value_a.to_lowercase().cmp(&value_b.to_lowercase())
1197
+ };
1026
1198
 
1027
1199
  let ordering = if field.ascending { ordering } else { ordering.reverse() };
1028
1200
 
@@ -1032,15 +1204,19 @@ impl Document {
1032
1204
  }
1033
1205
 
1034
1206
  if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
1035
- return values_a[0].cmp(&values_b[0]);
1207
+ return if case_sensitive {
1208
+ values_a[0].cmp(&values_b[0])
1209
+ } else {
1210
+ values_a[0].to_lowercase().cmp(&values_b[0].to_lowercase())
1211
+ };
1036
1212
  }
1037
1213
 
1038
1214
  std::cmp::Ordering::Equal
1039
1215
  });
1040
1216
 
1041
- let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
1217
+ let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1042
1218
 
1043
- if sorted_texts == original_order {
1219
+ if sorted_bodies == original_bodies {
1044
1220
  return Ok(());
1045
1221
  }
1046
1222
 
@@ -1049,20 +1225,25 @@ impl Document {
1049
1225
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
1050
1226
  .unwrap_or_default();
1051
1227
 
1052
- let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
1053
- let sequence_range = sequence.syntax().text_range();
1228
+ let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
1229
+ let sequence_text = rebuild_from_groups(&sorted_groups, &indent);
1054
1230
 
1055
- self.apply_edit(sequence_range, &sequence_text)
1231
+ self.apply_edit(range, &sequence_text)
1056
1232
  }
1057
1233
 
1058
- fn sort_each_items(&mut self, dot_path: &str, sort_fields: &[SortField]) -> Result<(), YerbaError> {
1234
+ fn sort_each_items(
1235
+ &mut self,
1236
+ dot_path: &str,
1237
+ sort_fields: &[SortField],
1238
+ case_sensitive: bool,
1239
+ ) -> Result<(), YerbaError> {
1059
1240
  let (parent_path, child_path) = if let Some(last_bracket) = dot_path.rfind("[].") {
1060
1241
  (&dot_path[..last_bracket + 2], &dot_path[last_bracket + 3..])
1061
1242
  } else {
1062
1243
  (dot_path, "")
1063
1244
  };
1064
1245
 
1065
- let parent_nodes = self.navigate_to_many(parent_path);
1246
+ let parent_nodes = self.navigate_all(parent_path);
1066
1247
  let source = self.root.text().to_string();
1067
1248
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1068
1249
 
@@ -1085,9 +1266,12 @@ impl Document {
1085
1266
  continue;
1086
1267
  }
1087
1268
 
1088
- let mut sortable: Vec<(Vec<String>, String)> = entries
1269
+ let (groups, group_range) = collect_groups_with_range(sequence.syntax());
1270
+
1271
+ let mut sortable: Vec<(Vec<String>, EntryGroup)> = entries
1089
1272
  .iter()
1090
- .map(|entry| {
1273
+ .zip(groups)
1274
+ .map(|(entry, group)| {
1091
1275
  let sort_values = if sort_fields.is_empty() {
1092
1276
  vec![entry
1093
1277
  .flow()
@@ -1103,18 +1287,23 @@ impl Document {
1103
1287
  .collect()
1104
1288
  };
1105
1289
 
1106
- (sort_values, entry.syntax().text().to_string())
1290
+ (sort_values, group)
1107
1291
  })
1108
1292
  .collect();
1109
1293
 
1110
- let original_order: Vec<String> = sortable.iter().map(|(_, text)| text.clone()).collect();
1294
+ let original_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1111
1295
 
1112
1296
  sortable.sort_by(|(values_a, _), (values_b, _)| {
1113
1297
  for (index, field) in sort_fields.iter().enumerate().take(values_a.len()) {
1114
1298
  let value_a = &values_a[index];
1115
1299
  let value_b = &values_b[index];
1116
1300
 
1117
- let ordering = value_a.cmp(value_b);
1301
+ let ordering = if case_sensitive {
1302
+ value_a.cmp(value_b)
1303
+ } else {
1304
+ value_a.to_lowercase().cmp(&value_b.to_lowercase())
1305
+ };
1306
+
1118
1307
  let ordering = if field.ascending { ordering } else { ordering.reverse() };
1119
1308
 
1120
1309
  if ordering != std::cmp::Ordering::Equal {
@@ -1123,15 +1312,19 @@ impl Document {
1123
1312
  }
1124
1313
 
1125
1314
  if sort_fields.is_empty() && !values_a.is_empty() && !values_b.is_empty() {
1126
- return values_a[0].cmp(&values_b[0]);
1315
+ return if case_sensitive {
1316
+ values_a[0].cmp(&values_b[0])
1317
+ } else {
1318
+ values_a[0].to_lowercase().cmp(&values_b[0].to_lowercase())
1319
+ };
1127
1320
  }
1128
1321
 
1129
1322
  std::cmp::Ordering::Equal
1130
1323
  });
1131
1324
 
1132
- let sorted_texts: Vec<String> = sortable.into_iter().map(|(_, text)| text).collect();
1325
+ let sorted_bodies: Vec<String> = sortable.iter().map(|(_, group)| group.body.clone()).collect();
1133
1326
 
1134
- if sorted_texts == original_order {
1327
+ if sorted_bodies == original_bodies {
1135
1328
  continue;
1136
1329
  }
1137
1330
 
@@ -1140,9 +1333,9 @@ impl Document {
1140
1333
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
1141
1334
  .unwrap_or_default();
1142
1335
 
1143
- let sequence_text = rebuild_entries(sorted_texts.iter().map(|text| text.as_str()), &indent);
1144
-
1145
- edits.push((sequence.syntax().text_range(), sequence_text));
1336
+ let sorted_groups: Vec<EntryGroup> = sortable.into_iter().map(|(_, group)| group).collect();
1337
+ let sequence_text = rebuild_from_groups(&sorted_groups, &indent);
1338
+ edits.push((group_range, sequence_text));
1146
1339
  }
1147
1340
  }
1148
1341
 
@@ -1169,40 +1362,43 @@ impl Document {
1169
1362
  }
1170
1363
 
1171
1364
  pub fn enforce_blank_lines(&mut self, dot_path: &str, blank_lines: usize) -> Result<(), YerbaError> {
1172
- let keys: Vec<&str> = dot_path.split('.').collect();
1173
- let current_node = self.navigate_to_path(&keys)?;
1174
-
1175
- let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
1176
- Some(sequence) => sequence,
1177
- None => return Ok(()),
1365
+ let nodes = if dot_path.contains('[') {
1366
+ self.navigate_all(dot_path)
1367
+ } else {
1368
+ vec![self.navigate(dot_path)?]
1178
1369
  };
1179
1370
 
1180
- let entries: Vec<_> = sequence.entries().collect();
1371
+ let mut edits: Vec<(TextRange, String)> = Vec::new();
1181
1372
 
1182
- if entries.len() <= 1 {
1183
- return Ok(());
1184
- }
1373
+ for current_node in &nodes {
1374
+ let sequence = match current_node.descendants().find_map(BlockSeq::cast) {
1375
+ Some(sequence) => sequence,
1376
+ None => continue,
1377
+ };
1185
1378
 
1186
- let source = self.root.text().to_string();
1187
- let mut edits: Vec<(TextRange, String)> = Vec::new();
1379
+ let entries: Vec<_> = sequence.entries().collect();
1188
1380
 
1189
- for entry in entries.iter().skip(1) {
1190
- if let Some(whitespace_token) = preceding_whitespace_token(entry.syntax()) {
1191
- let whitespace_text = whitespace_token.text();
1381
+ if entries.len() <= 1 {
1382
+ continue;
1383
+ }
1192
1384
 
1193
- let newline_count = whitespace_text.chars().filter(|character| *character == '\n').count();
1385
+ for entry in entries.iter().skip(1) {
1386
+ if let Some(whitespace_token) = preceding_whitespace_token(entry.syntax()) {
1387
+ let whitespace_text = whitespace_token.text();
1388
+ let newline_count = whitespace_text.chars().filter(|character| *character == '\n').count();
1194
1389
 
1195
- let indent = whitespace_text
1196
- .rfind('\n')
1197
- .map(|position| &whitespace_text[position + 1..])
1198
- .unwrap_or("");
1390
+ let indent = whitespace_text
1391
+ .rfind('\n')
1392
+ .map(|position| &whitespace_text[position + 1..])
1393
+ .unwrap_or("");
1199
1394
 
1200
- let desired_newlines = blank_lines + 1;
1395
+ let desired_newlines = blank_lines + 1;
1201
1396
 
1202
- if newline_count != desired_newlines {
1203
- let new_whitespace = format!("{}{}", "\n".repeat(desired_newlines), indent);
1397
+ if newline_count != desired_newlines {
1398
+ let new_whitespace = format!("{}{}", "\n".repeat(desired_newlines), indent);
1204
1399
 
1205
- edits.push((whitespace_token.text_range(), new_whitespace));
1400
+ edits.push((whitespace_token.text_range(), new_whitespace));
1401
+ }
1206
1402
  }
1207
1403
  }
1208
1404
  }
@@ -1211,8 +1407,9 @@ impl Document {
1211
1407
  return Ok(());
1212
1408
  }
1213
1409
 
1214
- edits.reverse();
1410
+ edits.sort_by_key(|edit| std::cmp::Reverse(edit.0.start()));
1215
1411
 
1412
+ let source = self.root.text().to_string();
1216
1413
  let mut new_source = source;
1217
1414
 
1218
1415
  for (range, replacement) in edits {
@@ -1232,20 +1429,19 @@ impl Document {
1232
1429
  pub fn enforce_key_style(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
1233
1430
  let source = self.root.text().to_string();
1234
1431
 
1235
- let scope_node = match dot_path {
1236
- Some(path) if !path.is_empty() => {
1237
- let keys: Vec<&str> = path.split('.').collect();
1238
- self.navigate_to_path(&keys)?
1239
- }
1240
- _ => self.root.clone(),
1432
+ let scope_ranges: Vec<TextRange> = match dot_path {
1433
+ Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
1434
+ _ => vec![self.root.text_range()],
1241
1435
  };
1242
1436
 
1243
- let scope_range = scope_node.text_range();
1244
1437
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1245
1438
 
1246
1439
  for element in self.root.descendants_with_tokens() {
1247
1440
  if let Some(token) = element.into_token() {
1248
- if !scope_range.contains_range(token.text_range()) {
1441
+ if !scope_ranges
1442
+ .iter()
1443
+ .any(|range| range.contains_range(token.text_range()))
1444
+ {
1249
1445
  continue;
1250
1446
  }
1251
1447
 
@@ -1335,21 +1531,19 @@ impl Document {
1335
1531
  pub fn enforce_quotes_at(&mut self, style: &QuoteStyle, dot_path: Option<&str>) -> Result<(), YerbaError> {
1336
1532
  let source = self.root.text().to_string();
1337
1533
 
1338
- let scope_node = match dot_path {
1339
- Some(path) if !path.is_empty() => {
1340
- let keys: Vec<&str> = path.split('.').collect();
1341
- self.navigate_to_path(&keys)?
1342
- }
1343
- _ => self.root.clone(),
1534
+ let scope_ranges: Vec<TextRange> = match dot_path {
1535
+ Some(path) if !path.is_empty() => self.navigate_all(path).iter().map(|node| node.text_range()).collect(),
1536
+ _ => vec![self.root.text_range()],
1344
1537
  };
1345
1538
 
1346
- let scope_range = scope_node.text_range();
1347
-
1348
1539
  let mut edits: Vec<(TextRange, String)> = Vec::new();
1349
1540
 
1350
1541
  for element in self.root.descendants_with_tokens() {
1351
1542
  if let Some(token) = element.into_token() {
1352
- if !scope_range.contains_range(token.text_range()) {
1543
+ if !scope_ranges
1544
+ .iter()
1545
+ .any(|range| range.contains_range(token.text_range()))
1546
+ {
1353
1547
  continue;
1354
1548
  }
1355
1549
 
@@ -1466,8 +1660,71 @@ impl Document {
1466
1660
  Ok(())
1467
1661
  }
1468
1662
 
1469
- pub fn navigate_to_many(&self, dot_path: &str) -> Vec<SyntaxNode> {
1470
- let segments = parse_path_segments(dot_path);
1663
+ pub fn navigate(&self, dot_path: &str) -> Result<SyntaxNode, YerbaError> {
1664
+ Self::validate_path(dot_path)?;
1665
+
1666
+ if dot_path.is_empty() {
1667
+ let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
1668
+
1669
+ let document = root
1670
+ .documents()
1671
+ .next()
1672
+ .ok_or_else(|| YerbaError::PathNotFound(dot_path.to_string()))?;
1673
+
1674
+ return Ok(document.syntax().clone());
1675
+ }
1676
+
1677
+ let nodes = self.navigate_all(dot_path);
1678
+
1679
+ match nodes.len() {
1680
+ 0 => Err(YerbaError::PathNotFound(dot_path.to_string())),
1681
+ 1 => Ok(nodes.into_iter().next().unwrap()),
1682
+ _ => Err(YerbaError::PathNotFound(format!(
1683
+ "{} (matched {} nodes, expected 1)",
1684
+ dot_path,
1685
+ nodes.len()
1686
+ ))),
1687
+ }
1688
+ }
1689
+
1690
+ pub fn validate_path(dot_path: &str) -> Result<(), YerbaError> {
1691
+ if dot_path.ends_with('.') {
1692
+ return Err(YerbaError::ParseError(format!(
1693
+ "invalid path: trailing dot in '{}'",
1694
+ dot_path
1695
+ )));
1696
+ }
1697
+
1698
+ if dot_path.contains("..") {
1699
+ return Err(YerbaError::ParseError(format!(
1700
+ "invalid path: double dot in '{}'",
1701
+ dot_path
1702
+ )));
1703
+ }
1704
+
1705
+ if dot_path.starts_with('.') {
1706
+ return Err(YerbaError::ParseError(format!(
1707
+ "invalid path: leading dot in '{}'",
1708
+ dot_path
1709
+ )));
1710
+ }
1711
+
1712
+ if dot_path.contains('[') && !dot_path.contains(']') {
1713
+ return Err(YerbaError::ParseError(format!(
1714
+ "invalid path: unclosed bracket in '{}'",
1715
+ dot_path
1716
+ )));
1717
+ }
1718
+
1719
+ Ok(())
1720
+ }
1721
+
1722
+ pub fn navigate_all(&self, dot_path: &str) -> Vec<SyntaxNode> {
1723
+ if Document::validate_path(dot_path).is_err() {
1724
+ return Vec::new();
1725
+ }
1726
+
1727
+ let parsed = crate::selector::Selector::parse(dot_path);
1471
1728
 
1472
1729
  let root = match Root::cast(self.root.clone()) {
1473
1730
  Some(root) => root,
@@ -1481,7 +1738,7 @@ impl Document {
1481
1738
 
1482
1739
  let mut current_nodes = vec![document.syntax().clone()];
1483
1740
 
1484
- if segments.is_empty() {
1741
+ if parsed.is_empty() {
1485
1742
  if let Some(sequence) = document.syntax().descendants().find_map(BlockSeq::cast) {
1486
1743
  current_nodes = sequence.entries().map(|entry| entry.syntax().clone()).collect();
1487
1744
  }
@@ -1489,7 +1746,7 @@ impl Document {
1489
1746
  return current_nodes;
1490
1747
  }
1491
1748
 
1492
- for segment in &segments {
1749
+ for segment in parsed.segments() {
1493
1750
  let mut next_nodes = Vec::new();
1494
1751
 
1495
1752
  for node in &current_nodes {
@@ -1506,37 +1763,6 @@ impl Document {
1506
1763
  current_nodes
1507
1764
  }
1508
1765
 
1509
- fn navigate_to_path(&self, keys: &[&str]) -> Result<SyntaxNode, YerbaError> {
1510
- let keys: Vec<&&str> = keys.iter().filter(|key| !key.is_empty()).collect();
1511
- let path_string = keys.iter().map(|key| **key).collect::<Vec<_>>().join(".");
1512
-
1513
- let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1514
-
1515
- let document = root
1516
- .documents()
1517
- .next()
1518
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1519
-
1520
- let mut current_node = document.syntax().clone();
1521
-
1522
- for key in &keys {
1523
- let map = current_node
1524
- .descendants()
1525
- .find_map(BlockMap::cast)
1526
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1527
-
1528
- let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1529
-
1530
- let map_value = entry
1531
- .value()
1532
- .ok_or_else(|| YerbaError::PathNotFound(path_string.clone()))?;
1533
-
1534
- current_node = map_value.syntax().clone();
1535
- }
1536
-
1537
- Ok(current_node)
1538
- }
1539
-
1540
1766
  fn replace_token(&mut self, token: &SyntaxToken, new_text: &str) -> Result<(), YerbaError> {
1541
1767
  let range = token.text_range();
1542
1768
 
@@ -1551,22 +1777,51 @@ impl Document {
1551
1777
  }
1552
1778
 
1553
1779
  fn remove_node(&mut self, node: &SyntaxNode) -> Result<(), YerbaError> {
1780
+ let inline_comment = self.find_inline_comment(node);
1554
1781
  let range = removal_range(node);
1555
1782
 
1556
- self.apply_edit(range, "")
1783
+ if let Some((comment_text, comment_end)) = inline_comment {
1784
+ let indent = preceding_whitespace_indent(node);
1785
+ let replacement = format!("\n{}{}", indent, comment_text);
1786
+ let expanded_range = TextRange::new(range.start(), comment_end);
1787
+
1788
+ self.apply_edit(expanded_range, &replacement)
1789
+ } else {
1790
+ self.apply_edit(range, "")
1791
+ }
1557
1792
  }
1558
1793
 
1559
- fn reorder_entries<T>(
1560
- &mut self,
1561
- entries: &[T],
1562
- from: usize,
1563
- to: usize,
1564
- get_text: impl Fn(&T) -> String,
1565
- get_indent: impl Fn(&T) -> String,
1566
- range: TextRange,
1567
- ) -> Result<(), YerbaError>
1794
+ fn find_inline_comment(&self, node: &SyntaxNode) -> Option<(String, rowan::TextSize)> {
1795
+ let mut sibling = node.next_sibling_or_token();
1796
+
1797
+ while let Some(ref element) = sibling {
1798
+ match element {
1799
+ rowan::NodeOrToken::Token(token) => {
1800
+ if token.kind() == SyntaxKind::COMMENT {
1801
+ return Some((token.text().to_string(), token.text_range().end()));
1802
+ } else if token.kind() == SyntaxKind::WHITESPACE {
1803
+ if token.text().contains('\n') {
1804
+ return None;
1805
+ }
1806
+ } else {
1807
+ return None;
1808
+ }
1809
+ }
1810
+ _ => return None,
1811
+ }
1812
+
1813
+ sibling = match element {
1814
+ rowan::NodeOrToken::Token(token) => token.next_sibling_or_token(),
1815
+ rowan::NodeOrToken::Node(node) => node.next_sibling_or_token(),
1816
+ };
1817
+ }
1818
+
1819
+ None
1820
+ }
1821
+
1822
+ fn reorder_entries<T>(&mut self, parent: &SyntaxNode, entries: &[T], from: usize, to: usize) -> Result<(), YerbaError>
1568
1823
  where
1569
- T: rowan::ast::AstNode,
1824
+ T: rowan::ast::AstNode<Language = yaml_parser::YamlLanguage>,
1570
1825
  {
1571
1826
  let length = entries.len();
1572
1827
 
@@ -1578,15 +1833,18 @@ impl Document {
1578
1833
  return Err(YerbaError::IndexOutOfBounds(to, length));
1579
1834
  }
1580
1835
 
1581
- let entry_texts: Vec<String> = entries.iter().map(&get_text).collect();
1836
+ let (groups, range) = collect_groups_with_range(parent);
1582
1837
 
1583
- let mut reordered = entry_texts.clone();
1838
+ let mut reordered = groups.clone();
1584
1839
  let item = reordered.remove(from);
1585
-
1586
1840
  reordered.insert(to, item);
1587
1841
 
1588
- let indent = entries.get(1).map(&get_indent).unwrap_or_default();
1589
- let text = rebuild_entries(reordered.iter().map(|text| text.as_str()), &indent);
1842
+ let indent = entries
1843
+ .get(1)
1844
+ .map(|entry| preceding_whitespace_indent(entry.syntax()))
1845
+ .unwrap_or_default();
1846
+
1847
+ let text = rebuild_from_groups(&reordered, &indent);
1590
1848
 
1591
1849
  self.apply_edit(range, &text)
1592
1850
  }
@@ -1613,6 +1871,96 @@ impl std::fmt::Display for Document {
1613
1871
  }
1614
1872
  }
1615
1873
 
1874
+ pub fn node_to_yaml_value(node: &SyntaxNode) -> serde_yaml::Value {
1875
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1876
+ let map_position = node
1877
+ .descendants()
1878
+ .find_map(BlockMap::cast)
1879
+ .map(|map| map.syntax().text_range().start());
1880
+
1881
+ let sequence_position = sequence.syntax().text_range().start();
1882
+
1883
+ if map_position.is_none() || sequence_position <= map_position.unwrap() {
1884
+ let values: Vec<serde_yaml::Value> = sequence
1885
+ .entries()
1886
+ .map(|entry| node_to_yaml_value(entry.syntax()))
1887
+ .collect();
1888
+
1889
+ return serde_yaml::Value::Sequence(values);
1890
+ }
1891
+ }
1892
+
1893
+ if let Some(map) = node.descendants().find_map(BlockMap::cast) {
1894
+ let mut mapping = serde_yaml::Mapping::new();
1895
+
1896
+ for entry in map.entries() {
1897
+ let key = entry
1898
+ .key()
1899
+ .and_then(|key_node| extract_scalar_text(key_node.syntax()))
1900
+ .unwrap_or_default();
1901
+
1902
+ let value = entry
1903
+ .value()
1904
+ .map(|value_node| node_to_yaml_value(value_node.syntax()))
1905
+ .unwrap_or(serde_yaml::Value::Null);
1906
+
1907
+ mapping.insert(serde_yaml::Value::String(key), value);
1908
+ }
1909
+
1910
+ return serde_yaml::Value::Mapping(mapping);
1911
+ }
1912
+
1913
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1914
+ let values: Vec<serde_yaml::Value> = sequence
1915
+ .entries()
1916
+ .map(|entry| node_to_yaml_value(entry.syntax()))
1917
+ .collect();
1918
+
1919
+ return serde_yaml::Value::Sequence(values);
1920
+ }
1921
+
1922
+ if let Some(block_scalar) = node
1923
+ .descendants()
1924
+ .find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR)
1925
+ {
1926
+ let text = block_scalar
1927
+ .descendants_with_tokens()
1928
+ .filter_map(|element| element.into_token())
1929
+ .find(|token| token.kind() == SyntaxKind::BLOCK_SCALAR_TEXT)
1930
+ .map(|token| token.text().to_string())
1931
+ .unwrap_or_default();
1932
+
1933
+ return serde_yaml::Value::String(text);
1934
+ }
1935
+
1936
+ if let Some(scalar) = extract_scalar(node) {
1937
+ use crate::syntax::{detect_yaml_type, is_yaml_truthy, YerbaValueType};
1938
+
1939
+ return match detect_yaml_type(&scalar) {
1940
+ YerbaValueType::Null => serde_yaml::Value::Null,
1941
+ YerbaValueType::Boolean => serde_yaml::Value::Bool(is_yaml_truthy(&scalar.text)),
1942
+
1943
+ YerbaValueType::Integer => scalar
1944
+ .text
1945
+ .parse::<i64>()
1946
+ .map(|n| serde_yaml::Value::Number(serde_yaml::Number::from(n)))
1947
+ .unwrap_or(serde_yaml::Value::String(scalar.text)),
1948
+
1949
+ YerbaValueType::Float => scalar
1950
+ .text
1951
+ .parse::<f64>()
1952
+ .map(|n| serde_yaml::Value::Number(serde_yaml::Number::from(n)))
1953
+ .unwrap_or(serde_yaml::Value::String(scalar.text)),
1954
+
1955
+ YerbaValueType::String => serde_yaml::Value::String(scalar.text),
1956
+ };
1957
+ }
1958
+
1959
+ let text = node.text().to_string();
1960
+
1961
+ serde_yaml::from_str(&text).unwrap_or(serde_yaml::Value::String(text))
1962
+ }
1963
+
1616
1964
  fn parse_condition(condition: &str) -> Option<(String, &str, String)> {
1617
1965
  let (left, operator, right) = if let Some(index) = condition.find(" not_contains ") {
1618
1966
  (
@@ -1639,140 +1987,250 @@ fn parse_condition(condition: &str) -> Option<(String, &str, String)> {
1639
1987
  Some((left.to_string(), operator, right.to_string()))
1640
1988
  }
1641
1989
 
1642
- fn parse_path_segments(path: &str) -> Vec<&str> {
1643
- if path.is_empty() {
1644
- return Vec::new();
1645
- }
1990
+ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment) -> Vec<SyntaxNode> {
1991
+ use crate::selector::SelectorSegment;
1646
1992
 
1647
- let mut segments = Vec::new();
1648
- let mut rest = path;
1993
+ match segment {
1994
+ SelectorSegment::AllItems => {
1995
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1996
+ sequence.entries().map(|entry| entry.syntax().clone()).collect()
1997
+ } else {
1998
+ Vec::new()
1999
+ }
2000
+ }
1649
2001
 
1650
- while !rest.is_empty() {
1651
- if rest.starts_with('[') {
1652
- if let Some(close) = rest.find(']') {
1653
- segments.push(&rest[..close + 1]);
1654
- rest = &rest[close + 1..];
2002
+ SelectorSegment::Index(index) => {
2003
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
2004
+ sequence
2005
+ .entries()
2006
+ .nth(*index)
2007
+ .map(|entry| vec![entry.syntax().clone()])
2008
+ .unwrap_or_default()
2009
+ } else {
2010
+ Vec::new()
2011
+ }
2012
+ }
1655
2013
 
1656
- if rest.starts_with('.') {
1657
- rest = &rest[1..];
2014
+ SelectorSegment::Key(key) => {
2015
+ if let Some(map) = node.descendants().find_map(BlockMap::cast) {
2016
+ if let Some(entry) = find_entry_by_key(&map, key) {
2017
+ if let Some(value) = entry.value() {
2018
+ return vec![value.syntax().clone()];
2019
+ }
1658
2020
  }
1659
- } else {
1660
- segments.push(rest);
1661
- break;
1662
2021
  }
1663
- } else {
1664
- let dot_index = rest.find('.');
1665
- let bracket_index = rest.find('[');
1666
-
1667
- let split_at = match (dot_index, bracket_index) {
1668
- (Some(dot), Some(bracket)) => Some(dot.min(bracket)),
1669
- (Some(dot), None) => Some(dot),
1670
- (None, Some(bracket)) => Some(bracket),
1671
- (None, None) => None,
1672
- };
1673
2022
 
1674
- match split_at {
1675
- Some(index) => {
1676
- let segment = &rest[..index];
2023
+ Vec::new()
2024
+ }
2025
+ }
2026
+ }
1677
2027
 
1678
- if !segment.is_empty() {
1679
- segments.push(segment);
1680
- }
2028
+ fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
2029
+ let parsed = crate::selector::Selector::parse(path);
2030
+ let mut current_nodes = vec![node.clone()];
1681
2031
 
1682
- rest = &rest[index..];
2032
+ for segment in parsed.segments() {
2033
+ let mut next_nodes = Vec::new();
1683
2034
 
1684
- if rest.starts_with('.') {
1685
- rest = &rest[1..];
1686
- }
1687
- }
2035
+ for current in &current_nodes {
2036
+ next_nodes.extend(resolve_segment(current, segment));
2037
+ }
1688
2038
 
1689
- None => {
1690
- segments.push(rest);
1691
- break;
1692
- }
1693
- }
2039
+ current_nodes = next_nodes;
2040
+
2041
+ if current_nodes.is_empty() {
2042
+ break;
1694
2043
  }
1695
2044
  }
1696
2045
 
1697
- segments
2046
+ current_nodes
1698
2047
  }
1699
2048
 
1700
- fn parse_bracket_index(segment: &str) -> Option<usize> {
1701
- if segment == "[]" {
1702
- return None;
1703
- }
2049
+ #[derive(Debug, Clone)]
2050
+ struct EntryGroup {
2051
+ separator: String,
2052
+ preceding: String,
2053
+ body: String,
2054
+ }
1704
2055
 
1705
- segment
1706
- .strip_prefix('[')
1707
- .and_then(|rest| rest.strip_suffix(']'))
1708
- .and_then(|inner| inner.parse::<usize>().ok())
2056
+ impl EntryGroup {
2057
+ fn full_text(&self) -> String {
2058
+ if self.preceding.is_empty() {
2059
+ self.body.clone()
2060
+ } else {
2061
+ format!("{}\n{}", self.preceding, self.body)
2062
+ }
2063
+ }
1709
2064
  }
1710
2065
 
1711
- fn resolve_segment(node: &SyntaxNode, segment: &str) -> Vec<SyntaxNode> {
1712
- if segment.starts_with('[') {
1713
- if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
1714
- match parse_bracket_index(segment) {
1715
- None => sequence.entries().map(|entry| entry.syntax().clone()).collect(),
2066
+ fn collect_entry_groups(parent: &SyntaxNode) -> Vec<EntryGroup> {
2067
+ let mut groups: Vec<EntryGroup> = Vec::new();
2068
+ let mut buffer = String::new();
1716
2069
 
1717
- Some(index) => sequence
1718
- .entries()
1719
- .nth(index)
1720
- .map(|entry| vec![entry.syntax().clone()])
1721
- .unwrap_or_default(),
2070
+ for child in parent.children_with_tokens() {
2071
+ let is_entry = child.as_node().is_some()
2072
+ && matches!(
2073
+ child.as_node().unwrap().kind(),
2074
+ SyntaxKind::BLOCK_MAP_ENTRY | SyntaxKind::BLOCK_SEQ_ENTRY
2075
+ );
2076
+
2077
+ if is_entry {
2078
+ let entry_text = child.as_node().unwrap().text().to_string();
2079
+
2080
+ if groups.is_empty() {
2081
+ let preceding = buffer.trim_start_matches('\n').to_string();
2082
+
2083
+ groups.push(EntryGroup {
2084
+ separator: String::new(),
2085
+ preceding,
2086
+ body: entry_text,
2087
+ });
2088
+ } else {
2089
+ let (trailing, separator, preceding) = split_at_blank_line(&buffer);
2090
+
2091
+ if let Some(last) = groups.last_mut() {
2092
+ last.body.push_str(&trailing);
2093
+ }
2094
+
2095
+ groups.push(EntryGroup {
2096
+ separator,
2097
+ preceding,
2098
+ body: entry_text,
2099
+ });
1722
2100
  }
2101
+
2102
+ buffer.clear();
1723
2103
  } else {
1724
- Vec::new()
2104
+ let text = match &child {
2105
+ rowan::NodeOrToken::Node(node) => node.text().to_string(),
2106
+ rowan::NodeOrToken::Token(token) => token.text().to_string(),
2107
+ };
2108
+
2109
+ buffer.push_str(&text);
1725
2110
  }
1726
- } else {
1727
- if let Some(map) = node.descendants().find_map(BlockMap::cast) {
1728
- if let Some(entry) = find_entry_by_key(&map, segment) {
1729
- if let Some(value) = entry.value() {
1730
- return vec![value.syntax().clone()];
1731
- }
1732
- }
2111
+ }
2112
+
2113
+ if let Some(last) = groups.last_mut() {
2114
+ let trimmed = buffer.trim_end_matches(['\n', ' ', '\t']);
2115
+
2116
+ if !trimmed.is_empty() {
2117
+ last.body.push_str(trimmed);
1733
2118
  }
2119
+ }
1734
2120
 
1735
- Vec::new()
2121
+ for group in &mut groups {
2122
+ let trimmed = group.body.trim_end_matches(['\n', ' ', '\t']);
2123
+
2124
+ group.body = trimmed.to_string();
1736
2125
  }
2126
+
2127
+ groups
1737
2128
  }
1738
2129
 
1739
- fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
1740
- let segments = parse_path_segments(path);
1741
- let mut current_nodes = vec![node.clone()];
2130
+ fn split_at_blank_line(text: &str) -> (String, String, String) {
2131
+ if let Some(position) = text.find("\n\n") {
2132
+ let trailing = text[..position].to_string();
2133
+ let rest = &text[position..];
2134
+ let content_start = rest.len() - rest.trim_start_matches('\n').len();
2135
+ let separator = rest[..content_start].to_string();
2136
+ let preceding = rest[content_start..].trim_end_matches(['\n', ' ', '\t']).to_string();
1742
2137
 
1743
- for segment in &segments {
1744
- let mut next_nodes = Vec::new();
2138
+ (trailing, separator, preceding)
2139
+ } else {
2140
+ (text.to_string(), String::new(), String::new())
2141
+ }
2142
+ }
1745
2143
 
1746
- for current in &current_nodes {
1747
- next_nodes.extend(resolve_segment(current, segment));
1748
- }
2144
+ fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String, Option<rowan::TextSize>) {
2145
+ let mut comments: Vec<String> = Vec::new();
2146
+ let mut earliest_start = None;
2147
+ let mut node = parent.clone();
2148
+
2149
+ loop {
2150
+ let mut sibling = node.prev_sibling_or_token();
2151
+
2152
+ while let Some(ref element) = sibling {
2153
+ match element {
2154
+ rowan::NodeOrToken::Token(token) => {
2155
+ if token.kind() == SyntaxKind::COMMENT {
2156
+ comments.push(token.text().to_string());
2157
+ earliest_start = Some(token.text_range().start());
2158
+ } else if token.kind() == SyntaxKind::WHITESPACE {
2159
+ // Keep looking past whitespace
2160
+ } else {
2161
+ break;
2162
+ }
2163
+ }
2164
+ _ => break,
2165
+ }
1749
2166
 
1750
- current_nodes = next_nodes;
2167
+ sibling = match element {
2168
+ rowan::NodeOrToken::Token(token) => token.prev_sibling_or_token(),
2169
+ rowan::NodeOrToken::Node(node) => node.prev_sibling_or_token(),
2170
+ };
2171
+ }
1751
2172
 
1752
- if current_nodes.is_empty() {
2173
+ if !comments.is_empty() {
1753
2174
  break;
1754
2175
  }
2176
+
2177
+ match node.parent() {
2178
+ Some(parent)
2179
+ if parent.kind() == SyntaxKind::BLOCK
2180
+ || parent.kind() == SyntaxKind::DOCUMENT
2181
+ || parent.kind() == SyntaxKind::BLOCK_MAP_VALUE
2182
+ || parent.kind() == SyntaxKind::BLOCK_SEQ_ENTRY =>
2183
+ {
2184
+ node = parent
2185
+ }
2186
+ _ => break,
2187
+ }
1755
2188
  }
1756
2189
 
1757
- current_nodes
2190
+ comments.reverse();
2191
+ (comments.join("\n"), earliest_start)
1758
2192
  }
1759
2193
 
1760
- fn byte_offset_to_line(source: &str, offset: usize) -> usize {
1761
- source[..offset.min(source.len())]
1762
- .chars()
1763
- .filter(|character| *character == '\n')
1764
- .count()
1765
- + 1
2194
+ fn collect_groups_with_range(parent: &SyntaxNode) -> (Vec<EntryGroup>, TextRange) {
2195
+ let mut groups = collect_entry_groups(parent);
2196
+
2197
+ let (sibling_comments, earliest_start) = collect_preceding_sibling_comments(parent);
2198
+
2199
+ if !sibling_comments.is_empty() {
2200
+ if let Some(first) = groups.first_mut() {
2201
+ if first.preceding.is_empty() {
2202
+ first.preceding = sibling_comments;
2203
+ } else {
2204
+ first.preceding = format!("{}\n{}", sibling_comments, first.preceding);
2205
+ }
2206
+ }
2207
+ }
2208
+
2209
+ let range = match earliest_start {
2210
+ Some(start) => TextRange::new(start, parent.text_range().end()),
2211
+ None => parent.text_range(),
2212
+ };
2213
+
2214
+ (groups, range)
1766
2215
  }
1767
2216
 
1768
- fn rebuild_entries<'a>(entries: impl Iterator<Item = &'a str>, indent: &str) -> String {
1769
- entries
2217
+ fn rebuild_from_groups(groups: &[EntryGroup], indent: &str) -> String {
2218
+ let default_separator = groups
2219
+ .iter()
2220
+ .find(|group| !group.separator.is_empty())
2221
+ .map(|group| group.separator.clone())
2222
+ .unwrap_or_else(|| "\n".to_string());
2223
+
2224
+ groups
2225
+ .iter()
1770
2226
  .enumerate()
1771
- .map(|(index, text)| {
2227
+ .map(|(index, group)| {
1772
2228
  if index == 0 {
1773
- text.to_string()
2229
+ group.full_text()
2230
+ } else if group.preceding.is_empty() {
2231
+ format!("{}{}{}", default_separator, indent, group.body)
1774
2232
  } else {
1775
- format!("\n{}{}", indent, text)
2233
+ format!("{}{}\n{}{}", default_separator, group.preceding, indent, group.body)
1776
2234
  }
1777
2235
  })
1778
2236
  .collect()