1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fmt;
4
5use crate::syntax::node_link;
6use crate::{
7 Children, CollectionStyle, Diagnostic, DiagnosticKind, FromYamlDoc, Node, NodeId, NodeKind,
8 Parser, ScalarStyle, SemanticKind, SemanticProperties, SemanticStore, Source, Span, ToYamlDoc,
9 ToYamlFragment, Token, YamlEditError, YamlError, YamlEvent, YamlFragment,
10 decode_scalar_value_with_content_indent, directive_emit_error, double_quoted_scalar_end,
11 edits_conflict, events_to_test_string, format_scalar_value, lex, parse_node_properties,
12 plain_scalar_end, resolve_tag, single_quoted_scalar_end, strip_inline_comment,
13 validate_plain_mapping_fragment, validate_tag_directive_parts_for_emit, validate_yaml_chars,
14 validate_yaml_directive_version_for_emit,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Edit {
20 pub span: Span,
22 pub replacement: String,
24}
25
26pub struct YamlEvents<'doc> {
28 doc: &'doc YamlDoc,
29 tasks: Vec<EventTask>,
30}
31
32impl Iterator for YamlEvents<'_> {
33 type Item = YamlEvent;
34
35 fn next(&mut self) -> Option<Self::Item> {
36 while let Some(task) = self.tasks.pop() {
37 match task {
38 EventTask::StreamStart => {
39 return Some(YamlEvent {
40 kind: crate::YamlEventKind::StreamStart,
41 span: Span::from_usize(0, self.doc.source.len()),
42 cst: None,
43 content_indent: None,
44 });
45 }
46 EventTask::Documents(index) => self.schedule_document(index),
47 EventTask::DocumentStart(document) => {
48 let Some(metadata) = self.doc.semantic_metadata(document) else {
49 continue;
50 };
51 return Some(YamlEvent {
52 kind: crate::YamlEventKind::DocumentStart {
53 explicit: metadata.explicit_start(),
54 },
55 span: self.doc.semantic_span(document, metadata),
56 cst: Some(document),
57 content_indent: None,
58 });
59 }
60 EventTask::DocumentChildren(next) => self.schedule_document_child(next),
61 EventTask::DocumentEnd(document) => {
62 let Some(metadata) = self.doc.semantic_metadata(document) else {
63 continue;
64 };
65 return Some(YamlEvent {
66 kind: crate::YamlEventKind::DocumentEnd {
67 explicit: metadata.explicit_end(),
68 },
69 span: self.doc.semantic_end_span(document, metadata),
70 cst: None,
71 content_indent: None,
72 });
73 }
74 EventTask::Node(node) => {
75 if let Some(event) = self.schedule_node(node) {
76 return Some(event);
77 }
78 }
79 EventTask::MappingEntries(next) => self.schedule_mapping_entry(next),
80 EventTask::SequenceEntries(next) => self.schedule_sequence_entry(next),
81 EventTask::CollectionEnd { node, mapping } => {
82 let Some(metadata) = self.doc.semantic_metadata(node) else {
83 continue;
84 };
85 return Some(YamlEvent {
86 kind: if mapping {
87 crate::YamlEventKind::MappingEnd
88 } else {
89 crate::YamlEventKind::SequenceEnd
90 },
91 span: self.doc.semantic_end_span(node, metadata),
92 cst: None,
93 content_indent: None,
94 });
95 }
96 EventTask::StreamEnd => {
97 return Some(YamlEvent {
98 kind: crate::YamlEventKind::StreamEnd,
99 span: Span::empty_from_usize(self.doc.source.len()),
100 cst: None,
101 content_indent: None,
102 });
103 }
104 }
105 }
106 None
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111enum EventTask {
112 StreamStart,
113 Documents(usize),
114 DocumentStart(NodeId),
115 DocumentChildren(u32),
116 DocumentEnd(NodeId),
117 Node(NodeId),
118 MappingEntries(u32),
119 SequenceEntries(u32),
120 CollectionEnd { node: NodeId, mapping: bool },
121 StreamEnd,
122}
123
124impl YamlEvents<'_> {
125 fn schedule_document(&mut self, index: usize) {
126 let Some(&document) = self.doc.semantics.documents.get(index) else {
127 return;
128 };
129 let Some(node) = self.doc.node(document) else {
130 self.tasks.push(EventTask::Documents(index + 1));
131 return;
132 };
133 self.tasks.push(EventTask::Documents(index + 1));
134 self.tasks.push(EventTask::DocumentEnd(document));
135 self.tasks
136 .push(EventTask::DocumentChildren(node.first_child));
137 self.tasks.push(EventTask::DocumentStart(document));
138 }
139
140 fn schedule_document_child(&mut self, next: u32) {
141 let Some(child) = node_link(next) else {
142 return;
143 };
144 self.tasks.push(EventTask::DocumentChildren(
145 self.doc.nodes[child.as_usize()].next_sibling,
146 ));
147 if self.doc.semantic_metadata(child).is_some() {
148 self.tasks.push(EventTask::Node(child));
149 }
150 }
151
152 fn schedule_node(&mut self, node: NodeId) -> Option<YamlEvent> {
153 let metadata = self.doc.semantic_metadata(node)?;
154 let span = self.doc.semantic_span(node, metadata);
155 match metadata.kind {
156 SemanticKind::Document => None,
157 SemanticKind::Mapping { style } => {
158 self.tasks.push(EventTask::CollectionEnd {
159 node,
160 mapping: true,
161 });
162 self.tasks.push(EventTask::MappingEntries(
163 self.doc.nodes[node.as_usize()].first_child,
164 ));
165 Some(YamlEvent {
166 kind: crate::YamlEventKind::MappingStart {
167 style,
168 tag: self
169 .doc
170 .resolved_tag(node)
171 .ok()
172 .flatten()
173 .map(Cow::into_owned),
174 anchor: self.doc.anchor(node).map(str::to_owned),
175 },
176 span,
177 cst: Some(node),
178 content_indent: None,
179 })
180 }
181 SemanticKind::Sequence { style } => {
182 self.tasks.push(EventTask::CollectionEnd {
183 node,
184 mapping: false,
185 });
186 self.tasks.push(EventTask::SequenceEntries(
187 self.doc.nodes[node.as_usize()].first_child,
188 ));
189 Some(YamlEvent {
190 kind: crate::YamlEventKind::SequenceStart {
191 style,
192 tag: self
193 .doc
194 .resolved_tag(node)
195 .ok()
196 .flatten()
197 .map(Cow::into_owned),
198 anchor: self.doc.anchor(node).map(str::to_owned),
199 },
200 span,
201 cst: Some(node),
202 content_indent: None,
203 })
204 }
205 SemanticKind::Scalar { style } => Some(YamlEvent {
206 kind: crate::YamlEventKind::Scalar {
207 style,
208 value: self
209 .doc
210 .scalar_value(node)
211 .map(Cow::into_owned)
212 .unwrap_or_default(),
213 tag: self
214 .doc
215 .resolved_tag(node)
216 .ok()
217 .flatten()
218 .map(Cow::into_owned),
219 anchor: self.doc.anchor(node).map(str::to_owned),
220 },
221 span,
222 cst: Some(node),
223 content_indent: self
224 .doc
225 .semantic_properties(node)
226 .and_then(|properties| properties.content_indent),
227 }),
228 SemanticKind::Alias => Some(YamlEvent {
229 kind: crate::YamlEventKind::Alias {
230 name: self.doc.alias_name(node).unwrap_or_default().to_owned(),
231 },
232 span,
233 cst: Some(node),
234 content_indent: None,
235 }),
236 }
237 }
238
239 fn schedule_mapping_entry(&mut self, next: u32) {
240 let Some(entry) = node_link(next) else {
241 return;
242 };
243 self.tasks.push(EventTask::MappingEntries(
244 self.doc.nodes[entry.as_usize()].next_sibling,
245 ));
246 if self.doc.nodes[entry.as_usize()].kind != NodeKind::MappingEntry {
247 return;
248 }
249 let Some(key) = self.first_semantic_child(entry) else {
250 return;
251 };
252 let Some(value) = self.next_semantic_sibling(key) else {
253 return;
254 };
255 self.tasks.push(EventTask::Node(value));
256 self.tasks.push(EventTask::Node(key));
257 }
258
259 fn schedule_sequence_entry(&mut self, next: u32) {
260 let Some(entry) = node_link(next) else {
261 return;
262 };
263 self.tasks.push(EventTask::SequenceEntries(
264 self.doc.nodes[entry.as_usize()].next_sibling,
265 ));
266 if self.doc.nodes[entry.as_usize()].kind != NodeKind::SequenceEntry {
267 return;
268 }
269 if let Some(item) = self.first_semantic_child(entry) {
270 self.tasks.push(EventTask::Node(item));
271 }
272 }
273
274 fn first_semantic_child(&self, parent: NodeId) -> Option<NodeId> {
275 let next = self.doc.nodes[parent.as_usize()].first_child;
276 self.next_semantic(next)
277 }
278
279 fn next_semantic_sibling(&self, node: NodeId) -> Option<NodeId> {
280 let next = self.doc.nodes[node.as_usize()].next_sibling;
281 self.next_semantic(next)
282 }
283
284 fn next_semantic(&self, mut next: u32) -> Option<NodeId> {
285 while let Some(node) = node_link(next) {
286 if self.doc.semantic_metadata(node).is_some() {
287 return Some(node);
288 }
289 next = self.doc.nodes[node.as_usize()].next_sibling;
290 }
291 None
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct YamlDirective {
298 pub version: String,
300 pub node: NodeId,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct TagDirective {
307 pub handle: String,
309 pub prefix: String,
311 pub node: NodeId,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
317pub struct ReservedDirective {
318 pub name: String,
320 pub parameters: Vec<String>,
322 pub node: NodeId,
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum ParsedDirective {
328 Yaml(YamlDirective),
329 Tag(TagDirective),
330 Reserved(ReservedDirective),
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
335pub enum MappingEntryStyle {
336 #[default]
338 Inherit,
339 Indent(usize),
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct YamlDoc {
346 pub(crate) source: Source,
348 pub(crate) nodes: Vec<Node>,
350 pub(crate) semantics: SemanticStore,
352 pub(crate) root_override: Option<NodeId>,
354 pub(crate) edits: Vec<Edit>,
356}
357
358impl YamlDoc {
359 pub fn parse(input: &str) -> Result<Self, YamlError> {
369 Self::parse_owned(input.to_owned())
370 }
371
372 pub fn parse_owned(input: String) -> Result<Self, YamlError> {
379 let source = Source::new(input)?;
380 let parsed = Parser::new(&source)
381 .parse()
382 .map_err(|error| error.with_position_from(&source))?;
383 Ok(Self {
384 source,
385 nodes: parsed.nodes,
386 semantics: parsed.semantics,
387 root_override: None,
388 edits: Vec::new(),
389 })
390 }
391
392 pub fn commit_edits(&mut self) -> Result<(), YamlError> {
404 if self.edits.is_empty() {
405 return Ok(());
406 }
407
408 let edited = self.to_string();
409 *self = Self::parse(&edited)?;
410 Ok(())
411 }
412
413 #[must_use]
415 pub fn as_source(&self) -> &str {
416 self.source.as_str()
417 }
418
419 #[must_use]
421 pub const fn source(&self) -> &Source {
422 &self.source
423 }
424
425 pub fn tokens(&self) -> Result<Vec<Token>, YamlError> {
434 lex(&self.source).map_err(|error| error.with_position_from(&self.source))
435 }
436
437 #[must_use]
439 pub fn root(&self) -> Option<NodeId> {
440 (!self.nodes.is_empty()).then_some(NodeId(0))
441 }
442
443 #[must_use]
445 pub fn events(&self) -> YamlEvents<'_> {
446 let mut tasks = Vec::with_capacity(8);
447 tasks.push(EventTask::StreamEnd);
448 tasks.push(EventTask::Documents(0));
449 tasks.push(EventTask::StreamStart);
450 YamlEvents { doc: self, tasks }
451 }
452
453 #[must_use]
455 pub fn events_to_test_string(&self) -> String {
456 events_to_test_string(self.events())
457 }
458
459 #[must_use]
461 pub fn document_count(&self) -> usize {
462 self.root_override
463 .map_or(self.semantics.documents.len(), |_| 1)
464 }
465
466 pub fn append_document<T>(&mut self, value: &T) -> Result<(), YamlError>
476 where
477 T: ToYamlFragment,
478 {
479 let line_ending = self.preferred_line_ending();
480 let mut replacement = self.document_append_prefix(line_ending);
481 replacement.push_str("---");
482 replacement.push_str(line_ending);
483 let fragment = value.to_yaml_fragment(0, line_ending)?;
484 replacement.push_str(&fragment);
485 replacement.push_str(line_ending);
486 self.queue_edit(Span::empty_from_usize(self.source.len()), replacement)
487 }
488
489 pub fn append_empty_mapping_document(&mut self) -> Result<(), YamlError> {
496 self.append_document(&std::collections::BTreeMap::<String, String>::new())
497 }
498
499 #[must_use]
501 pub fn yaml_directive(&self) -> Option<YamlDirective> {
502 self.directive_nodes()
503 .filter_map(|node| self.parse_directive_node(node).ok())
504 .find_map(|directive| match directive {
505 ParsedDirective::Yaml(directive) => Some(directive),
506 ParsedDirective::Tag(_) | ParsedDirective::Reserved(_) => None,
507 })
508 }
509
510 #[must_use]
512 pub fn tag_directives(&self) -> Vec<TagDirective> {
513 self.directive_nodes()
514 .filter_map(|node| self.parse_directive_node(node).ok())
515 .filter_map(|directive| match directive {
516 ParsedDirective::Tag(directive) => Some(directive),
517 ParsedDirective::Yaml(_) | ParsedDirective::Reserved(_) => None,
518 })
519 .collect()
520 }
521
522 #[must_use]
524 pub fn reserved_directives(&self) -> Vec<ReservedDirective> {
525 self.directive_nodes()
526 .filter_map(|node| self.parse_directive_node(node).ok())
527 .filter_map(|directive| match directive {
528 ParsedDirective::Reserved(directive) => Some(directive),
529 ParsedDirective::Yaml(_) | ParsedDirective::Tag(_) => None,
530 })
531 .collect()
532 }
533
534 pub fn set_yaml_directive(&mut self, version: &str) -> Result<(), YamlError> {
541 validate_yaml_directive_version_for_emit(version)?;
542 let replacement = format!("%YAML {version}");
543 if let Some(directive) = self.yaml_directive() {
544 let span = self.directive_content_span(directive.node)?;
545 self.queue_edit(span, replacement)
546 } else {
547 self.insert_directive_line(replacement)
548 }
549 }
550
551 pub fn set_tag_directive(&mut self, handle: &str, prefix: &str) -> Result<(), YamlError> {
558 validate_tag_directive_parts_for_emit(handle, prefix)?;
559 let replacement = format!("%TAG {handle} {prefix}");
560 if let Some(directive) = self
561 .tag_directives()
562 .into_iter()
563 .find(|directive| directive.handle == handle)
564 {
565 let span = self.directive_content_span(directive.node)?;
566 self.queue_edit(span, replacement)
567 } else {
568 self.insert_directive_line(replacement)
569 }
570 }
571
572 pub fn remove_yaml_directive(&mut self) -> Result<(), YamlError> {
578 if let Some(directive) = self.yaml_directive() {
579 self.remove_directive_node(directive.node)?;
580 }
581 Ok(())
582 }
583
584 pub fn remove_tag_directive(&mut self, handle: &str) -> Result<(), YamlError> {
590 if let Some(directive) = self
591 .tag_directives()
592 .into_iter()
593 .find(|directive| directive.handle == handle)
594 {
595 self.remove_directive_node(directive.node)?;
596 }
597 Ok(())
598 }
599
600 #[must_use]
602 pub fn node(&self, node: NodeId) -> Option<&Node> {
603 self.nodes.get(node.0 as usize)
604 }
605
606 #[must_use]
608 pub fn children(&self, node: NodeId) -> Children<'_> {
609 Children::new(&self.nodes, node)
610 }
611
612 pub(crate) fn semantic_children(&self, node: NodeId) -> impl Iterator<Item = NodeId> + '_ {
613 self.children(node)
614 .filter(|child| self.semantic_metadata(*child).is_some())
615 }
616
617 #[must_use]
619 pub fn semantic_kind(&self, node: NodeId) -> Option<SemanticKind> {
620 self.semantic_metadata(node).map(|node| node.kind)
621 }
622
623 pub(crate) fn semantic_metadata(&self, node: NodeId) -> Option<crate::semantic::SemanticNode> {
624 self.semantics.get(self.node(node)?)
625 }
626
627 pub(crate) fn semantic_properties(&self, node: NodeId) -> Option<SemanticProperties> {
628 self.semantics.properties(self.node(node)?)
629 }
630
631 #[must_use]
633 pub fn raw_tag(&self, node: NodeId) -> Option<&str> {
634 let span = self.semantic_properties(node)?.tag?;
635 Some(self.source.slice(span))
636 }
637
638 pub fn resolved_tag(&self, node: NodeId) -> Result<Option<Cow<'_, str>>, YamlError> {
644 let Some(raw) = self.raw_tag(node) else {
645 return Ok(None);
646 };
647 let document = self
648 .node(node)
649 .and_then(|cst| self.semantics.property_document(cst))
650 .unwrap_or(node);
651 let handles = self
652 .semantics
653 .tag_directives(document)
654 .map(|(handle, prefix)| {
655 (
656 self.source.slice(handle).to_owned(),
657 self.source.slice(prefix).to_owned(),
658 )
659 })
660 .collect::<BTreeMap<_, _>>();
661 let span = self
662 .semantic_properties(node)
663 .and_then(|properties| properties.tag)
664 .unwrap_or_else(|| self.node(node).map_or(Span::empty(0), Node::span));
665 resolve_tag(raw, &handles, span).map(|tag| Some(Cow::Owned(tag)))
666 }
667
668 #[must_use]
670 pub fn anchor(&self, node: NodeId) -> Option<&str> {
671 let span = self.semantic_properties(node)?.anchor?;
672 Some(self.source.slice(span))
673 }
674
675 #[must_use]
677 pub fn alias_name(&self, node: NodeId) -> Option<&str> {
678 let span = self.semantic_properties(node)?.alias?;
679 Some(self.source.slice(span))
680 }
681
682 #[must_use]
684 pub fn resolve_alias(&self, node: NodeId) -> Option<NodeId> {
685 let name = self.alias_name(node)?;
686 let document = self.semantics.property_document(self.node(node)?)?;
687 let alias_start = self.node(node)?.span.start;
688 self.semantics
689 .anchors()
690 .rev()
691 .find(|(span, target, anchor_document)| {
692 *anchor_document == document
693 && self
694 .node(*target)
695 .is_some_and(|node| node.span.start <= alias_start)
696 && self.source.slice(*span) == name
697 })
698 .map(|(_, target, _)| target)
699 }
700
701 fn semantic_span(&self, node: NodeId, metadata: crate::semantic::SemanticNode) -> Span {
702 let cst_start = self
703 .node(node)
704 .map_or(metadata.end_offset, |node| node.span.start);
705 Span::new(
706 self.semantics
707 .span_start(self.node(node).expect("semantic node has CST"), cst_start),
708 self.node(node)
709 .map_or(metadata.end_offset, |node| node.span.end),
710 )
711 }
712
713 fn semantic_end_span(&self, node: NodeId, metadata: crate::semantic::SemanticNode) -> Span {
714 if metadata.explicit_end()
715 && let Some(marker) = self.children(node).find_map(|child| {
716 let child = self.node(child)?;
717 (child.kind == NodeKind::DocumentMarker
718 && self.source.slice(child.span).starts_with("..."))
719 .then_some(child.span)
720 })
721 {
722 return marker;
723 }
724 if matches!(
725 metadata.kind,
726 SemanticKind::Mapping {
727 style: CollectionStyle::Flow
728 } | SemanticKind::Sequence {
729 style: CollectionStyle::Flow
730 }
731 ) {
732 return self.semantic_span(node, metadata);
733 }
734 Span::empty(metadata.end_offset)
735 }
736
737 pub fn documents(&self) -> impl Iterator<Item = NodeId> + '_ {
739 self.semantics.documents.iter().copied()
740 }
741
742 pub fn document_root(&self, index: usize) -> Result<Option<NodeId>, YamlError> {
752 if let Some(root) = self.root_override {
753 return (index == 0)
754 .then_some(Some(root))
755 .ok_or_else(|| self.document_index_error(index));
756 }
757 let document = self
758 .semantics
759 .documents
760 .get(index)
761 .copied()
762 .ok_or_else(|| self.document_index_error(index))?;
763 let root = self.semantic_children(document).next();
764 Ok(root.filter(|root| {
765 !matches!(self.semantic_kind(*root), Some(SemanticKind::Scalar { .. }))
766 || self.node(*root).is_some_and(|node| !node.span.is_empty())
767 }))
768 }
769
770 pub fn mapping_entries(&self, mapping: NodeId) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
772 let is_mapping = matches!(
773 self.semantic_kind(mapping),
774 Some(SemanticKind::Mapping { .. })
775 );
776 self.children(mapping).filter_map(move |entry| {
777 if !is_mapping || self.node(entry)?.kind != NodeKind::MappingEntry {
778 return None;
779 }
780 let mut children = self.semantic_children(entry);
781 Some((children.next()?, children.next()?))
782 })
783 }
784
785 pub fn sequence_items(&self, sequence: NodeId) -> impl Iterator<Item = NodeId> + '_ {
787 let is_sequence = matches!(
788 self.semantic_kind(sequence),
789 Some(SemanticKind::Sequence { .. })
790 );
791 self.children(sequence).filter_map(move |entry| {
792 if !is_sequence || self.node(entry)?.kind != NodeKind::SequenceEntry {
793 return None;
794 }
795 self.semantic_children(entry).next()
796 })
797 }
798
799 pub fn root_mapping(&self) -> Result<NodeId, YamlError> {
806 self.document_root_mapping(0)
807 }
808
809 pub fn document_root_mapping(&self, index: usize) -> Result<NodeId, YamlError> {
816 if let Some(root) = self.root_override {
817 return (index == 0)
818 .then_some(root)
819 .ok_or_else(|| self.document_index_error(index));
820 }
821 let document = self
822 .semantics
823 .documents
824 .get(index)
825 .copied()
826 .ok_or_else(|| self.document_index_error(index))?;
827 self.semantic_children(document)
828 .find(|child| {
829 self.node(*child).is_some_and(|node| {
830 matches!(node.kind, NodeKind::BlockMapping | NodeKind::FlowMapping)
831 }) && matches!(
832 self.semantic_kind(*child),
833 Some(SemanticKind::Mapping { .. })
834 )
835 })
836 .ok_or_else(|| {
837 YamlError::new(
838 Diagnostic::new(
839 DiagnosticKind::Semantic,
840 "document does not contain a root mapping",
841 self.node(document).map_or(Span::empty(0), |node| node.span),
842 )
843 .with_expected("a block or flow mapping node"),
844 )
845 })
846 }
847
848 pub fn read_document<T>(&self, index: usize) -> Result<T, YamlError>
855 where
856 T: FromYamlDoc,
857 {
858 let root = self
859 .document_root(index)?
860 .ok_or_else(|| self.empty_document_error(index))?;
861 let nested = self.rerooted_at(root)?;
862 T::from_yaml_doc(&nested)
863 }
864
865 pub fn write_document<T>(&mut self, index: usize, value: &T) -> Result<(), YamlError>
872 where
873 T: ToYamlDoc,
874 {
875 let root = self
876 .document_root(index)?
877 .ok_or_else(|| self.empty_document_error(index))?;
878 let mut nested = self.rerooted_at(root)?;
879 value.apply_to_yaml_doc(&mut nested)?;
880 self.queue_edits_from(&nested)
881 }
882
883 pub fn get_mapping_entry(
889 &self,
890 mapping: NodeId,
891 key: &str,
892 ) -> Result<Option<NodeId>, YamlError> {
893 Ok(self
894 .find_mapping_pair(mapping, key)?
895 .and_then(|(key, _)| self.containing_entry(key)))
896 }
897
898 pub fn get_mapping_value(
904 &self,
905 mapping: NodeId,
906 key: &str,
907 ) -> Result<Option<NodeId>, YamlError> {
908 Ok(self
909 .find_mapping_pair(mapping, key)?
910 .map(|(_, value)| value))
911 }
912
913 pub fn get_path(&self, path: &[&str]) -> Result<Option<NodeId>, YamlError> {
919 self.get_path_in_document(0, path)
920 }
921
922 pub fn get_path_in_document(
929 &self,
930 index: usize,
931 path: &[&str],
932 ) -> Result<Option<NodeId>, YamlError> {
933 let Some((first, rest)) = path.split_first() else {
934 return Ok(None);
935 };
936 let Some((_, mut current)) =
937 self.find_mapping_pair(self.document_root_mapping(index)?, first)?
938 else {
939 return Ok(None);
940 };
941 for segment in rest {
942 let Some((_, value)) = self.find_mapping_pair(current, segment)? else {
943 return Ok(None);
944 };
945 current = value;
946 }
947 Ok(Some(current))
948 }
949
950 fn document_index_error(&self, index: usize) -> YamlError {
951 YamlError::new(
952 Diagnostic::new(
953 DiagnosticKind::Semantic,
954 format!("document index {index} is out of range"),
955 Span::empty_from_usize(self.source.len()),
956 )
957 .with_expected("an existing document index"),
958 )
959 }
960
961 fn empty_document_error(&self, index: usize) -> YamlError {
962 YamlError::new(
963 Diagnostic::new(
964 DiagnosticKind::Typed,
965 format!("document {index} does not contain a YAML value"),
966 Span::empty_from_usize(self.source.len()),
967 )
968 .with_expected("a scalar, sequence, or mapping document root"),
969 )
970 }
971
972 fn find_mapping_pair(
973 &self,
974 mapping: NodeId,
975 key: &str,
976 ) -> Result<Option<(NodeId, NodeId)>, YamlError> {
977 for (key_node, value_node) in self.mapping_entries(mapping) {
978 if self.scalar_value(key_node)? == key {
979 return Ok(Some((key_node, value_node)));
980 }
981 }
982 Ok(None)
983 }
984
985 pub(crate) fn rerooted_at(&self, root: NodeId) -> Result<Self, YamlError> {
986 let root_node = self.expect_node(root)?;
987 if self.semantic_kind(root).is_none() {
988 return Err(YamlError::new(
989 Diagnostic::new(
990 DiagnosticKind::Semantic,
991 "typed overlay root does not have semantic metadata",
992 root_node.span,
993 )
994 .with_expected("a semantic YAML value"),
995 )
996 .with_position_from(&self.source));
997 }
998 let mut doc = self.clone();
999 doc.root_override = Some(root);
1000 doc.edits.clear();
1001 Ok(doc)
1002 }
1003
1004 pub(crate) fn rerooted_without_tag(&self, root: NodeId) -> Result<Self, YamlError> {
1005 let mut doc = self.rerooted_at(root)?;
1006 doc.semantics.clear_tag(&doc.nodes[root.as_usize()]);
1007 Ok(doc)
1008 }
1009
1010 pub(crate) fn rerooted_at_mapping(&self, mapping: NodeId) -> Result<Self, YamlError> {
1011 let mapping_node = self.expect_node(mapping)?;
1012 if !matches!(
1013 mapping_node.kind,
1014 NodeKind::BlockMapping | NodeKind::FlowMapping
1015 ) {
1016 return Err(YamlError::new(
1017 Diagnostic::new(
1018 DiagnosticKind::Semantic,
1019 format!("expected mapping, found {:?}", mapping_node.kind),
1020 mapping_node.span,
1021 )
1022 .with_expected("BlockMapping or FlowMapping"),
1023 )
1024 .with_position_from(&self.source));
1025 }
1026 if !matches!(
1027 self.semantic_kind(mapping),
1028 Some(SemanticKind::Mapping { .. })
1029 ) {
1030 return Err(YamlError::new(
1031 Diagnostic::new(
1032 DiagnosticKind::Semantic,
1033 "mapping does not have semantic metadata",
1034 self.expect_node(mapping)?.span,
1035 )
1036 .with_expected("a semantic mapping"),
1037 )
1038 .with_position_from(&self.source));
1039 }
1040 self.rerooted_at(mapping)
1041 }
1042
1043 pub(crate) fn queue_edits_from(&mut self, other: &YamlDoc) -> Result<(), YamlError> {
1044 for edit in &other.edits {
1045 self.queue_edit(edit.span, edit.replacement.clone())?;
1046 }
1047 Ok(())
1048 }
1049
1050 pub fn scalar_text(&self, node: NodeId) -> Result<&str, YamlError> {
1057 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1058 Ok(self.source.slice(node.span))
1059 }
1060
1061 pub fn scalar_value(&self, node: NodeId) -> Result<Cow<'_, str>, YamlError> {
1072 let node_ref = self.expect_node(node)?;
1073 if !matches!(
1074 node_ref.kind,
1075 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1076 ) {
1077 return Err(YamlError::new(
1078 Diagnostic::new(
1079 DiagnosticKind::Semantic,
1080 format!("expected scalar value, found {:?}", node_ref.kind),
1081 node_ref.span,
1082 )
1083 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1084 )
1085 .with_position_from(&self.source));
1086 }
1087 let text = self.source.slice(node_ref.span);
1088 let properties = parse_node_properties(text, node_ref.span)?;
1089 let value_text = &text[properties.value_start()..];
1090 if matches!(
1091 self.semantic_kind(node),
1092 Some(SemanticKind::Scalar {
1093 style: crate::YamlScalarStyle::Plain,
1094 ..
1095 })
1096 ) && !value_text.contains(['\n', '\r'])
1097 {
1098 return Ok(Cow::Borrowed(&value_text[..plain_scalar_end(value_text)]));
1099 }
1100 decode_scalar_value_with_content_indent(
1101 value_text,
1102 self.semantic_properties(node)
1103 .and_then(|properties| properties.content_indent)
1104 .map(|indent| indent as usize),
1105 )
1106 .map(Cow::Owned)
1107 }
1108
1109 pub fn borrowable_scalar_span(&self, node: NodeId) -> Result<Option<Span>, YamlError> {
1121 let node_ref = self.expect_node(node)?;
1122 if !matches!(
1123 self.semantic_kind(node),
1124 Some(SemanticKind::Scalar {
1125 style: crate::YamlScalarStyle::Plain,
1126 })
1127 ) {
1128 if matches!(
1129 node_ref.kind,
1130 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1131 ) {
1132 return Ok(None);
1133 }
1134 return Err(YamlError::new(
1135 Diagnostic::new(
1136 DiagnosticKind::Semantic,
1137 format!("expected scalar value, found {:?}", node_ref.kind),
1138 node_ref.span,
1139 )
1140 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1141 )
1142 .with_position_from(&self.source));
1143 }
1144
1145 let text = self.source.slice(node_ref.span);
1146 if text.contains(['\n', '\r']) {
1147 return Ok(None);
1148 }
1149 let properties = parse_node_properties(text, node_ref.span)?;
1150 let value_text = &text[properties.value_start()..];
1151 let value_len = plain_scalar_end(value_text);
1152 let start = Span::offset_from_usize(node_ref.span.start, properties.value_start());
1153 Ok(Some(Span::new(
1154 start,
1155 Span::offset_from_usize(start, value_len),
1156 )))
1157 }
1158
1159 pub fn set_scalar(&mut self, path: &[&str], value: &str) -> Result<(), YamlError> {
1173 let node = self.get_path(path)?.ok_or_else(|| {
1174 YamlError::new(
1175 Diagnostic::new(
1176 DiagnosticKind::Semantic,
1177 format!("path `{}` does not exist", path.join(".")),
1178 Span::empty(0),
1179 )
1180 .with_expected("an existing scalar node"),
1181 )
1182 })?;
1183
1184 let (span, style) = self.scalar_replacement_target(node)?;
1185 let replacement = format_scalar_value(value, style)?;
1186 self.queue_edit(span, replacement)
1187 }
1188
1189 pub fn replace_node_text(
1199 &mut self,
1200 node: NodeId,
1201 text: impl Into<String>,
1202 ) -> Result<(), YamlError> {
1203 let span = self.expect_node(node)?.span;
1204 self.queue_edit(span, text.into())
1205 }
1206
1207 pub fn insert_mapping_entry(
1218 &mut self,
1219 mapping: NodeId,
1220 key: &str,
1221 value: &str,
1222 style: MappingEntryStyle,
1223 ) -> Result<(), YamlError> {
1224 self.insert_mapping_entry_with_comment(mapping, key, value, style, None)
1225 }
1226
1227 pub fn insert_mapping_entry_with_comment(
1239 &mut self,
1240 mapping: NodeId,
1241 key: &str,
1242 value: &str,
1243 style: MappingEntryStyle,
1244 comment: Option<&str>,
1245 ) -> Result<(), YamlError> {
1246 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1247 let indent = match style {
1248 MappingEntryStyle::Inherit => self.block_mapping_entry_indent(mapping),
1249 MappingEntryStyle::Indent(indent) => indent,
1250 };
1251 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1252 let needs_leading_break =
1253 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1254 let preserve_paragraph_break = comment.is_some()
1255 && insertion_offset == self.source.len()
1256 && self.mapping_has_blank_line(mapping_node);
1257 let replacement = self.format_mapping_entry_replacement(
1258 indent,
1259 key,
1260 value,
1261 comment,
1262 needs_leading_break,
1263 preserve_paragraph_break,
1264 )?;
1265
1266 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1267 }
1268
1269 pub fn insert_mapping_value_with_comment<T>(
1277 &mut self,
1278 mapping: NodeId,
1279 key: &str,
1280 value: &T,
1281 style: MappingEntryStyle,
1282 comment: Option<&str>,
1283 ) -> Result<(), YamlError>
1284 where
1285 T: ToYamlFragment,
1286 {
1287 if matches!(
1288 self.semantic_kind(mapping),
1289 Some(SemanticKind::Mapping {
1290 style: CollectionStyle::Flow
1291 })
1292 ) {
1293 let fragment = self.typed_value_fragment(value)?;
1294 return self
1295 .queue_mapping_insert(mapping, key, &fragment)
1296 .map_err(YamlEditError::into_yaml_error);
1297 }
1298 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1299 let indent = match style {
1300 MappingEntryStyle::Inherit => self.block_mapping_entry_indent(mapping),
1301 MappingEntryStyle::Indent(indent) => indent,
1302 };
1303 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1304 let needs_leading_break =
1305 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1306 let preserve_paragraph_break = comment.is_some()
1307 && insertion_offset == self.source.len()
1308 && self.mapping_has_blank_line(mapping_node);
1309 let replacement = self.format_mapping_value_replacement(
1310 indent,
1311 key,
1312 value,
1313 comment,
1314 needs_leading_break,
1315 preserve_paragraph_break,
1316 )?;
1317
1318 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1319 }
1320
1321 fn typed_value_fragment<T>(&self, value: &T) -> Result<YamlFragment, YamlError>
1322 where
1323 T: ToYamlFragment,
1324 {
1325 let yaml = value.to_yaml_fragment(0, self.preferred_line_ending())?;
1326 YamlFragment::parse(&yaml).map_err(|error| {
1327 YamlError::new(
1328 Diagnostic::new(
1329 DiagnosticKind::Emitter,
1330 format!("typed YAML fragment is invalid: {error}"),
1331 Span::empty(0),
1332 )
1333 .with_expected("one valid YAML value"),
1334 )
1335 })
1336 }
1337
1338 pub fn insert_mapping_value_ordered_with_comment<T>(
1345 &mut self,
1346 mapping: NodeId,
1347 key: &str,
1348 value: &T,
1349 style: MappingEntryStyle,
1350 comment: Option<&str>,
1351 ordered_keys: &[&str],
1352 ) -> Result<(), YamlError>
1353 where
1354 T: ToYamlFragment,
1355 {
1356 let mut next_entry = None;
1357 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1358 for later_key in &ordered_keys[position + 1..] {
1359 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1360 next_entry = Some(entry);
1361 break;
1362 }
1363 }
1364 }
1365
1366 if let Some(next_entry) = next_entry {
1367 self.insert_mapping_value_before_with_comment(next_entry, key, value, style, comment)
1368 } else {
1369 self.insert_mapping_value_with_comment(mapping, key, value, style, comment)
1370 }
1371 }
1372
1373 pub fn insert_mapping_entry_before_with_comment(
1381 &mut self,
1382 before_entry: NodeId,
1383 key: &str,
1384 value: &str,
1385 style: MappingEntryStyle,
1386 comment: Option<&str>,
1387 ) -> Result<(), YamlError> {
1388 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1389 let indent = match style {
1390 MappingEntryStyle::Inherit => self.node_column(before_node),
1391 MappingEntryStyle::Indent(indent) => indent,
1392 };
1393 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1394 let replacement =
1395 self.format_mapping_entry_replacement(indent, key, value, comment, false, false)?;
1396
1397 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1398 }
1399
1400 pub fn insert_mapping_value_before_with_comment<T>(
1407 &mut self,
1408 before_entry: NodeId,
1409 key: &str,
1410 value: &T,
1411 style: MappingEntryStyle,
1412 comment: Option<&str>,
1413 ) -> Result<(), YamlError>
1414 where
1415 T: ToYamlFragment,
1416 {
1417 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1418 let mapping = before_node.parent().ok_or_else(|| {
1419 YamlError::new(
1420 Diagnostic::new(
1421 DiagnosticKind::Semantic,
1422 "mapping entry has no parent mapping",
1423 before_node.span,
1424 )
1425 .with_expected("a mapping parent"),
1426 )
1427 })?;
1428 if matches!(
1429 self.semantic_kind(mapping),
1430 Some(SemanticKind::Mapping {
1431 style: CollectionStyle::Flow
1432 })
1433 ) {
1434 let fragment = self.typed_value_fragment(value)?;
1435 return self
1436 .queue_mapping_insert_before(mapping, before_entry, key, &fragment)
1437 .map_err(YamlEditError::into_yaml_error);
1438 }
1439 let indent = match style {
1440 MappingEntryStyle::Inherit => self.node_column(before_node),
1441 MappingEntryStyle::Indent(indent) => indent,
1442 };
1443 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1444 let replacement =
1445 self.format_mapping_value_replacement(indent, key, value, comment, false, false)?;
1446
1447 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1448 }
1449
1450 pub fn insert_mapping_entry_ordered_with_comment(
1462 &mut self,
1463 mapping: NodeId,
1464 key: &str,
1465 value: &str,
1466 style: MappingEntryStyle,
1467 comment: Option<&str>,
1468 ordered_keys: &[&str],
1469 ) -> Result<(), YamlError> {
1470 let mut next_entry = None;
1471 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1472 for later_key in &ordered_keys[position + 1..] {
1473 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1474 next_entry = Some(entry);
1475 break;
1476 }
1477 }
1478 }
1479
1480 if let Some(next_entry) = next_entry {
1481 self.insert_mapping_entry_before_with_comment(next_entry, key, value, style, comment)
1482 } else {
1483 self.insert_mapping_entry_with_comment(mapping, key, value, style, comment)
1484 }
1485 }
1486
1487 pub fn remove_mapping_entry(&mut self, mapping: NodeId, key: &str) -> Result<(), YamlError> {
1497 let Some(entry) = self.get_mapping_entry(mapping, key)? else {
1498 return Ok(());
1499 };
1500 self.remove_collection_entries(mapping, &[entry])
1501 }
1502
1503 pub fn retain_mapping_entries(
1514 &mut self,
1515 mapping: NodeId,
1516 allowed_keys: &[&str],
1517 ) -> Result<(), YamlError> {
1518 let mapping_node = self.expect_node(mapping)?;
1519 if !matches!(
1520 mapping_node.kind,
1521 NodeKind::BlockMapping | NodeKind::FlowMapping
1522 ) {
1523 return Err(YamlError::new(
1524 Diagnostic::new(
1525 DiagnosticKind::Semantic,
1526 format!("expected mapping, found {:?}", mapping_node.kind),
1527 mapping_node.span,
1528 )
1529 .with_expected("BlockMapping or FlowMapping"),
1530 ));
1531 }
1532 let mut removals = Vec::new();
1533
1534 for entry in self.children(mapping) {
1535 let entry_node = self.expect_node(entry)?;
1536 if entry_node.kind != NodeKind::MappingEntry {
1537 continue;
1538 }
1539
1540 let Some(key_node) = self.children(entry).next() else {
1541 continue;
1542 };
1543 let key = self.scalar_value(key_node)?;
1544 if !allowed_keys.contains(&key.as_ref()) {
1545 removals.push(entry);
1546 }
1547 }
1548
1549 self.remove_collection_entries(mapping, &removals)
1550 }
1551
1552 pub(crate) fn remove_collection_entries(
1553 &mut self,
1554 collection: NodeId,
1555 removals: &[NodeId],
1556 ) -> Result<(), YamlError> {
1557 if removals.is_empty() {
1558 return Ok(());
1559 }
1560 let Some(style) = (match self.semantic_kind(collection) {
1561 Some(SemanticKind::Mapping { style } | SemanticKind::Sequence { style }) => Some(style),
1562 _ => None,
1563 }) else {
1564 return Err(YamlError::new(
1565 Diagnostic::new(
1566 DiagnosticKind::Semantic,
1567 "collection entry removal target is not a mapping or sequence",
1568 self.expect_node(collection)?.span,
1569 )
1570 .with_expected("a mapping or sequence"),
1571 ));
1572 };
1573 if style == CollectionStyle::Block {
1574 let entries = self
1575 .children(collection)
1576 .filter(|node| self.containing_entry_child(*node))
1577 .collect::<Vec<_>>();
1578 if removals.len() == entries.len() {
1579 let first = self.block_collection_entry_removal_span(collection, entries[0])?;
1580 let last = self.block_collection_entry_removal_span(
1581 collection,
1582 *entries.last().expect("nonempty removals have entries"),
1583 )?;
1584 let span = Span::new(first.start, last.end);
1585 let empty = match self.semantic_kind(collection) {
1586 Some(SemanticKind::Mapping { .. }) => "{}",
1587 Some(SemanticKind::Sequence { .. }) => "[]",
1588 _ => unreachable!(),
1589 };
1590 let (span, replacement) = self
1591 .empty_block_collection_edit(collection, span, empty)
1592 .map_err(crate::YamlEditError::into_yaml_error)?;
1593 return self.queue_edit(span, replacement);
1594 }
1595 for entry in removals {
1596 let span = self.block_collection_entry_removal_span(collection, *entry)?;
1597 self.queue_edit(span, String::new())?;
1598 }
1599 return Ok(());
1600 }
1601
1602 let entries = self
1603 .children(collection)
1604 .filter(|node| {
1605 self.node(*node).is_some_and(|node| {
1606 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1607 })
1608 })
1609 .collect::<Vec<_>>();
1610 if removals.len() == entries.len() {
1611 let collection_node = self.expect_node(collection)?;
1612 let delimiter = match collection_node.kind {
1613 NodeKind::FlowMapping => '}',
1614 NodeKind::FlowSequence => ']',
1615 _ => unreachable!("flow semantic collection must have a flow CST node"),
1616 };
1617 if let Some(relative) = self.source.slice(collection_node.span).rfind(delimiter) {
1618 let close = Span::offset_from_usize(collection_node.span.start, relative);
1619 if let Some(edit) = self
1620 .edits
1621 .iter_mut()
1622 .find(|edit| edit.span == Span::empty(close))
1623 && let Some(replacement) = edit.replacement.strip_prefix(", ")
1624 {
1625 edit.replacement = replacement.to_owned();
1626 }
1627 }
1628 }
1629 let mut index = 0;
1630 while index < entries.len() {
1631 if !removals.contains(&entries[index]) {
1632 index += 1;
1633 continue;
1634 }
1635 let start = index;
1636 while index < entries.len() && removals.contains(&entries[index]) {
1637 index += 1;
1638 }
1639 let end = index;
1640 let first = self.expect_node(entries[start])?.span;
1641 let last = self.expect_node(entries[end - 1])?.span;
1642 let span = if let Some(next) = entries.get(end).copied() {
1643 Span::new(first.start, self.expect_node(next)?.span.start)
1644 } else if start > 0 {
1645 Span::new(self.expect_node(entries[start - 1])?.span.end, last.end)
1646 } else {
1647 Span::new(first.start, last.end)
1648 };
1649 self.queue_edit(span, String::new())?;
1650 }
1651 Ok(())
1652 }
1653
1654 pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1664 let node = self.expect_node(node)?;
1665 let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1666 self.line_span_including_break(node.span)
1667 } else {
1668 node.span
1669 };
1670 self.queue_edit(span, String::new())
1671 }
1672
1673 pub(crate) fn block_collection_entry_removal_span(
1674 &self,
1675 collection: NodeId,
1676 entry: NodeId,
1677 ) -> Result<Span, YamlError> {
1678 let collection_node = self.expect_node(collection)?;
1679 let entry_node = self.expect_node(entry)?;
1680 let entry_kind = match self.semantic_kind(collection) {
1681 Some(SemanticKind::Mapping { .. }) => NodeKind::MappingEntry,
1682 Some(SemanticKind::Sequence { .. }) => NodeKind::SequenceEntry,
1683 _ => {
1684 return Err(YamlError::new(
1685 Diagnostic::new(
1686 DiagnosticKind::Semantic,
1687 "block entry parent is not a semantic collection",
1688 collection_node.span,
1689 )
1690 .with_expected("a block mapping or sequence"),
1691 ));
1692 }
1693 };
1694 let entries = self
1695 .children(collection)
1696 .filter(|child| {
1697 self.node(*child)
1698 .is_some_and(|node| node.kind == entry_kind)
1699 })
1700 .collect::<Vec<_>>();
1701 let index = entries
1702 .iter()
1703 .position(|candidate| *candidate == entry)
1704 .ok_or_else(|| {
1705 YamlError::new(
1706 Diagnostic::new(
1707 DiagnosticKind::Semantic,
1708 "block collection entry is missing from its parent",
1709 entry_node.span,
1710 )
1711 .with_expected("an entry owned by the selected collection"),
1712 )
1713 })?;
1714 let compact_sequence_mapping = entry_kind == NodeKind::MappingEntry
1715 && self
1716 .node(collection)
1717 .and_then(Node::parent)
1718 .and_then(|parent| self.node(parent))
1719 .is_some_and(|parent| parent.kind == NodeKind::SequenceEntry);
1720 if entries.len() == 1 {
1721 return Ok(Span::new(
1722 collection_node.span.start,
1723 Span::usize_to_u32(self.block_entry_extent_end(entries[0])?),
1724 ));
1725 }
1726 let start = if compact_sequence_mapping && index == 0 {
1727 collection_node.span.start as usize
1728 } else {
1729 self.line_start_for_offset(entry_node.span.start as usize)
1730 };
1731 let entry_indent = self.source.as_str()[start..]
1732 .bytes()
1733 .take_while(|byte| *byte == b' ')
1734 .count();
1735 let start = self.attached_block_comment_start(start, entry_indent);
1736 let end = if let Some(next) = entries.get(index + 1).copied() {
1737 if compact_sequence_mapping && index == 0 {
1738 self.collection_entry_content_start(next)?
1739 } else {
1740 let next_start =
1741 self.line_start_for_offset(self.expect_node(next)?.span.start as usize);
1742 let next_indent = self.source.as_str()[next_start..]
1743 .bytes()
1744 .take_while(|byte| *byte == b' ')
1745 .count();
1746 self.attached_block_comment_start(next_start, next_indent)
1747 }
1748 } else {
1749 self.block_entry_extent_end(entry)?
1750 };
1751 Ok(Span::from_usize(start, end))
1752 }
1753
1754 fn attached_block_comment_start(&self, entry_start: usize, entry_indent: usize) -> usize {
1755 let source = self.source.as_str();
1756 let line_starts = self.source.line_starts();
1757 let Ok(mut line_index) = line_starts.binary_search(&Span::usize_to_u32(entry_start)) else {
1758 return entry_start;
1759 };
1760 let mut start = entry_start;
1761
1762 while let Some(previous_index) = line_index.checked_sub(1) {
1763 let line_start = line_starts[previous_index] as usize;
1764 let mut line_end = line_starts[line_index] as usize;
1765 while line_end > line_start && matches!(source.as_bytes()[line_end - 1], b'\r' | b'\n')
1766 {
1767 line_end -= 1;
1768 }
1769 let line = &source[line_start..line_end];
1770 let indent = line.bytes().take_while(|byte| *byte == b' ').count();
1771 if indent != entry_indent || !line[indent..].starts_with('#') {
1772 break;
1773 }
1774 start = line_start;
1775 line_index = previous_index;
1776 }
1777
1778 start
1779 }
1780
1781 pub(crate) fn collection_entry_content_start(&self, entry: NodeId) -> Result<usize, YamlError> {
1782 let entry_node = self.expect_node(entry)?;
1783 Ok(self
1784 .semantic_children(entry)
1785 .next()
1786 .and_then(|child| self.node(child))
1787 .map_or(entry_node.span.start as usize, |child| {
1788 child.span.start as usize
1789 }))
1790 }
1791
1792 fn block_entry_extent_end(&self, entry: NodeId) -> Result<usize, YamlError> {
1793 let source = self.source.as_str();
1794 let entry_node = self.expect_node(entry)?;
1795 let content_start = if entry_node.kind == NodeKind::MappingEntry {
1796 self.collection_entry_content_start(entry)?
1797 } else {
1798 entry_node.span.start as usize
1799 };
1800 let line_start = self.line_start_for_offset(content_start);
1801 let entry_indent = source[line_start..]
1802 .bytes()
1803 .take_while(|byte| *byte == b' ')
1804 .count();
1805 let line_index = self
1806 .source
1807 .line_starts()
1808 .binary_search(&Span::usize_to_u32(line_start))
1809 .expect("entry line start is indexed");
1810 for next_start in self.source.line_starts().iter().skip(line_index + 1) {
1811 let next_start = *next_start as usize;
1812 let line_end = source[next_start..]
1813 .find(['\r', '\n'])
1814 .map_or(source.len(), |relative| next_start + relative);
1815 let line = &source[next_start..line_end];
1816 if line.trim().is_empty() {
1817 continue;
1818 }
1819 let indent = line.bytes().take_while(|byte| *byte == b' ').count();
1820 if indent <= entry_indent {
1821 return Ok(next_start);
1822 }
1823 }
1824 Ok(source.len())
1825 }
1826
1827 pub(crate) fn scalar_replacement_target(
1828 &self,
1829 node: NodeId,
1830 ) -> Result<(Span, ScalarStyle), YamlError> {
1831 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1832 let text = self.source.slice(node.span);
1833 let properties = parse_node_properties(text, node.span)?;
1834 let value_text = &text[properties.value_start()..];
1835 let value_start = Span::offset_from_usize(node.span.start, properties.value_start());
1836
1837 if value_text.starts_with('"') {
1838 let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1839 YamlError::new(
1840 Diagnostic::new(
1841 DiagnosticKind::Emitter,
1842 "could not find the end of the double-quoted scalar",
1843 node.span,
1844 )
1845 .with_expected("a closed double-quoted scalar"),
1846 )
1847 })?;
1848 return Ok((
1849 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1850 ScalarStyle::DoubleQuoted,
1851 ));
1852 }
1853
1854 if value_text.starts_with('\'') {
1855 let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1856 YamlError::new(
1857 Diagnostic::new(
1858 DiagnosticKind::Emitter,
1859 "could not find the end of the single-quoted scalar",
1860 node.span,
1861 )
1862 .with_expected("a closed single-quoted scalar"),
1863 )
1864 })?;
1865 return Ok((
1866 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1867 ScalarStyle::SingleQuoted,
1868 ));
1869 }
1870
1871 let end = plain_scalar_end(value_text);
1872 if end == 0 {
1873 return Err(YamlError::new(
1874 Diagnostic::new(
1875 DiagnosticKind::Emitter,
1876 "could not find plain scalar text to replace",
1877 node.span,
1878 )
1879 .with_expected("plain scalar text"),
1880 ));
1881 }
1882
1883 Ok((
1884 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1885 ScalarStyle::Plain,
1886 ))
1887 }
1888
1889 pub(crate) fn node_value_start(&self, node: NodeId) -> Result<usize, YamlError> {
1890 let node = self.expect_node(node)?;
1891 let properties = parse_node_properties(self.source.slice(node.span), node.span)?;
1892 Ok(node.span.start as usize + properties.value_start())
1893 }
1894
1895 fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1896 self.root()
1897 .into_iter()
1898 .flat_map(|root| self.children(root))
1899 .filter(|node| {
1900 self.node(*node)
1901 .is_some_and(|node| node.kind == NodeKind::Directive)
1902 })
1903 }
1904
1905 fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1906 let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1907 let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1908 let mut parts = body.split_whitespace();
1909 let Some(name) = parts.next() else {
1910 return Err(directive_emit_error(
1911 "directive is missing a name",
1912 node_ref.span,
1913 "%YAML, %TAG, or reserved directive syntax",
1914 )
1915 .with_position_from(&self.source));
1916 };
1917
1918 Ok(match name {
1919 "%YAML" => ParsedDirective::Yaml(YamlDirective {
1920 version: parts.next().unwrap_or_default().to_owned(),
1921 node,
1922 }),
1923 "%TAG" => ParsedDirective::Tag(TagDirective {
1924 handle: parts.next().unwrap_or_default().to_owned(),
1925 prefix: parts.next().unwrap_or_default().to_owned(),
1926 node,
1927 }),
1928 _ => ParsedDirective::Reserved(ReservedDirective {
1929 name: name.to_owned(),
1930 parameters: parts.map(str::to_owned).collect(),
1931 node,
1932 }),
1933 })
1934 }
1935
1936 fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1937 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1938 let text = self.source.slice(node.span);
1939 let end = strip_inline_comment(text)
1940 .trim_end_matches([' ', '\t'])
1941 .len();
1942 Ok(Span::new(
1943 node.span.start,
1944 Span::offset_from_usize(node.span.start, end),
1945 ))
1946 }
1947
1948 fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1949 let insertion_offset = self.directive_insertion_offset();
1950 let mut line = replacement;
1951 line.push_str(self.preferred_line_ending());
1952 self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1953 }
1954
1955 fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1956 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1957 self.queue_edit(self.line_span_including_break(node.span), String::new())
1958 }
1959
1960 fn directive_insertion_offset(&self) -> usize {
1961 if let Some(last_directive) = self
1962 .directive_nodes()
1963 .filter_map(|node| self.node(node))
1964 .max_by_key(|node| node.span.start)
1965 {
1966 return self.line_span_including_break(last_directive.span).end as usize;
1967 }
1968
1969 self.root()
1970 .and_then(|root| self.children(root).next())
1971 .and_then(|node| self.node(node))
1972 .map_or(0, |node| {
1973 self.line_start_for_offset(node.span.start as usize)
1974 })
1975 }
1976
1977 pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1978 self.node(node).ok_or_else(|| {
1979 YamlError::new(Diagnostic::new(
1980 DiagnosticKind::Semantic,
1981 format!("unknown node id {}", node.0),
1982 Span::empty_from_usize(self.source.len()),
1983 ))
1984 })
1985 }
1986
1987 pub(crate) fn expect_node_kind(
1988 &self,
1989 node: NodeId,
1990 expected: NodeKind,
1991 ) -> Result<&Node, YamlError> {
1992 let actual = self.expect_node(node)?;
1993 if actual.kind == expected {
1994 Ok(actual)
1995 } else {
1996 Err(YamlError::new(
1997 Diagnostic::new(
1998 DiagnosticKind::Semantic,
1999 format!("expected {expected:?}, found {:?}", actual.kind),
2000 actual.span,
2001 )
2002 .with_expected(format!("{expected:?}")),
2003 )
2004 .with_position_from(&self.source))
2005 }
2006 }
2007
2008 pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
2009 self.node(value).and_then(Node::parent).filter(|parent| {
2010 self.node(*parent).is_some_and(|node| {
2011 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
2012 })
2013 })
2014 }
2015
2016 fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
2017 let start = self.line_start_for_offset(mapping.span.start as usize);
2018 let end = mapping.span.end as usize;
2019 let text = &self.source.as_str()[start..end];
2020 text.contains("\n\n") || text.contains("\r\n\r\n")
2021 }
2022
2023 fn format_mapping_entry_replacement(
2024 &self,
2025 indent: usize,
2026 key: &str,
2027 value: &str,
2028 comment: Option<&str>,
2029 needs_leading_break: bool,
2030 preserve_paragraph_break: bool,
2031 ) -> Result<String, YamlError> {
2032 validate_plain_mapping_fragment(key, "mapping key")?;
2033 validate_plain_mapping_fragment(value, "mapping value")?;
2034 if let Some(comment) = comment {
2035 validate_yaml_chars(comment)?;
2036 }
2037
2038 let line_ending = self.preferred_line_ending();
2039 let indent_text = " ".repeat(indent);
2040 let mut replacement = String::new();
2041 if needs_leading_break {
2042 replacement.push_str(line_ending);
2043 }
2044 if preserve_paragraph_break {
2045 replacement.push_str(line_ending);
2046 }
2047 if let Some(comment) = comment {
2048 for line in comment.lines() {
2049 replacement.push_str(&indent_text);
2050 replacement.push('#');
2051 if !line.is_empty() {
2052 replacement.push(' ');
2053 replacement.push_str(line.trim());
2054 }
2055 replacement.push_str(line_ending);
2056 }
2057 }
2058 replacement.push_str(&indent_text);
2059 replacement.push_str(key);
2060 replacement.push_str(": ");
2061 replacement.push_str(value);
2062 replacement.push_str(line_ending);
2063 Ok(replacement)
2064 }
2065
2066 fn format_mapping_value_replacement<T>(
2067 &self,
2068 indent: usize,
2069 key: &str,
2070 value: &T,
2071 comment: Option<&str>,
2072 needs_leading_break: bool,
2073 preserve_paragraph_break: bool,
2074 ) -> Result<String, YamlError>
2075 where
2076 T: ToYamlFragment,
2077 {
2078 validate_yaml_chars(key)?;
2079 if let Some(comment) = comment {
2080 validate_yaml_chars(comment)?;
2081 }
2082
2083 let line_ending = self.preferred_line_ending();
2084 let indent_text = " ".repeat(indent);
2085 let child_indent = indent + 2;
2086 let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
2087 let mut replacement = String::new();
2088 if needs_leading_break {
2089 replacement.push_str(line_ending);
2090 }
2091 if preserve_paragraph_break {
2092 replacement.push_str(line_ending);
2093 }
2094 if let Some(comment) = comment {
2095 for line in comment.lines() {
2096 replacement.push_str(&indent_text);
2097 replacement.push('#');
2098 if !line.is_empty() {
2099 replacement.push(' ');
2100 replacement.push_str(line.trim());
2101 }
2102 replacement.push_str(line_ending);
2103 }
2104 }
2105 replacement.push_str(&indent_text);
2106 replacement.push_str(&crate::edit::emit_string_key(key));
2107 if fragment.contains('\n') || fragment.starts_with(' ') {
2108 replacement.push(':');
2109 replacement.push_str(line_ending);
2110 replacement.push_str(&fragment);
2111 } else {
2112 replacement.push_str(": ");
2113 replacement.push_str(&fragment);
2114 }
2115 replacement.push_str(line_ending);
2116 Ok(replacement)
2117 }
2118
2119 pub(crate) fn node_indent(&self, node: &Node) -> usize {
2120 let line_start = self.line_start_for_offset(node.span.start as usize);
2121 self.source.as_str()[line_start..node.span.start as usize]
2122 .bytes()
2123 .filter(|byte| *byte == b' ')
2124 .count()
2125 }
2126
2127 pub(crate) fn block_mapping_entry_indent(&self, mapping: NodeId) -> usize {
2128 self.mapping_entries(mapping)
2129 .next()
2130 .and_then(|(key, _)| self.node(key).map(|node| self.node_column(node)))
2131 .or_else(|| self.node(mapping).map(|node| self.node_column(node)))
2132 .unwrap_or_default()
2133 }
2134
2135 fn node_column(&self, node: &Node) -> usize {
2136 node.span.start as usize - self.line_start_for_offset(node.span.start as usize)
2137 }
2138
2139 fn line_start_for_offset(&self, offset: usize) -> usize {
2140 let offset = Span::usize_to_u32(offset);
2141 match self.source.line_starts().binary_search(&offset) {
2142 Ok(index) => self.source.line_starts()[index] as usize,
2143 Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
2144 }
2145 }
2146
2147 pub(crate) fn find_nested_collection_after(
2148 &self,
2149 entry: &Node,
2150 parent_indent: usize,
2151 ) -> Option<NodeId> {
2152 self.nodes
2153 .iter()
2154 .enumerate()
2155 .filter(|(_, node)| {
2156 matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
2157 && node.span.start >= entry.span.end
2158 && self.node_indent(node) > parent_indent
2159 })
2160 .min_by_key(|(_, node)| node.span.start)
2161 .map(|(index, _)| NodeId::from_usize(index))
2162 }
2163
2164 pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
2165 let text = self.source.slice(scalar.span);
2166 let header_end = text.find(['\r', '\n'])?;
2167 let mut rest = &text[header_end..];
2168 while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
2169 rest = stripped;
2170 }
2171 for line in rest.lines() {
2172 if line.trim().is_empty() {
2173 continue;
2174 }
2175 return Some(line.bytes().take_while(|byte| *byte == b' ').count());
2176 }
2177 None
2178 }
2179
2180 pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
2181 self.source.try_slice(span)?;
2182 validate_yaml_chars(&replacement)?;
2183
2184 if span.is_empty()
2185 && let Some(existing) = self
2186 .edits
2187 .iter_mut()
2188 .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
2189 {
2190 existing.replacement.push_str(&replacement);
2191 return Ok(());
2192 }
2193
2194 if let Some(existing) = self
2195 .edits
2196 .iter()
2197 .find(|edit| edits_conflict(edit.span, span))
2198 {
2199 return Err(YamlError::new(
2200 Diagnostic::new(
2201 DiagnosticKind::Emitter,
2202 "edit overlaps an existing pending edit",
2203 span,
2204 )
2205 .with_note(format!(
2206 "existing edit covers bytes {}..{}",
2207 existing.span.start, existing.span.end
2208 )),
2209 )
2210 .with_position_from(&self.source));
2211 }
2212
2213 self.edits.push(Edit { span, replacement });
2214 Ok(())
2215 }
2216
2217 pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2218 node_link(mapping.last_child)
2219 .and_then(|child| self.node(child))
2220 .map_or(mapping.span.end as usize, |last_child| {
2221 self.line_span_including_break(last_child.span).end as usize
2222 })
2223 }
2224
2225 pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2226 node_link(sequence.last_child)
2227 .and_then(|child| self.node(child))
2228 .map_or(sequence.span.end as usize, |last_child| {
2229 self.line_span_including_break(last_child.span).end as usize
2230 })
2231 }
2232
2233 fn line_span_including_break(&self, span: Span) -> Span {
2234 let start = self.line_start_for_offset(span.start as usize);
2235 let mut end = span.end as usize;
2236 let bytes = self.source.as_str().as_bytes();
2237
2238 if end < bytes.len() {
2239 if bytes[end] == b'\r' {
2240 end += 1;
2241 if end < bytes.len() && bytes[end] == b'\n' {
2242 end += 1;
2243 }
2244 } else if bytes[end] == b'\n' {
2245 end += 1;
2246 }
2247 }
2248
2249 Span::from_usize(start, end)
2250 }
2251
2252 pub(crate) fn preferred_line_ending(&self) -> &str {
2253 let bytes = self.source.as_str().as_bytes();
2254 for (index, byte) in bytes.iter().enumerate() {
2255 if *byte == b'\r' {
2256 return if bytes.get(index + 1) == Some(&b'\n') {
2257 "\r\n"
2258 } else {
2259 "\r"
2260 };
2261 }
2262 if *byte == b'\n' {
2263 return if index > 0 && bytes[index - 1] == b'\r' {
2264 "\r\n"
2265 } else {
2266 "\n"
2267 };
2268 }
2269 }
2270 "\n"
2271 }
2272
2273 fn source_ends_with_line_break(&self) -> bool {
2274 self.source
2275 .as_str()
2276 .as_bytes()
2277 .last()
2278 .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2279 }
2280
2281 fn document_append_prefix(&self, line_ending: &str) -> String {
2282 if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2283 String::new()
2284 } else {
2285 line_ending.to_owned()
2286 }
2287 }
2288}
2289impl fmt::Display for YamlDoc {
2290 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2291 if self.edits.is_empty() {
2292 return formatter.write_str(self.source.as_str());
2293 }
2294
2295 let mut output = self.source.as_str().to_owned();
2296 let mut edits = self.edits.clone();
2297 edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2298
2299 for edit in edits {
2300 output.replace_range(
2301 edit.span.start as usize..edit.span.end as usize,
2302 &edit.replacement,
2303 );
2304 }
2305
2306 formatter.write_str(&output)
2307 }
2308}