yerba 0.9.2-arm-linux-gnu → 0.10.1-arm-linux-gnu

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -17
  3. data/exe/arm-linux-gnu/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +58 -35
  5. data/ext/yerba/yerba.c +281 -187
  6. data/lib/yerba/3.2/yerba.so +0 -0
  7. data/lib/yerba/3.3/yerba.so +0 -0
  8. data/lib/yerba/3.4/yerba.so +0 -0
  9. data/lib/yerba/4.0/yerba.so +0 -0
  10. data/lib/yerba/collection.rb +4 -4
  11. data/lib/yerba/document.rb +134 -0
  12. data/lib/yerba/formatting.rb +1 -0
  13. data/lib/yerba/location.rb +8 -1
  14. data/lib/yerba/map.rb +16 -6
  15. data/lib/yerba/node.rb +41 -0
  16. data/lib/yerba/query_result.rb +33 -10
  17. data/lib/yerba/scalar.rb +29 -0
  18. data/lib/yerba/selector.rb +3 -1
  19. data/lib/yerba/sequence.rb +38 -33
  20. data/lib/yerba/version.rb +1 -1
  21. data/rust/Cargo.lock +1 -1
  22. data/rust/Cargo.toml +1 -1
  23. data/rust/src/commands/get.rs +12 -12
  24. data/rust/src/commands/init.rs +8 -8
  25. data/rust/src/commands/insert.rs +4 -18
  26. data/rust/src/commands/location.rs +2 -0
  27. data/rust/src/commands/mod.rs +34 -6
  28. data/rust/src/commands/move_item.rs +1 -1
  29. data/rust/src/commands/move_key.rs +8 -8
  30. data/rust/src/commands/rename.rs +1 -1
  31. data/rust/src/commands/selectors.rs +1 -1
  32. data/rust/src/commands/set.rs +14 -6
  33. data/rust/src/commands/sort.rs +5 -1
  34. data/rust/src/document/condition.rs +25 -25
  35. data/rust/src/document/delete.rs +57 -43
  36. data/rust/src/document/get.rs +91 -82
  37. data/rust/src/document/insert.rs +77 -77
  38. data/rust/src/document/mod.rs +202 -76
  39. data/rust/src/document/nodes.rs +162 -0
  40. data/rust/src/document/schema.rs +8 -8
  41. data/rust/src/document/set.rs +49 -26
  42. data/rust/src/document/sort.rs +105 -60
  43. data/rust/src/document/style.rs +50 -49
  44. data/rust/src/document/unique.rs +5 -5
  45. data/rust/src/error.rs +8 -4
  46. data/rust/src/ffi.rs +234 -107
  47. data/rust/src/json.rs +62 -2
  48. data/rust/src/lib.rs +4 -2
  49. data/rust/src/main.rs +2 -1
  50. data/rust/src/selector.rs +48 -7
  51. data/rust/src/syntax.rs +449 -56
  52. data/rust/src/validation.rs +5 -3
  53. data/rust/src/yerbafile.rs +168 -94
  54. metadata +3 -2
@@ -2,6 +2,7 @@ mod condition;
2
2
  mod delete;
3
3
  mod get;
4
4
  mod insert;
5
+ mod nodes;
5
6
  mod set;
6
7
  mod sort;
7
8
  mod unique;
@@ -32,6 +33,7 @@ pub struct LocatedNode {
32
33
  use std::fs;
33
34
  use std::path::{Path, PathBuf};
34
35
  use std::sync::atomic::{AtomicU64, Ordering};
36
+ use std::sync::OnceLock;
35
37
 
36
38
  use rowan::ast::AstNode;
37
39
  use rowan::{TextRange, TextSize};
@@ -46,34 +48,34 @@ use crate::syntax::{
46
48
  column_at, dedent_block_scalar, extract_scalar, extract_scalar_text, find_block_map, find_block_sequence, find_entry_by_key, find_flow_entry_by_key,
47
49
  find_flow_map, find_flow_sequence, find_scalar_token, first_collection, flow_map_entries, flow_sequence_entries, format_scalar_value, in_flow_collection,
48
50
  is_map_key, is_yaml_non_string, line_at, line_start_at, preceding_whitespace_indent, preceding_whitespace_token, raw_scalar_value, removal_range,
49
- FirstCollection, ScalarValue,
51
+ FirstCollection, LineIndex, ScalarValue,
50
52
  };
51
53
 
52
54
  #[derive(Debug, Clone)]
53
55
  pub struct SortField {
54
- pub path: String,
56
+ pub selector: String,
55
57
  pub ascending: bool,
56
58
  }
57
59
 
58
60
  impl SortField {
59
- pub fn asc(path: &str) -> Self {
61
+ pub fn asc(selector: &str) -> Self {
60
62
  SortField {
61
- path: path.to_string(),
63
+ selector: selector.to_string(),
62
64
  ascending: true,
63
65
  }
64
66
  }
65
67
 
66
- pub fn desc(path: &str) -> Self {
68
+ pub fn desc(selector: &str) -> Self {
67
69
  SortField {
68
- path: path.to_string(),
70
+ selector: selector.to_string(),
69
71
  ascending: false,
70
72
  }
71
73
  }
72
74
 
73
75
  pub fn parse(input: &str) -> Self {
74
- if let Some((path, direction)) = input.rsplit_once(':') {
76
+ if let Some((selector, direction)) = input.rsplit_once(':') {
75
77
  match direction {
76
- "desc" | "descending" => SortField::desc(path),
78
+ "desc" | "descending" => SortField::desc(selector),
77
79
  _ => SortField::asc(input),
78
80
  }
79
81
  } else {
@@ -82,7 +84,58 @@ impl SortField {
82
84
  }
83
85
 
84
86
  pub fn parse_list(input: &str) -> Vec<Self> {
85
- input.split(',').map(|field| SortField::parse(field.trim())).collect()
87
+ let mut fields = Vec::new();
88
+ let mut current = String::new();
89
+ let mut depth = 0usize;
90
+ let mut quote: Option<char> = None;
91
+
92
+ for character in input.chars() {
93
+ match (quote, character) {
94
+ (Some(open), _) if character == open => quote = None,
95
+ (Some(_), _) => {}
96
+ (None, '"' | '\'') => quote = Some(character),
97
+ (None, '[') => depth += 1,
98
+ (None, ']') => depth = depth.saturating_sub(1),
99
+ (None, ',') if depth == 0 => {
100
+ fields.push(std::mem::take(&mut current));
101
+ continue;
102
+ }
103
+ (None, _) if character.is_whitespace() && depth == 0 => {
104
+ fields.push(std::mem::take(&mut current));
105
+ continue;
106
+ }
107
+ _ => {}
108
+ }
109
+
110
+ current.push(character);
111
+ }
112
+
113
+ fields.push(current);
114
+
115
+ fields.iter().filter(|field| !field.is_empty()).map(|field| SortField::parse(field)).collect()
116
+ }
117
+ }
118
+
119
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
120
+ #[serde(rename_all = "lowercase")]
121
+ pub enum SortMissing {
122
+ First,
123
+ Last,
124
+ Keep,
125
+ Skip,
126
+ }
127
+
128
+ impl std::str::FromStr for SortMissing {
129
+ type Err = String;
130
+
131
+ fn from_str(input: &str) -> Result<Self, Self::Err> {
132
+ match input {
133
+ "first" => Ok(SortMissing::First),
134
+ "last" => Ok(SortMissing::Last),
135
+ "keep" => Ok(SortMissing::Keep),
136
+ "skip" => Ok(SortMissing::Skip),
137
+ _ => Err(format!("unknown missing: {} (expected first, last, keep or skip)", input)),
138
+ }
86
139
  }
87
140
  }
88
141
 
@@ -103,6 +156,8 @@ pub struct Location {
103
156
  pub end_column: usize,
104
157
  pub start_offset: usize,
105
158
  pub end_offset: usize,
159
+ pub start_character_column: usize,
160
+ pub end_character_column: usize,
106
161
  }
107
162
 
108
163
  #[derive(Debug)]
@@ -127,6 +182,28 @@ pub enum InsertPosition {
127
182
  FromSortOrder(Vec<String>),
128
183
  }
129
184
 
185
+ impl InsertPosition {
186
+ pub fn before(target: impl Into<String>) -> Self {
187
+ let target = target.into();
188
+
189
+ if target.starts_with('.') {
190
+ InsertPosition::BeforeCondition(target)
191
+ } else {
192
+ InsertPosition::Before(target)
193
+ }
194
+ }
195
+
196
+ pub fn after(target: impl Into<String>) -> Self {
197
+ let target = target.into();
198
+
199
+ if target.starts_with('.') {
200
+ InsertPosition::AfterCondition(target)
201
+ } else {
202
+ InsertPosition::After(target)
203
+ }
204
+ }
205
+ }
206
+
130
207
  static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
131
208
 
132
209
  #[derive(Debug)]
@@ -134,6 +211,7 @@ pub struct Document {
134
211
  root: SyntaxNode,
135
212
  path: Option<PathBuf>,
136
213
  generation: u64,
214
+ line_index: OnceLock<LineIndex>,
137
215
  }
138
216
 
139
217
  impl Document {
@@ -146,6 +224,7 @@ impl Document {
146
224
  root: tree,
147
225
  path: None,
148
226
  generation: NEXT_GENERATION.fetch_add(1, Ordering::Relaxed),
227
+ line_index: OnceLock::new(),
149
228
  })
150
229
  }
151
230
 
@@ -181,49 +260,49 @@ impl Document {
181
260
  Ok(())
182
261
  }
183
262
 
184
- pub fn source(&self, dot_path: &str) -> Result<String, YerbaError> {
185
- let node = self.navigate(dot_path)?;
263
+ pub fn source(&self, selector: &str) -> Result<String, YerbaError> {
264
+ let node = self.navigate(selector)?;
186
265
  Ok(node.text().to_string())
187
266
  }
188
267
 
189
- pub fn navigate(&self, dot_path: &str) -> Result<SyntaxNode, YerbaError> {
190
- Self::validate_path(dot_path)?;
268
+ pub fn navigate(&self, selector: &str) -> Result<SyntaxNode, YerbaError> {
269
+ Self::validate_selector(selector)?;
191
270
 
192
- if dot_path.is_empty() {
193
- let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
271
+ if selector.is_empty() {
272
+ let root = Root::cast(self.root.clone()).ok_or_else(|| YerbaError::SelectorNotFound(selector.to_string()))?;
194
273
 
195
- let document = root.documents().next().ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
274
+ let document = root.documents().next().ok_or_else(|| YerbaError::SelectorNotFound(selector.to_string()))?;
196
275
 
197
276
  return Ok(document.syntax().clone());
198
277
  }
199
278
 
200
- let nodes = self.navigate_all_compact(dot_path);
279
+ let nodes = self.navigate_all_compact(selector);
201
280
 
202
281
  match nodes.len() {
203
- 0 => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
282
+ 0 => Err(YerbaError::SelectorNotFound(selector.to_string())),
204
283
  1 => Ok(nodes.into_iter().next().unwrap()),
205
- _ => Err(YerbaError::AmbiguousSelector(dot_path.to_string(), nodes.len())),
284
+ _ => Err(YerbaError::AmbiguousSelector(selector.to_string(), nodes.len())),
206
285
  }
207
286
  }
208
287
 
209
- pub fn navigate_all_compact(&self, dot_path: &str) -> Vec<SyntaxNode> {
210
- self.navigate_all(dot_path).into_iter().flatten().collect()
288
+ pub fn navigate_all_compact(&self, selector: &str) -> Vec<SyntaxNode> {
289
+ self.navigate_all(selector).into_iter().flatten().collect()
211
290
  }
212
291
 
213
- pub(crate) fn refuse_flow_target(&self, dot_path: &str) -> Result<(), YerbaError> {
214
- if self.navigate_all_compact(dot_path).iter().any(in_flow_collection) {
215
- return Err(YerbaError::FlowCollectionNotWritable(dot_path.to_string()));
292
+ pub(crate) fn refuse_flow_target(&self, selector: &str) -> Result<(), YerbaError> {
293
+ if self.navigate_all_compact(selector).iter().any(in_flow_collection) {
294
+ return Err(YerbaError::FlowCollectionNotWritable(selector.to_string()));
216
295
  }
217
296
 
218
297
  Ok(())
219
298
  }
220
299
 
221
- pub fn navigate_all(&self, dot_path: &str) -> Vec<Option<SyntaxNode>> {
222
- if Document::validate_path(dot_path).is_err() {
300
+ pub fn navigate_all(&self, selector: &str) -> Vec<Option<SyntaxNode>> {
301
+ if Document::validate_selector(selector).is_err() {
223
302
  return Vec::new();
224
303
  }
225
304
 
226
- let parsed = crate::selector::Selector::parse(dot_path);
305
+ let parsed = crate::selector::Selector::parse(selector);
227
306
 
228
307
  let root = match Root::cast(self.root.clone()) {
229
308
  Some(root) => root,
@@ -299,21 +378,21 @@ impl Document {
299
378
  current_nodes
300
379
  }
301
380
 
302
- pub fn validate_path(dot_path: &str) -> Result<(), YerbaError> {
303
- if dot_path.ends_with('.') {
304
- return Err(YerbaError::ParseError(format!("invalid path: trailing dot in '{}'", dot_path)));
381
+ pub fn validate_selector(selector: &str) -> Result<(), YerbaError> {
382
+ if selector.ends_with('.') {
383
+ return Err(YerbaError::ParseError(format!("invalid selector: trailing dot in '{}'", selector)));
305
384
  }
306
385
 
307
- if dot_path.contains("..") {
308
- return Err(YerbaError::ParseError(format!("invalid path: double dot in '{}'", dot_path)));
386
+ if selector.contains("..") {
387
+ return Err(YerbaError::ParseError(format!("invalid selector: double dot in '{}'", selector)));
309
388
  }
310
389
 
311
- if dot_path.starts_with('.') {
312
- return Err(YerbaError::ParseError(format!("invalid path: leading dot in '{}'", dot_path)));
390
+ if selector.starts_with('.') {
391
+ return Err(YerbaError::ParseError(format!("invalid selector: leading dot in '{}'", selector)));
313
392
  }
314
393
 
315
- if dot_path.contains('[') && !dot_path.contains(']') {
316
- return Err(YerbaError::ParseError(format!("invalid path: unclosed bracket in '{}'", dot_path)));
394
+ if selector.contains('[') && !selector.contains(']') {
395
+ return Err(YerbaError::ParseError(format!("invalid selector: unclosed bracket in '{}'", selector)));
317
396
  }
318
397
 
319
398
  Ok(())
@@ -487,6 +566,10 @@ impl Document {
487
566
  self.root.text().to_string()
488
567
  }
489
568
 
569
+ fn line_index(&self) -> &LineIndex {
570
+ self.line_index.get_or_init(|| LineIndex::new(&self.source_text()))
571
+ }
572
+
490
573
  fn reparse(&mut self, new_source: &str) -> Result<(), YerbaError> {
491
574
  let document = Self::parse(new_source)?;
492
575
  let path = self.path.take();
@@ -538,17 +621,19 @@ fn check_duplicate_keys(root: &SyntaxNode) -> Result<(), YerbaError> {
538
621
  Ok(())
539
622
  }
540
623
 
541
- pub(crate) fn compute_location(source: &str, start_offset: usize, end_offset: usize) -> Location {
542
- let start = start_offset.min(source.len());
543
- let end = end_offset.min(source.len());
624
+ pub(crate) fn compute_location(index: &LineIndex, start_offset: usize, end_offset: usize) -> Location {
625
+ let start = start_offset.min(index.source_length());
626
+ let end = end_offset.min(index.source_length());
544
627
 
545
628
  Location {
546
629
  start_offset: start,
547
630
  end_offset: end,
548
- start_line: line_at(source, start),
549
- start_column: column_at(source, start),
550
- end_line: line_at(source, end),
551
- end_column: column_at(source, end),
631
+ start_line: index.line_at(start),
632
+ start_column: index.column_at(start),
633
+ end_line: index.line_at(end),
634
+ end_column: index.column_at(end),
635
+ start_character_column: index.character_column_at(start),
636
+ end_character_column: index.character_column_at(end),
552
637
  }
553
638
  }
554
639
 
@@ -635,25 +720,17 @@ pub(crate) fn node_to_yaml_value(node: &SyntaxNode) -> yaml_serde::Value {
635
720
  }
636
721
 
637
722
  if let Some(scalar) = extract_scalar(node) {
638
- use crate::syntax::{detect_yaml_type, is_yaml_truthy, YerbaValueType};
639
-
640
- return match detect_yaml_type(&scalar) {
641
- YerbaValueType::Null => yaml_serde::Value::Null,
642
- YerbaValueType::Boolean => yaml_serde::Value::Bool(is_yaml_truthy(&scalar.text)),
643
-
644
- YerbaValueType::Integer => scalar
645
- .text
646
- .parse::<i64>()
647
- .map(|n| yaml_serde::Value::Number(yaml_serde::Number::from(n)))
648
- .unwrap_or(yaml_serde::Value::String(scalar.text)),
649
-
650
- YerbaValueType::Float => scalar
651
- .text
652
- .parse::<f64>()
653
- .map(|n| yaml_serde::Value::Number(yaml_serde::Number::from(n)))
654
- .unwrap_or(yaml_serde::Value::String(scalar.text)),
655
-
656
- YerbaValueType::String => yaml_serde::Value::String(scalar.text),
723
+ use crate::syntax::{resolve_scalar, PlainScalar};
724
+
725
+ return match resolve_scalar(&scalar) {
726
+ PlainScalar::Null => yaml_serde::Value::Null,
727
+ PlainScalar::Boolean(boolean) => yaml_serde::Value::Bool(boolean),
728
+ PlainScalar::Integer(integer) => yaml_serde::Value::Number(match i64::try_from(integer) {
729
+ Ok(integer) => yaml_serde::Number::from(integer),
730
+ Err(_) => yaml_serde::Number::from(integer as u64),
731
+ }),
732
+ PlainScalar::Float(float) => yaml_serde::Value::Number(yaml_serde::Number::from(float)),
733
+ PlainScalar::String => yaml_serde::Value::String(scalar.text),
657
734
  };
658
735
  }
659
736
 
@@ -728,6 +805,26 @@ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment
728
805
  }
729
806
  }
730
807
 
808
+ SelectorSegment::IndexFromEnd(_) => {
809
+ if let Some(sequence) = find_block_sequence(node) {
810
+ segment
811
+ .sequence_index(sequence.entries().count())
812
+ .and_then(|index| sequence.entries().nth(index))
813
+ .map(|entry| vec![entry.syntax().clone()])
814
+ .unwrap_or_default()
815
+ } else if let Some(sequence) = find_flow_sequence(node) {
816
+ let entries = flow_sequence_entries(&sequence);
817
+
818
+ segment
819
+ .sequence_index(entries.len())
820
+ .and_then(|index| entries.into_iter().nth(index))
821
+ .map(|entry| vec![entry])
822
+ .unwrap_or_default()
823
+ } else {
824
+ Vec::new()
825
+ }
826
+ }
827
+
731
828
  SelectorSegment::AllKeys => {
732
829
  if let Some(map) = find_block_map(node) {
733
830
  map.entries().filter_map(|entry| entry.value().map(|value| value.syntax().clone())).collect()
@@ -765,8 +862,8 @@ fn resolve_segment(node: &SyntaxNode, segment: &crate::selector::SelectorSegment
765
862
  }
766
863
  }
767
864
 
768
- pub(crate) fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNode> {
769
- let parsed = crate::selector::Selector::parse(path);
865
+ pub(crate) fn navigate_from_node(node: &SyntaxNode, selector: &str) -> Vec<SyntaxNode> {
866
+ let parsed = crate::selector::Selector::parse(selector);
770
867
 
771
868
  navigate_remaining(node, parsed.segments())
772
869
  }
@@ -775,6 +872,7 @@ pub(crate) fn navigate_from_node(node: &SyntaxNode, path: &str) -> Vec<SyntaxNod
775
872
  pub(crate) struct EntryGroup {
776
873
  pub(crate) separator: String,
777
874
  pub(crate) preceding: String,
875
+ pub(crate) gap: String,
778
876
  pub(crate) body: String,
779
877
  }
780
878
 
@@ -783,7 +881,7 @@ impl EntryGroup {
783
881
  if self.preceding.is_empty() {
784
882
  self.body.clone()
785
883
  } else {
786
- format!("{}\n{}", self.preceding, self.body)
884
+ format!("{}{}{}", self.preceding, self.gap, self.body)
787
885
  }
788
886
  }
789
887
  }
@@ -818,15 +916,17 @@ fn collect_entry_groups(parent: &SyntaxNode) -> Vec<EntryGroup> {
818
916
  let entry_text = child.as_node().unwrap().text().to_string();
819
917
 
820
918
  if groups.is_empty() {
821
- let preceding = buffer.trim_start_matches('\n').to_string();
919
+ let (preceding, gap) = split_gap(buffer.trim_start_matches('\n'));
822
920
 
823
921
  groups.push(EntryGroup {
824
922
  separator: String::new(),
825
923
  preceding,
924
+ gap,
826
925
  body: entry_text,
827
926
  });
828
927
  } else {
829
928
  let (trailing, separator, preceding) = split_at_blank_line(&buffer);
929
+ let (preceding, gap) = split_gap(&preceding);
830
930
 
831
931
  if let Some(last) = groups.last_mut() {
832
932
  last.body.push_str(&trailing);
@@ -835,6 +935,7 @@ fn collect_entry_groups(parent: &SyntaxNode) -> Vec<EntryGroup> {
835
935
  groups.push(EntryGroup {
836
936
  separator,
837
937
  preceding,
938
+ gap,
838
939
  body: entry_text,
839
940
  });
840
941
  }
@@ -873,7 +974,7 @@ fn split_at_blank_line(text: &str) -> (String, String, String) {
873
974
  let rest = &text[position..];
874
975
  let content_start = rest.len() - rest.trim_start_matches('\n').len();
875
976
  let separator = rest[..content_start].to_string();
876
- let preceding = rest[content_start..].trim_end_matches(['\n', ' ', '\t']).to_string();
977
+ let preceding = rest[content_start..].to_string();
877
978
 
878
979
  (trailing, separator, preceding)
879
980
  } else {
@@ -881,8 +982,16 @@ fn split_at_blank_line(text: &str) -> (String, String, String) {
881
982
  }
882
983
  }
883
984
 
884
- pub(crate) fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String, Option<rowan::TextSize>) {
885
- let mut comments: Vec<String> = Vec::new();
985
+ fn split_gap(text: &str) -> (String, String) {
986
+ let content = text.trim_end_matches(['\n', ' ', '\t']);
987
+ let newlines = text[content.len()..].matches('\n').count().max(1);
988
+
989
+ (content.to_string(), "\n".repeat(newlines))
990
+ }
991
+
992
+ pub(crate) fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String, String, Option<rowan::TextSize>) {
993
+ let mut comments: Vec<(String, usize)> = Vec::new();
994
+ let mut newlines = 0;
886
995
  let mut earliest_start = None;
887
996
  let mut node = parent.clone();
888
997
 
@@ -893,10 +1002,11 @@ pub(crate) fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String
893
1002
  match element {
894
1003
  rowan::NodeOrToken::Token(token) => {
895
1004
  if token.kind() == SyntaxKind::COMMENT {
896
- comments.push(token.text().to_string());
1005
+ comments.push((token.text().to_string(), newlines));
1006
+ newlines = 0;
897
1007
  earliest_start = Some(token.text_range().start());
898
1008
  } else if token.kind() == SyntaxKind::WHITESPACE {
899
- // Keep looking past whitespace
1009
+ newlines += token.text().matches('\n').count();
900
1010
  } else {
901
1011
  break;
902
1012
  }
@@ -910,7 +1020,7 @@ pub(crate) fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String
910
1020
  };
911
1021
  }
912
1022
 
913
- if !comments.is_empty() {
1023
+ if !comments.is_empty() || sibling.is_some() {
914
1024
  break;
915
1025
  }
916
1026
 
@@ -922,21 +1032,37 @@ pub(crate) fn collect_preceding_sibling_comments(parent: &SyntaxNode) -> (String
922
1032
  }
923
1033
  }
924
1034
 
1035
+ let gap = "\n".repeat(comments.first().map_or(1, |(_, newlines)| (*newlines).max(1)));
1036
+
925
1037
  comments.reverse();
926
- (comments.join("\n"), earliest_start)
1038
+
1039
+ let text = comments
1040
+ .iter()
1041
+ .enumerate()
1042
+ .map(|(index, (comment, newlines))| {
1043
+ if index + 1 < comments.len() {
1044
+ format!("{}{}", comment, "\n".repeat((*newlines).max(1)))
1045
+ } else {
1046
+ comment.clone()
1047
+ }
1048
+ })
1049
+ .collect();
1050
+
1051
+ (text, gap, earliest_start)
927
1052
  }
928
1053
 
929
1054
  pub(crate) fn collect_groups_with_range(parent: &SyntaxNode) -> (Vec<EntryGroup>, TextRange) {
930
1055
  let mut groups = collect_entry_groups(parent);
931
1056
 
932
- let (sibling_comments, earliest_start) = collect_preceding_sibling_comments(parent);
1057
+ let (sibling_comments, sibling_gap, earliest_start) = collect_preceding_sibling_comments(parent);
933
1058
 
934
1059
  if !sibling_comments.is_empty() {
935
1060
  if let Some(first) = groups.first_mut() {
936
1061
  if first.preceding.is_empty() {
937
1062
  first.preceding = sibling_comments;
1063
+ first.gap = sibling_gap;
938
1064
  } else {
939
- first.preceding = format!("{}\n{}", sibling_comments, first.preceding);
1065
+ first.preceding = format!("{}{}{}", sibling_comments, sibling_gap, first.preceding);
940
1066
  }
941
1067
  }
942
1068
  }
@@ -976,7 +1102,7 @@ pub(crate) fn rebuild_from_groups(groups: &[EntryGroup], indent: &str, preserve_
976
1102
  if group.preceding.is_empty() {
977
1103
  format!("{}{}{}", separator, indent, group.body)
978
1104
  } else {
979
- format!("{}{}\n{}{}", separator, group.preceding, indent, group.body)
1105
+ format!("{}{}{}{}{}", separator, group.preceding, group.gap, indent, group.body)
980
1106
  }
981
1107
  }
982
1108
  })
@@ -0,0 +1,162 @@
1
+ use super::*;
2
+
3
+ #[derive(Debug)]
4
+ pub struct NodeRecord {
5
+ pub parent: Option<usize>,
6
+ pub node_type: NodeType,
7
+ pub selector: String,
8
+ pub key_name: Option<String>,
9
+ pub scalar: Option<ScalarValue>,
10
+ pub has_value: bool,
11
+ pub location: Location,
12
+ pub key_location: Location,
13
+ }
14
+
15
+ fn location_of(lines: &LineIndex, range: TextRange) -> Location {
16
+ compute_location(lines, range.start().into(), range.end().into())
17
+ }
18
+
19
+ fn collection_type(node: &SyntaxNode) -> Option<NodeType> {
20
+ let mut flow = None;
21
+
22
+ for descendant in node.descendants() {
23
+ match descendant.kind() {
24
+ SyntaxKind::BLOCK_SEQ => return Some(NodeType::Sequence),
25
+ SyntaxKind::BLOCK_MAP => return Some(NodeType::Map),
26
+ SyntaxKind::FLOW_SEQ if flow.is_none() => flow = Some(NodeType::Sequence),
27
+ SyntaxKind::FLOW_MAP if flow.is_none() => flow = Some(NodeType::Map),
28
+ _ => {}
29
+ }
30
+ }
31
+
32
+ flow
33
+ }
34
+
35
+ struct Walker<'a> {
36
+ lines: &'a LineIndex,
37
+ records: Vec<NodeRecord>,
38
+ }
39
+
40
+ impl Walker<'_> {
41
+ fn push(&mut self, parent: Option<usize>, node: Option<&SyntaxNode>, selector: String, key: Option<(String, Location)>) {
42
+ let collection = node.and_then(collection_type);
43
+ let scalar = match (node, collection) {
44
+ (Some(node), None) => extract_scalar(node),
45
+ _ => None,
46
+ };
47
+ let node_type = match (collection, &scalar) {
48
+ (Some(node_type), _) => node_type,
49
+ (None, Some(_)) => NodeType::Scalar,
50
+ (None, None) => NodeType::NotFound,
51
+ };
52
+ let location = match node {
53
+ Some(node) if parent.is_some() || !matches!(node_type, NodeType::Sequence) => location_of(self.lines, node.text_range()),
54
+ _ => Location::default(),
55
+ };
56
+ let (key_name, key_location) = key.map(|(name, location)| (Some(name), location)).unwrap_or((None, Location::default()));
57
+
58
+ let index = self.records.len();
59
+
60
+ self.records.push(NodeRecord {
61
+ parent,
62
+ node_type,
63
+ selector: selector.clone(),
64
+ key_name,
65
+ scalar,
66
+ has_value: true,
67
+ location,
68
+ key_location,
69
+ });
70
+
71
+ if let Some(node) = node {
72
+ match node_type {
73
+ NodeType::Sequence => self.sequence_children(index, node, &selector),
74
+ NodeType::Map => self.map_children(index, node, &selector),
75
+ _ => {}
76
+ }
77
+ }
78
+ }
79
+
80
+ fn sequence_children(&mut self, index: usize, node: &SyntaxNode, selector: &str) {
81
+ let entries: Vec<SyntaxNode> = if let Some(sequence) = find_block_sequence(node) {
82
+ sequence.entries().map(|entry| entry.syntax().clone()).collect()
83
+ } else if let Some(sequence) = find_flow_sequence(node) {
84
+ flow_sequence_entries(&sequence)
85
+ } else {
86
+ Vec::new()
87
+ };
88
+
89
+ for (position, entry) in entries.iter().enumerate() {
90
+ self.push(Some(index), Some(entry), format!("{}[{}]", selector, position), None);
91
+ }
92
+ }
93
+
94
+ fn map_children(&mut self, index: usize, node: &SyntaxNode, selector: &str) {
95
+ let entries: Vec<(SyntaxNode, Option<SyntaxNode>, Option<SyntaxNode>)> = if let Some(map) = find_block_map(node) {
96
+ map
97
+ .entries()
98
+ .map(|entry| {
99
+ (
100
+ entry.syntax().clone(),
101
+ entry.key().map(|key| key.syntax().clone()),
102
+ entry.value().map(|value| value.syntax().clone()),
103
+ )
104
+ })
105
+ .collect()
106
+ } else if let Some(map) = find_flow_map(node) {
107
+ flow_map_entries(&map)
108
+ .into_iter()
109
+ .map(|entry| {
110
+ (
111
+ entry.syntax().clone(),
112
+ entry.key().map(|key| key.syntax().clone()),
113
+ entry.value().map(|value| value.syntax().clone()),
114
+ )
115
+ })
116
+ .collect()
117
+ } else {
118
+ Vec::new()
119
+ };
120
+
121
+ for (entry_node, key_node, value_node) in entries {
122
+ let Some(key_node) = key_node else { continue };
123
+ let Some(key_name) = extract_scalar_text(&key_node) else { continue };
124
+
125
+ let key_location = location_of(self.lines, key_node.text_range());
126
+ let child_selector = if selector.is_empty() {
127
+ key_name.clone()
128
+ } else {
129
+ format!("{}.{}", selector, key_name)
130
+ };
131
+
132
+ match value_node {
133
+ Some(value_node) => self.push(Some(index), Some(&value_node), child_selector, Some((key_name, key_location))),
134
+ None => self.records.push(NodeRecord {
135
+ parent: Some(index),
136
+ node_type: NodeType::Scalar,
137
+ selector: child_selector,
138
+ key_name: Some(key_name),
139
+ scalar: None,
140
+ has_value: false,
141
+ location: location_of(self.lines, entry_node.text_range()),
142
+ key_location,
143
+ }),
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ impl Document {
150
+ pub fn node_records(&self) -> Vec<NodeRecord> {
151
+ let mut walker = Walker {
152
+ lines: self.line_index(),
153
+ records: Vec::new(),
154
+ };
155
+
156
+ if let Some(document) = Root::cast(self.root.clone()).and_then(|root| root.documents().next()) {
157
+ walker.push(None, Some(document.syntax()), String::new(), None);
158
+ }
159
+
160
+ walker.records
161
+ }
162
+ }