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 for entry in removals {
1575 self.remove_node(*entry)?;
1576 }
1577 return Ok(());
1578 }
1579
1580 let entries = self
1581 .children(collection)
1582 .filter(|node| {
1583 self.node(*node).is_some_and(|node| {
1584 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1585 })
1586 })
1587 .collect::<Vec<_>>();
1588 if removals.len() == entries.len() {
1589 let collection_node = self.expect_node(collection)?;
1590 let delimiter = match collection_node.kind {
1591 NodeKind::FlowMapping => '}',
1592 NodeKind::FlowSequence => ']',
1593 _ => unreachable!("flow semantic collection must have a flow CST node"),
1594 };
1595 if let Some(relative) = self.source.slice(collection_node.span).rfind(delimiter) {
1596 let close = Span::offset_from_usize(collection_node.span.start, relative);
1597 if let Some(edit) = self
1598 .edits
1599 .iter_mut()
1600 .find(|edit| edit.span == Span::empty(close))
1601 && let Some(replacement) = edit.replacement.strip_prefix(", ")
1602 {
1603 edit.replacement = replacement.to_owned();
1604 }
1605 }
1606 }
1607 let mut index = 0;
1608 while index < entries.len() {
1609 if !removals.contains(&entries[index]) {
1610 index += 1;
1611 continue;
1612 }
1613 let start = index;
1614 while index < entries.len() && removals.contains(&entries[index]) {
1615 index += 1;
1616 }
1617 let end = index;
1618 let first = self.expect_node(entries[start])?.span;
1619 let last = self.expect_node(entries[end - 1])?.span;
1620 let span = if let Some(next) = entries.get(end).copied() {
1621 Span::new(first.start, self.expect_node(next)?.span.start)
1622 } else if start > 0 {
1623 Span::new(self.expect_node(entries[start - 1])?.span.end, last.end)
1624 } else {
1625 Span::new(first.start, last.end)
1626 };
1627 self.queue_edit(span, String::new())?;
1628 }
1629 Ok(())
1630 }
1631
1632 pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1642 let node = self.expect_node(node)?;
1643 let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1644 self.line_span_including_break(node.span)
1645 } else {
1646 node.span
1647 };
1648 self.queue_edit(span, String::new())
1649 }
1650
1651 pub(crate) fn block_collection_entry_removal_span(
1652 &self,
1653 collection: NodeId,
1654 entry: NodeId,
1655 ) -> Result<Span, YamlError> {
1656 let collection_node = self.expect_node(collection)?;
1657 let entry_node = self.expect_node(entry)?;
1658 let entry_kind = match self.semantic_kind(collection) {
1659 Some(SemanticKind::Mapping { .. }) => NodeKind::MappingEntry,
1660 Some(SemanticKind::Sequence { .. }) => NodeKind::SequenceEntry,
1661 _ => {
1662 return Err(YamlError::new(
1663 Diagnostic::new(
1664 DiagnosticKind::Semantic,
1665 "block entry parent is not a semantic collection",
1666 collection_node.span,
1667 )
1668 .with_expected("a block mapping or sequence"),
1669 ));
1670 }
1671 };
1672 let entries = self
1673 .children(collection)
1674 .filter(|child| {
1675 self.node(*child)
1676 .is_some_and(|node| node.kind == entry_kind)
1677 })
1678 .collect::<Vec<_>>();
1679 let index = entries
1680 .iter()
1681 .position(|candidate| *candidate == entry)
1682 .ok_or_else(|| {
1683 YamlError::new(
1684 Diagnostic::new(
1685 DiagnosticKind::Semantic,
1686 "block collection entry is missing from its parent",
1687 entry_node.span,
1688 )
1689 .with_expected("an entry owned by the selected collection"),
1690 )
1691 })?;
1692 let compact_sequence_mapping = entry_kind == NodeKind::MappingEntry
1693 && self
1694 .node(collection)
1695 .and_then(Node::parent)
1696 .and_then(|parent| self.node(parent))
1697 .is_some_and(|parent| parent.kind == NodeKind::SequenceEntry);
1698 if entries.len() == 1 {
1699 return Ok(Span::new(
1700 collection_node.span.start,
1701 Span::usize_to_u32(self.block_entry_extent_end(entries[0])?),
1702 ));
1703 }
1704 let start = if compact_sequence_mapping && index == 0 {
1705 collection_node.span.start as usize
1706 } else {
1707 self.line_start_for_offset(entry_node.span.start as usize)
1708 };
1709 let entry_indent = self.source.as_str()[start..]
1710 .bytes()
1711 .take_while(|byte| *byte == b' ')
1712 .count();
1713 let start = self.attached_block_comment_start(start, entry_indent);
1714 let end = if let Some(next) = entries.get(index + 1).copied() {
1715 if compact_sequence_mapping && index == 0 {
1716 self.collection_entry_content_start(next)?
1717 } else {
1718 let next_start =
1719 self.line_start_for_offset(self.expect_node(next)?.span.start as usize);
1720 let next_indent = self.source.as_str()[next_start..]
1721 .bytes()
1722 .take_while(|byte| *byte == b' ')
1723 .count();
1724 self.attached_block_comment_start(next_start, next_indent)
1725 }
1726 } else {
1727 self.block_entry_extent_end(entry)?
1728 };
1729 Ok(Span::from_usize(start, end))
1730 }
1731
1732 fn attached_block_comment_start(&self, entry_start: usize, entry_indent: usize) -> usize {
1733 let source = self.source.as_str();
1734 let line_starts = self.source.line_starts();
1735 let Ok(mut line_index) = line_starts.binary_search(&Span::usize_to_u32(entry_start)) else {
1736 return entry_start;
1737 };
1738 let mut start = entry_start;
1739
1740 while let Some(previous_index) = line_index.checked_sub(1) {
1741 let line_start = line_starts[previous_index] as usize;
1742 let mut line_end = line_starts[line_index] as usize;
1743 while line_end > line_start && matches!(source.as_bytes()[line_end - 1], b'\r' | b'\n')
1744 {
1745 line_end -= 1;
1746 }
1747 let line = &source[line_start..line_end];
1748 let indent = line.bytes().take_while(|byte| *byte == b' ').count();
1749 if indent != entry_indent || !line[indent..].starts_with('#') {
1750 break;
1751 }
1752 start = line_start;
1753 line_index = previous_index;
1754 }
1755
1756 start
1757 }
1758
1759 pub(crate) fn collection_entry_content_start(&self, entry: NodeId) -> Result<usize, YamlError> {
1760 let entry_node = self.expect_node(entry)?;
1761 Ok(self
1762 .semantic_children(entry)
1763 .next()
1764 .and_then(|child| self.node(child))
1765 .map_or(entry_node.span.start as usize, |child| {
1766 child.span.start as usize
1767 }))
1768 }
1769
1770 fn block_entry_extent_end(&self, entry: NodeId) -> Result<usize, YamlError> {
1771 let source = self.source.as_str();
1772 let entry_node = self.expect_node(entry)?;
1773 let content_start = if entry_node.kind == NodeKind::MappingEntry {
1774 self.collection_entry_content_start(entry)?
1775 } else {
1776 entry_node.span.start as usize
1777 };
1778 let line_start = self.line_start_for_offset(content_start);
1779 let entry_indent = source[line_start..]
1780 .bytes()
1781 .take_while(|byte| *byte == b' ')
1782 .count();
1783 let line_index = self
1784 .source
1785 .line_starts()
1786 .binary_search(&Span::usize_to_u32(line_start))
1787 .expect("entry line start is indexed");
1788 for next_start in self.source.line_starts().iter().skip(line_index + 1) {
1789 let next_start = *next_start as usize;
1790 let line_end = source[next_start..]
1791 .find(['\r', '\n'])
1792 .map_or(source.len(), |relative| next_start + relative);
1793 let line = &source[next_start..line_end];
1794 if line.trim().is_empty() {
1795 continue;
1796 }
1797 let indent = line.bytes().take_while(|byte| *byte == b' ').count();
1798 if indent <= entry_indent {
1799 return Ok(next_start);
1800 }
1801 }
1802 Ok(source.len())
1803 }
1804
1805 pub(crate) fn scalar_replacement_target(
1806 &self,
1807 node: NodeId,
1808 ) -> Result<(Span, ScalarStyle), YamlError> {
1809 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1810 let text = self.source.slice(node.span);
1811 let properties = parse_node_properties(text, node.span)?;
1812 let value_text = &text[properties.value_start()..];
1813 let value_start = Span::offset_from_usize(node.span.start, properties.value_start());
1814
1815 if value_text.starts_with('"') {
1816 let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1817 YamlError::new(
1818 Diagnostic::new(
1819 DiagnosticKind::Emitter,
1820 "could not find the end of the double-quoted scalar",
1821 node.span,
1822 )
1823 .with_expected("a closed double-quoted scalar"),
1824 )
1825 })?;
1826 return Ok((
1827 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1828 ScalarStyle::DoubleQuoted,
1829 ));
1830 }
1831
1832 if value_text.starts_with('\'') {
1833 let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1834 YamlError::new(
1835 Diagnostic::new(
1836 DiagnosticKind::Emitter,
1837 "could not find the end of the single-quoted scalar",
1838 node.span,
1839 )
1840 .with_expected("a closed single-quoted scalar"),
1841 )
1842 })?;
1843 return Ok((
1844 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1845 ScalarStyle::SingleQuoted,
1846 ));
1847 }
1848
1849 let end = plain_scalar_end(value_text);
1850 if end == 0 {
1851 return Err(YamlError::new(
1852 Diagnostic::new(
1853 DiagnosticKind::Emitter,
1854 "could not find plain scalar text to replace",
1855 node.span,
1856 )
1857 .with_expected("plain scalar text"),
1858 ));
1859 }
1860
1861 Ok((
1862 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1863 ScalarStyle::Plain,
1864 ))
1865 }
1866
1867 pub(crate) fn node_value_start(&self, node: NodeId) -> Result<usize, YamlError> {
1868 let node = self.expect_node(node)?;
1869 let properties = parse_node_properties(self.source.slice(node.span), node.span)?;
1870 Ok(node.span.start as usize + properties.value_start())
1871 }
1872
1873 fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1874 self.root()
1875 .into_iter()
1876 .flat_map(|root| self.children(root))
1877 .filter(|node| {
1878 self.node(*node)
1879 .is_some_and(|node| node.kind == NodeKind::Directive)
1880 })
1881 }
1882
1883 fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1884 let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1885 let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1886 let mut parts = body.split_whitespace();
1887 let Some(name) = parts.next() else {
1888 return Err(directive_emit_error(
1889 "directive is missing a name",
1890 node_ref.span,
1891 "%YAML, %TAG, or reserved directive syntax",
1892 )
1893 .with_position_from(&self.source));
1894 };
1895
1896 Ok(match name {
1897 "%YAML" => ParsedDirective::Yaml(YamlDirective {
1898 version: parts.next().unwrap_or_default().to_owned(),
1899 node,
1900 }),
1901 "%TAG" => ParsedDirective::Tag(TagDirective {
1902 handle: parts.next().unwrap_or_default().to_owned(),
1903 prefix: parts.next().unwrap_or_default().to_owned(),
1904 node,
1905 }),
1906 _ => ParsedDirective::Reserved(ReservedDirective {
1907 name: name.to_owned(),
1908 parameters: parts.map(str::to_owned).collect(),
1909 node,
1910 }),
1911 })
1912 }
1913
1914 fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1915 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1916 let text = self.source.slice(node.span);
1917 let end = strip_inline_comment(text)
1918 .trim_end_matches([' ', '\t'])
1919 .len();
1920 Ok(Span::new(
1921 node.span.start,
1922 Span::offset_from_usize(node.span.start, end),
1923 ))
1924 }
1925
1926 fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1927 let insertion_offset = self.directive_insertion_offset();
1928 let mut line = replacement;
1929 line.push_str(self.preferred_line_ending());
1930 self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1931 }
1932
1933 fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1934 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1935 self.queue_edit(self.line_span_including_break(node.span), String::new())
1936 }
1937
1938 fn directive_insertion_offset(&self) -> usize {
1939 if let Some(last_directive) = self
1940 .directive_nodes()
1941 .filter_map(|node| self.node(node))
1942 .max_by_key(|node| node.span.start)
1943 {
1944 return self.line_span_including_break(last_directive.span).end as usize;
1945 }
1946
1947 self.root()
1948 .and_then(|root| self.children(root).next())
1949 .and_then(|node| self.node(node))
1950 .map_or(0, |node| {
1951 self.line_start_for_offset(node.span.start as usize)
1952 })
1953 }
1954
1955 pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1956 self.node(node).ok_or_else(|| {
1957 YamlError::new(Diagnostic::new(
1958 DiagnosticKind::Semantic,
1959 format!("unknown node id {}", node.0),
1960 Span::empty_from_usize(self.source.len()),
1961 ))
1962 })
1963 }
1964
1965 pub(crate) fn expect_node_kind(
1966 &self,
1967 node: NodeId,
1968 expected: NodeKind,
1969 ) -> Result<&Node, YamlError> {
1970 let actual = self.expect_node(node)?;
1971 if actual.kind == expected {
1972 Ok(actual)
1973 } else {
1974 Err(YamlError::new(
1975 Diagnostic::new(
1976 DiagnosticKind::Semantic,
1977 format!("expected {expected:?}, found {:?}", actual.kind),
1978 actual.span,
1979 )
1980 .with_expected(format!("{expected:?}")),
1981 )
1982 .with_position_from(&self.source))
1983 }
1984 }
1985
1986 pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1987 self.node(value).and_then(Node::parent).filter(|parent| {
1988 self.node(*parent).is_some_and(|node| {
1989 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1990 })
1991 })
1992 }
1993
1994 fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1995 let start = self.line_start_for_offset(mapping.span.start as usize);
1996 let end = mapping.span.end as usize;
1997 let text = &self.source.as_str()[start..end];
1998 text.contains("\n\n") || text.contains("\r\n\r\n")
1999 }
2000
2001 fn format_mapping_entry_replacement(
2002 &self,
2003 indent: usize,
2004 key: &str,
2005 value: &str,
2006 comment: Option<&str>,
2007 needs_leading_break: bool,
2008 preserve_paragraph_break: bool,
2009 ) -> Result<String, YamlError> {
2010 validate_plain_mapping_fragment(key, "mapping key")?;
2011 validate_plain_mapping_fragment(value, "mapping value")?;
2012 if let Some(comment) = comment {
2013 validate_yaml_chars(comment)?;
2014 }
2015
2016 let line_ending = self.preferred_line_ending();
2017 let indent_text = " ".repeat(indent);
2018 let mut replacement = String::new();
2019 if needs_leading_break {
2020 replacement.push_str(line_ending);
2021 }
2022 if preserve_paragraph_break {
2023 replacement.push_str(line_ending);
2024 }
2025 if let Some(comment) = comment {
2026 for line in comment.lines() {
2027 replacement.push_str(&indent_text);
2028 replacement.push('#');
2029 if !line.is_empty() {
2030 replacement.push(' ');
2031 replacement.push_str(line.trim());
2032 }
2033 replacement.push_str(line_ending);
2034 }
2035 }
2036 replacement.push_str(&indent_text);
2037 replacement.push_str(key);
2038 replacement.push_str(": ");
2039 replacement.push_str(value);
2040 replacement.push_str(line_ending);
2041 Ok(replacement)
2042 }
2043
2044 fn format_mapping_value_replacement<T>(
2045 &self,
2046 indent: usize,
2047 key: &str,
2048 value: &T,
2049 comment: Option<&str>,
2050 needs_leading_break: bool,
2051 preserve_paragraph_break: bool,
2052 ) -> Result<String, YamlError>
2053 where
2054 T: ToYamlFragment,
2055 {
2056 validate_yaml_chars(key)?;
2057 if let Some(comment) = comment {
2058 validate_yaml_chars(comment)?;
2059 }
2060
2061 let line_ending = self.preferred_line_ending();
2062 let indent_text = " ".repeat(indent);
2063 let child_indent = indent + 2;
2064 let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
2065 let mut replacement = String::new();
2066 if needs_leading_break {
2067 replacement.push_str(line_ending);
2068 }
2069 if preserve_paragraph_break {
2070 replacement.push_str(line_ending);
2071 }
2072 if let Some(comment) = comment {
2073 for line in comment.lines() {
2074 replacement.push_str(&indent_text);
2075 replacement.push('#');
2076 if !line.is_empty() {
2077 replacement.push(' ');
2078 replacement.push_str(line.trim());
2079 }
2080 replacement.push_str(line_ending);
2081 }
2082 }
2083 replacement.push_str(&indent_text);
2084 replacement.push_str(&crate::edit::emit_string_key(key));
2085 if fragment.contains('\n') || fragment.starts_with(' ') {
2086 replacement.push(':');
2087 replacement.push_str(line_ending);
2088 replacement.push_str(&fragment);
2089 } else {
2090 replacement.push_str(": ");
2091 replacement.push_str(&fragment);
2092 }
2093 replacement.push_str(line_ending);
2094 Ok(replacement)
2095 }
2096
2097 pub(crate) fn node_indent(&self, node: &Node) -> usize {
2098 let line_start = self.line_start_for_offset(node.span.start as usize);
2099 self.source.as_str()[line_start..node.span.start as usize]
2100 .bytes()
2101 .filter(|byte| *byte == b' ')
2102 .count()
2103 }
2104
2105 pub(crate) fn block_mapping_entry_indent(&self, mapping: NodeId) -> usize {
2106 self.mapping_entries(mapping)
2107 .next()
2108 .and_then(|(key, _)| self.node(key).map(|node| self.node_column(node)))
2109 .or_else(|| self.node(mapping).map(|node| self.node_column(node)))
2110 .unwrap_or_default()
2111 }
2112
2113 fn node_column(&self, node: &Node) -> usize {
2114 node.span.start as usize - self.line_start_for_offset(node.span.start as usize)
2115 }
2116
2117 fn line_start_for_offset(&self, offset: usize) -> usize {
2118 let offset = Span::usize_to_u32(offset);
2119 match self.source.line_starts().binary_search(&offset) {
2120 Ok(index) => self.source.line_starts()[index] as usize,
2121 Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
2122 }
2123 }
2124
2125 pub(crate) fn find_nested_collection_after(
2126 &self,
2127 entry: &Node,
2128 parent_indent: usize,
2129 ) -> Option<NodeId> {
2130 self.nodes
2131 .iter()
2132 .enumerate()
2133 .filter(|(_, node)| {
2134 matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
2135 && node.span.start >= entry.span.end
2136 && self.node_indent(node) > parent_indent
2137 })
2138 .min_by_key(|(_, node)| node.span.start)
2139 .map(|(index, _)| NodeId::from_usize(index))
2140 }
2141
2142 pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
2143 let text = self.source.slice(scalar.span);
2144 let header_end = text.find(['\r', '\n'])?;
2145 let mut rest = &text[header_end..];
2146 while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
2147 rest = stripped;
2148 }
2149 for line in rest.lines() {
2150 if line.trim().is_empty() {
2151 continue;
2152 }
2153 return Some(line.bytes().take_while(|byte| *byte == b' ').count());
2154 }
2155 None
2156 }
2157
2158 pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
2159 self.source.try_slice(span)?;
2160 validate_yaml_chars(&replacement)?;
2161
2162 if span.is_empty()
2163 && let Some(existing) = self
2164 .edits
2165 .iter_mut()
2166 .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
2167 {
2168 existing.replacement.push_str(&replacement);
2169 return Ok(());
2170 }
2171
2172 if let Some(existing) = self
2173 .edits
2174 .iter()
2175 .find(|edit| edits_conflict(edit.span, span))
2176 {
2177 return Err(YamlError::new(
2178 Diagnostic::new(
2179 DiagnosticKind::Emitter,
2180 "edit overlaps an existing pending edit",
2181 span,
2182 )
2183 .with_note(format!(
2184 "existing edit covers bytes {}..{}",
2185 existing.span.start, existing.span.end
2186 )),
2187 )
2188 .with_position_from(&self.source));
2189 }
2190
2191 self.edits.push(Edit { span, replacement });
2192 Ok(())
2193 }
2194
2195 pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2196 node_link(mapping.last_child)
2197 .and_then(|child| self.node(child))
2198 .map_or(mapping.span.end as usize, |last_child| {
2199 self.line_span_including_break(last_child.span).end as usize
2200 })
2201 }
2202
2203 pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2204 node_link(sequence.last_child)
2205 .and_then(|child| self.node(child))
2206 .map_or(sequence.span.end as usize, |last_child| {
2207 self.line_span_including_break(last_child.span).end as usize
2208 })
2209 }
2210
2211 fn line_span_including_break(&self, span: Span) -> Span {
2212 let start = self.line_start_for_offset(span.start as usize);
2213 let mut end = span.end as usize;
2214 let bytes = self.source.as_str().as_bytes();
2215
2216 if end < bytes.len() {
2217 if bytes[end] == b'\r' {
2218 end += 1;
2219 if end < bytes.len() && bytes[end] == b'\n' {
2220 end += 1;
2221 }
2222 } else if bytes[end] == b'\n' {
2223 end += 1;
2224 }
2225 }
2226
2227 Span::from_usize(start, end)
2228 }
2229
2230 pub(crate) fn preferred_line_ending(&self) -> &str {
2231 let bytes = self.source.as_str().as_bytes();
2232 for (index, byte) in bytes.iter().enumerate() {
2233 if *byte == b'\r' {
2234 return if bytes.get(index + 1) == Some(&b'\n') {
2235 "\r\n"
2236 } else {
2237 "\r"
2238 };
2239 }
2240 if *byte == b'\n' {
2241 return if index > 0 && bytes[index - 1] == b'\r' {
2242 "\r\n"
2243 } else {
2244 "\n"
2245 };
2246 }
2247 }
2248 "\n"
2249 }
2250
2251 fn source_ends_with_line_break(&self) -> bool {
2252 self.source
2253 .as_str()
2254 .as_bytes()
2255 .last()
2256 .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2257 }
2258
2259 fn document_append_prefix(&self, line_ending: &str) -> String {
2260 if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2261 String::new()
2262 } else {
2263 line_ending.to_owned()
2264 }
2265 }
2266}
2267impl fmt::Display for YamlDoc {
2268 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2269 if self.edits.is_empty() {
2270 return formatter.write_str(self.source.as_str());
2271 }
2272
2273 let mut output = self.source.as_str().to_owned();
2274 let mut edits = self.edits.clone();
2275 edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2276
2277 for edit in edits {
2278 output.replace_range(
2279 edit.span.start as usize..edit.span.end as usize,
2280 &edit.replacement,
2281 );
2282 }
2283
2284 formatter.write_str(&output)
2285 }
2286}