1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fmt;
4
5use crate::syntax::node_link;
6use crate::{
7 Children, CollectionStyle, CollectionTarget, Diagnostic, DiagnosticKind, FromYamlDoc, Node,
8 NodeId, NodeKind, Parser, ScalarStyle, SemanticKind, SemanticStore, Source, Span, ToYamlDoc,
9 ToYamlFragment, Token, YamlError, YamlEvent, decode_scalar_value_with_content_indent,
10 directive_emit_error, double_quoted_scalar_end, edits_conflict, events_to_test_string,
11 format_scalar_value, lex, next_line_content_start, parse_node_properties, plain_scalar_end,
12 resolve_tag, single_quoted_scalar_end, strip_inline_comment, validate_plain_mapping_fragment,
13 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.semantics.get(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.semantics.get(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.semantics.get(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.semantics.get(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.semantics.get(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 .semantics
226 .properties(node)
227 .and_then(|properties| properties.content_indent),
228 }),
229 SemanticKind::Alias => Some(YamlEvent {
230 kind: crate::YamlEventKind::Alias {
231 name: self.doc.alias_name(node).unwrap_or_default().to_owned(),
232 },
233 span,
234 cst: Some(node),
235 content_indent: None,
236 }),
237 }
238 }
239
240 fn schedule_mapping_entry(&mut self, next: u32) {
241 let Some(entry) = node_link(next) else {
242 return;
243 };
244 self.tasks.push(EventTask::MappingEntries(
245 self.doc.nodes[entry.as_usize()].next_sibling,
246 ));
247 if self.doc.nodes[entry.as_usize()].kind != NodeKind::MappingEntry {
248 return;
249 }
250 let Some(key) = self.first_semantic_child(entry) else {
251 return;
252 };
253 let Some(value) = self.next_semantic_sibling(key) else {
254 return;
255 };
256 self.tasks.push(EventTask::Node(value));
257 self.tasks.push(EventTask::Node(key));
258 }
259
260 fn schedule_sequence_entry(&mut self, next: u32) {
261 let Some(entry) = node_link(next) else {
262 return;
263 };
264 self.tasks.push(EventTask::SequenceEntries(
265 self.doc.nodes[entry.as_usize()].next_sibling,
266 ));
267 if self.doc.nodes[entry.as_usize()].kind != NodeKind::SequenceEntry {
268 return;
269 }
270 if let Some(item) = self.first_semantic_child(entry) {
271 self.tasks.push(EventTask::Node(item));
272 }
273 }
274
275 fn first_semantic_child(&self, parent: NodeId) -> Option<NodeId> {
276 let next = self.doc.nodes[parent.as_usize()].first_child;
277 self.next_semantic(next)
278 }
279
280 fn next_semantic_sibling(&self, node: NodeId) -> Option<NodeId> {
281 let next = self.doc.nodes[node.as_usize()].next_sibling;
282 self.next_semantic(next)
283 }
284
285 fn next_semantic(&self, mut next: u32) -> Option<NodeId> {
286 while let Some(node) = node_link(next) {
287 if self.doc.semantics.get(node).is_some() {
288 return Some(node);
289 }
290 next = self.doc.nodes[node.as_usize()].next_sibling;
291 }
292 None
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct YamlDirective {
299 pub version: String,
301 pub node: NodeId,
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct TagDirective {
308 pub handle: String,
310 pub prefix: String,
312 pub node: NodeId,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct ReservedDirective {
319 pub name: String,
321 pub parameters: Vec<String>,
323 pub node: NodeId,
325}
326
327#[derive(Debug, Clone, PartialEq, Eq)]
328enum ParsedDirective {
329 Yaml(YamlDirective),
330 Tag(TagDirective),
331 Reserved(ReservedDirective),
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
336pub enum MappingEntryStyle {
337 #[default]
339 Inherit,
340 Indent(usize),
342}
343
344#[derive(Debug, Clone, PartialEq, Eq)]
346pub struct YamlDoc {
347 pub(crate) source: Source,
349 pub(crate) nodes: Vec<Node>,
351 pub(crate) semantics: SemanticStore,
353 pub(crate) root_override: Option<NodeId>,
355 pub(crate) edits: Vec<Edit>,
357}
358
359impl YamlDoc {
360 pub fn parse(input: &str) -> Result<Self, YamlError> {
370 Self::parse_owned(input.to_owned())
371 }
372
373 pub fn parse_owned(input: String) -> Result<Self, YamlError> {
380 let source = Source::new(input)?;
381 let parsed = Parser::new(&source)
382 .parse()
383 .map_err(|error| error.with_position_from(&source))?;
384 Ok(Self {
385 source,
386 nodes: parsed.nodes,
387 semantics: parsed.semantics,
388 root_override: None,
389 edits: Vec::new(),
390 })
391 }
392
393 pub fn commit_edits(&mut self) -> Result<(), YamlError> {
405 if self.edits.is_empty() {
406 return Ok(());
407 }
408
409 let edited = self.to_string();
410 *self = Self::parse(&edited)?;
411 Ok(())
412 }
413
414 #[must_use]
416 pub fn as_source(&self) -> &str {
417 self.source.as_str()
418 }
419
420 #[must_use]
422 pub const fn source(&self) -> &Source {
423 &self.source
424 }
425
426 pub fn tokens(&self) -> Result<Vec<Token>, YamlError> {
435 lex(&self.source).map_err(|error| error.with_position_from(&self.source))
436 }
437
438 #[must_use]
440 pub fn root(&self) -> Option<NodeId> {
441 (!self.nodes.is_empty()).then_some(NodeId(0))
442 }
443
444 #[must_use]
446 pub fn events(&self) -> YamlEvents<'_> {
447 let mut tasks = Vec::with_capacity(8);
448 tasks.push(EventTask::StreamEnd);
449 tasks.push(EventTask::Documents(0));
450 tasks.push(EventTask::StreamStart);
451 YamlEvents { doc: self, tasks }
452 }
453
454 #[must_use]
456 pub fn events_to_test_string(&self) -> String {
457 events_to_test_string(self.events())
458 }
459
460 #[must_use]
462 pub fn document_count(&self) -> usize {
463 self.root_override
464 .map_or(self.semantics.documents.len(), |_| 1)
465 }
466
467 pub fn append_document<T>(&mut self, value: &T) -> Result<(), YamlError>
477 where
478 T: ToYamlFragment,
479 {
480 let line_ending = self.preferred_line_ending();
481 let mut replacement = self.document_append_prefix(line_ending);
482 replacement.push_str("---");
483 replacement.push_str(line_ending);
484 let fragment = value.to_yaml_fragment(0, line_ending)?;
485 replacement.push_str(&fragment);
486 replacement.push_str(line_ending);
487 self.queue_edit(Span::empty_from_usize(self.source.len()), replacement)
488 }
489
490 pub fn append_empty_mapping_document(&mut self) -> Result<(), YamlError> {
497 self.append_document(&std::collections::BTreeMap::<String, String>::new())
498 }
499
500 #[must_use]
502 pub fn yaml_directive(&self) -> Option<YamlDirective> {
503 self.directive_nodes()
504 .filter_map(|node| self.parse_directive_node(node).ok())
505 .find_map(|directive| match directive {
506 ParsedDirective::Yaml(directive) => Some(directive),
507 ParsedDirective::Tag(_) | ParsedDirective::Reserved(_) => None,
508 })
509 }
510
511 #[must_use]
513 pub fn tag_directives(&self) -> Vec<TagDirective> {
514 self.directive_nodes()
515 .filter_map(|node| self.parse_directive_node(node).ok())
516 .filter_map(|directive| match directive {
517 ParsedDirective::Tag(directive) => Some(directive),
518 ParsedDirective::Yaml(_) | ParsedDirective::Reserved(_) => None,
519 })
520 .collect()
521 }
522
523 #[must_use]
525 pub fn reserved_directives(&self) -> Vec<ReservedDirective> {
526 self.directive_nodes()
527 .filter_map(|node| self.parse_directive_node(node).ok())
528 .filter_map(|directive| match directive {
529 ParsedDirective::Reserved(directive) => Some(directive),
530 ParsedDirective::Yaml(_) | ParsedDirective::Tag(_) => None,
531 })
532 .collect()
533 }
534
535 pub fn set_yaml_directive(&mut self, version: &str) -> Result<(), YamlError> {
542 validate_yaml_directive_version_for_emit(version)?;
543 let replacement = format!("%YAML {version}");
544 if let Some(directive) = self.yaml_directive() {
545 let span = self.directive_content_span(directive.node)?;
546 self.queue_edit(span, replacement)
547 } else {
548 self.insert_directive_line(replacement)
549 }
550 }
551
552 pub fn set_tag_directive(&mut self, handle: &str, prefix: &str) -> Result<(), YamlError> {
559 validate_tag_directive_parts_for_emit(handle, prefix)?;
560 let replacement = format!("%TAG {handle} {prefix}");
561 if let Some(directive) = self
562 .tag_directives()
563 .into_iter()
564 .find(|directive| directive.handle == handle)
565 {
566 let span = self.directive_content_span(directive.node)?;
567 self.queue_edit(span, replacement)
568 } else {
569 self.insert_directive_line(replacement)
570 }
571 }
572
573 pub fn remove_yaml_directive(&mut self) -> Result<(), YamlError> {
579 if let Some(directive) = self.yaml_directive() {
580 self.remove_directive_node(directive.node)?;
581 }
582 Ok(())
583 }
584
585 pub fn remove_tag_directive(&mut self, handle: &str) -> Result<(), YamlError> {
591 if let Some(directive) = self
592 .tag_directives()
593 .into_iter()
594 .find(|directive| directive.handle == handle)
595 {
596 self.remove_directive_node(directive.node)?;
597 }
598 Ok(())
599 }
600
601 #[must_use]
603 pub fn node(&self, node: NodeId) -> Option<&Node> {
604 self.nodes.get(node.0 as usize)
605 }
606
607 #[must_use]
609 pub fn children(&self, node: NodeId) -> Children<'_> {
610 Children::new(&self.nodes, node)
611 }
612
613 fn semantic_children(&self, node: NodeId) -> impl Iterator<Item = NodeId> + '_ {
614 self.children(node)
615 .filter(|child| self.semantics.get(*child).is_some())
616 }
617
618 #[must_use]
620 pub fn semantic_kind(&self, node: NodeId) -> Option<SemanticKind> {
621 self.semantics.get(node).map(|node| node.kind)
622 }
623
624 #[must_use]
626 pub fn raw_tag(&self, node: NodeId) -> Option<&str> {
627 let span = self.semantics.properties(node)?.tag?;
628 Some(self.source.slice(span))
629 }
630
631 pub fn resolved_tag(&self, node: NodeId) -> Result<Option<Cow<'_, str>>, YamlError> {
633 let Some(raw) = self.raw_tag(node) else {
634 return Ok(None);
635 };
636 let document = self.semantics.property_document(node).unwrap_or(node);
637 let handles = self
638 .semantics
639 .tag_directives(document)
640 .map(|(handle, prefix)| {
641 (
642 self.source.slice(handle).to_owned(),
643 self.source.slice(prefix).to_owned(),
644 )
645 })
646 .collect::<BTreeMap<_, _>>();
647 let span = self
648 .semantics
649 .properties(node)
650 .and_then(|properties| properties.tag)
651 .unwrap_or_else(|| self.node(node).map_or(Span::empty(0), Node::span));
652 resolve_tag(raw, &handles, span).map(|tag| Some(Cow::Owned(tag)))
653 }
654
655 #[must_use]
657 pub fn anchor(&self, node: NodeId) -> Option<&str> {
658 let span = self.semantics.properties(node)?.anchor?;
659 Some(self.source.slice(span))
660 }
661
662 #[must_use]
664 pub fn alias_name(&self, node: NodeId) -> Option<&str> {
665 let span = self.semantics.properties(node)?.alias?;
666 Some(self.source.slice(span))
667 }
668
669 #[must_use]
671 pub fn resolve_alias(&self, node: NodeId) -> Option<NodeId> {
672 let name = self.alias_name(node)?;
673 let document = self.semantics.property_document(node)?;
674 let alias_start = self.node(node)?.span.start;
675 self.semantics
676 .anchors()
677 .rev()
678 .find(|(span, target, anchor_document)| {
679 *anchor_document == document
680 && self
681 .node(*target)
682 .is_some_and(|node| node.span.start <= alias_start)
683 && self.source.slice(*span) == name
684 })
685 .map(|(_, target, _)| target)
686 }
687
688 fn semantic_span(&self, node: NodeId, metadata: &crate::semantic::SemanticNode) -> Span {
689 Span::new(
690 metadata.span_start,
691 self.node(node)
692 .map_or(metadata.end_offset, |node| node.span.end),
693 )
694 }
695
696 fn semantic_end_span(&self, node: NodeId, metadata: &crate::semantic::SemanticNode) -> Span {
697 if metadata.explicit_end()
698 && let Some(marker) = self.children(node).find_map(|child| {
699 let child = self.node(child)?;
700 (child.kind == NodeKind::DocumentMarker
701 && self.source.slice(child.span).starts_with("..."))
702 .then_some(child.span)
703 })
704 {
705 return marker;
706 }
707 if matches!(
708 metadata.kind,
709 SemanticKind::Mapping {
710 style: CollectionStyle::Flow
711 } | SemanticKind::Sequence {
712 style: CollectionStyle::Flow
713 }
714 ) {
715 return self.semantic_span(node, metadata);
716 }
717 Span::empty(metadata.end_offset)
718 }
719
720 pub fn documents(&self) -> impl Iterator<Item = NodeId> + '_ {
722 self.semantics.documents.iter().copied()
723 }
724
725 pub fn document_root(&self, index: usize) -> Result<Option<NodeId>, YamlError> {
735 if let Some(root) = self.root_override {
736 return (index == 0)
737 .then_some(Some(root))
738 .ok_or_else(|| self.document_index_error(index));
739 }
740 let document = self
741 .semantics
742 .documents
743 .get(index)
744 .copied()
745 .ok_or_else(|| self.document_index_error(index))?;
746 let root = self.semantic_children(document).next();
747 Ok(root.filter(|root| {
748 !matches!(self.semantic_kind(*root), Some(SemanticKind::Scalar { .. }))
749 || self.node(*root).is_some_and(|node| !node.span.is_empty())
750 }))
751 }
752
753 pub fn mapping_entries(&self, mapping: NodeId) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
755 let is_mapping = matches!(
756 self.semantic_kind(mapping),
757 Some(SemanticKind::Mapping { .. })
758 );
759 self.children(mapping).filter_map(move |entry| {
760 if !is_mapping || self.node(entry)?.kind != NodeKind::MappingEntry {
761 return None;
762 }
763 let mut children = self.semantic_children(entry);
764 Some((children.next()?, children.next()?))
765 })
766 }
767
768 pub fn sequence_items(&self, sequence: NodeId) -> impl Iterator<Item = NodeId> + '_ {
770 let is_sequence = matches!(
771 self.semantic_kind(sequence),
772 Some(SemanticKind::Sequence { .. })
773 );
774 self.children(sequence).filter_map(move |entry| {
775 if !is_sequence || self.node(entry)?.kind != NodeKind::SequenceEntry {
776 return None;
777 }
778 self.semantic_children(entry).next()
779 })
780 }
781
782 pub fn root_mapping(&self) -> Result<NodeId, YamlError> {
789 self.document_root_mapping(0)
790 }
791
792 pub fn document_root_mapping(&self, index: usize) -> Result<NodeId, YamlError> {
799 if let Some(root) = self.root_override {
800 return (index == 0)
801 .then_some(root)
802 .ok_or_else(|| self.document_index_error(index));
803 }
804 let document = self
805 .semantics
806 .documents
807 .get(index)
808 .copied()
809 .ok_or_else(|| self.document_index_error(index))?;
810 self.semantic_children(document)
811 .find(|child| {
812 self.node(*child)
813 .is_some_and(|node| node.kind == NodeKind::BlockMapping)
814 && matches!(
815 self.semantic_kind(*child),
816 Some(SemanticKind::Mapping { .. })
817 )
818 })
819 .ok_or_else(|| {
820 YamlError::new(
821 Diagnostic::new(
822 DiagnosticKind::Semantic,
823 "document does not contain a root mapping",
824 self.node(document).map_or(Span::empty(0), |node| node.span),
825 )
826 .with_expected("a block mapping node"),
827 )
828 })
829 }
830
831 pub fn read_document<T>(&self, index: usize) -> Result<T, YamlError>
838 where
839 T: FromYamlDoc,
840 {
841 let root = self.document_root_mapping(index)?;
842 let nested = self.rerooted_at_mapping(root)?;
843 T::from_yaml_doc(&nested)
844 }
845
846 pub fn write_document<T>(&mut self, index: usize, value: &T) -> Result<(), YamlError>
853 where
854 T: ToYamlDoc,
855 {
856 let root = match self.document_root_mapping(index) {
857 Ok(root) => root,
858 Err(error) => {
859 if let Some(root) = self.empty_flow_mapping_document_root(index)? {
860 let replacement = value.to_yaml_fragment(0, self.preferred_line_ending())?;
861 return self.replace_node_text(root, replacement);
862 }
863 return Err(error);
864 }
865 };
866 let mut nested = self.rerooted_at_mapping(root)?;
867 value.apply_to_yaml_doc(&mut nested)?;
868 self.queue_edits_from(&nested)
869 }
870
871 pub fn get_mapping_entry(
877 &self,
878 mapping: NodeId,
879 key: &str,
880 ) -> Result<Option<NodeId>, YamlError> {
881 Ok(self
882 .find_mapping_pair(mapping, key)?
883 .and_then(|(key, _)| self.containing_entry(key)))
884 }
885
886 pub fn get_mapping_value(
892 &self,
893 mapping: NodeId,
894 key: &str,
895 ) -> Result<Option<NodeId>, YamlError> {
896 Ok(self
897 .find_mapping_pair(mapping, key)?
898 .map(|(_, value)| value))
899 }
900
901 pub fn get_path(&self, path: &[&str]) -> Result<Option<NodeId>, YamlError> {
907 self.get_path_in_document(0, path)
908 }
909
910 pub fn get_path_in_document(
917 &self,
918 index: usize,
919 path: &[&str],
920 ) -> Result<Option<NodeId>, YamlError> {
921 let Some((first, rest)) = path.split_first() else {
922 return Ok(None);
923 };
924 let Some((_, mut current)) =
925 self.find_mapping_pair(self.document_root_mapping(index)?, first)?
926 else {
927 return Ok(None);
928 };
929 for segment in rest {
930 let Some((_, value)) = self.find_mapping_pair(current, segment)? else {
931 return Ok(None);
932 };
933 current = value;
934 }
935 Ok(Some(current))
936 }
937
938 fn empty_flow_mapping_document_root(&self, index: usize) -> Result<Option<NodeId>, YamlError> {
939 let document = self
940 .semantics
941 .documents
942 .get(index)
943 .copied()
944 .ok_or_else(|| self.document_index_error(index))?;
945 let Some(child) = self.semantic_children(document).next() else {
946 return Ok(None);
947 };
948 let Some(SemanticKind::Mapping { style, .. }) = self.semantic_kind(child) else {
949 return Ok(None);
950 };
951 if style != CollectionStyle::Flow || self.mapping_entries(child).next().is_some() {
952 return Ok(None);
953 }
954 Ok(self
955 .node(child)
956 .is_some_and(|node| node.kind == NodeKind::FlowMapping)
957 .then_some(child))
958 }
959
960 fn document_index_error(&self, index: usize) -> YamlError {
961 YamlError::new(
962 Diagnostic::new(
963 DiagnosticKind::Semantic,
964 format!("document index {index} is out of range"),
965 Span::empty_from_usize(self.source.len()),
966 )
967 .with_expected("an existing document index"),
968 )
969 }
970
971 fn find_mapping_pair(
972 &self,
973 mapping: NodeId,
974 key: &str,
975 ) -> Result<Option<(NodeId, NodeId)>, YamlError> {
976 for (key_node, value_node) in self.mapping_entries(mapping) {
977 if self.scalar_value(key_node)? == key {
978 return Ok(Some((key_node, value_node)));
979 }
980 }
981 Ok(None)
982 }
983
984 pub(crate) fn rerooted_at_mapping(&self, mapping: NodeId) -> Result<Self, YamlError> {
985 self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
986 if !matches!(
987 self.semantic_kind(mapping),
988 Some(SemanticKind::Mapping { .. })
989 ) {
990 return Err(YamlError::new(
991 Diagnostic::new(
992 DiagnosticKind::Semantic,
993 "mapping does not have semantic metadata",
994 self.expect_node(mapping)?.span,
995 )
996 .with_expected("a semantic mapping"),
997 )
998 .with_position_from(&self.source));
999 }
1000 let mut doc = self.clone();
1001 doc.root_override = Some(mapping);
1002 doc.edits.clear();
1003 Ok(doc)
1004 }
1005
1006 pub(crate) fn queue_edits_from(&mut self, other: &YamlDoc) -> Result<(), YamlError> {
1007 for edit in &other.edits {
1008 self.queue_edit(edit.span, edit.replacement.clone())?;
1009 }
1010 Ok(())
1011 }
1012
1013 pub fn scalar_text(&self, node: NodeId) -> Result<&str, YamlError> {
1020 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1021 Ok(self.source.slice(node.span))
1022 }
1023
1024 pub fn scalar_value(&self, node: NodeId) -> Result<Cow<'_, str>, YamlError> {
1035 let node_ref = self.expect_node(node)?;
1036 if !matches!(
1037 node_ref.kind,
1038 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1039 ) {
1040 return Err(YamlError::new(
1041 Diagnostic::new(
1042 DiagnosticKind::Semantic,
1043 format!("expected scalar value, found {:?}", node_ref.kind),
1044 node_ref.span,
1045 )
1046 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1047 )
1048 .with_position_from(&self.source));
1049 }
1050 let text = self.source.slice(node_ref.span);
1051 let properties = parse_node_properties(text, node_ref.span)?;
1052 let value_text = &text[properties.value_start..];
1053 if matches!(
1054 self.semantic_kind(node),
1055 Some(SemanticKind::Scalar {
1056 style: crate::YamlScalarStyle::Plain,
1057 ..
1058 })
1059 ) && !value_text.contains(['\n', '\r'])
1060 {
1061 return Ok(Cow::Borrowed(&value_text[..plain_scalar_end(value_text)]));
1062 }
1063 decode_scalar_value_with_content_indent(
1064 value_text,
1065 self.semantics
1066 .properties(node)
1067 .and_then(|properties| properties.content_indent)
1068 .map(|indent| indent as usize),
1069 )
1070 .map(Cow::Owned)
1071 }
1072
1073 pub fn borrowable_scalar_span(&self, node: NodeId) -> Result<Option<Span>, YamlError> {
1085 let node_ref = self.expect_node(node)?;
1086 if !matches!(
1087 self.semantic_kind(node),
1088 Some(SemanticKind::Scalar {
1089 style: crate::YamlScalarStyle::Plain,
1090 })
1091 ) {
1092 if matches!(
1093 node_ref.kind,
1094 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1095 ) {
1096 return Ok(None);
1097 }
1098 return Err(YamlError::new(
1099 Diagnostic::new(
1100 DiagnosticKind::Semantic,
1101 format!("expected scalar value, found {:?}", node_ref.kind),
1102 node_ref.span,
1103 )
1104 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1105 )
1106 .with_position_from(&self.source));
1107 }
1108
1109 let text = self.source.slice(node_ref.span);
1110 if text.contains(['\n', '\r']) {
1111 return Ok(None);
1112 }
1113 let properties = parse_node_properties(text, node_ref.span)?;
1114 let value_text = &text[properties.value_start..];
1115 let value_len = plain_scalar_end(value_text);
1116 let start = Span::offset_from_usize(node_ref.span.start, properties.value_start);
1117 Ok(Some(Span::new(
1118 start,
1119 Span::offset_from_usize(start, value_len),
1120 )))
1121 }
1122
1123 pub fn set_scalar(&mut self, path: &[&str], value: &str) -> Result<(), YamlError> {
1137 let node = self.get_path(path)?.ok_or_else(|| {
1138 YamlError::new(
1139 Diagnostic::new(
1140 DiagnosticKind::Semantic,
1141 format!("path `{}` does not exist", path.join(".")),
1142 Span::empty(0),
1143 )
1144 .with_expected("an existing scalar node"),
1145 )
1146 })?;
1147
1148 let (span, style) = self.scalar_replacement_target(node)?;
1149 let replacement = format_scalar_value(value, style)?;
1150 self.queue_edit(span, replacement)
1151 }
1152
1153 pub fn replace_node_text(
1163 &mut self,
1164 node: NodeId,
1165 text: impl Into<String>,
1166 ) -> Result<(), YamlError> {
1167 let span = self.expect_node(node)?.span;
1168 self.queue_edit(span, text.into())
1169 }
1170
1171 pub fn insert_mapping_entry(
1182 &mut self,
1183 mapping: NodeId,
1184 key: &str,
1185 value: &str,
1186 style: MappingEntryStyle,
1187 ) -> Result<(), YamlError> {
1188 self.insert_mapping_entry_with_comment(mapping, key, value, style, None)
1189 }
1190
1191 pub fn insert_mapping_entry_with_comment(
1203 &mut self,
1204 mapping: NodeId,
1205 key: &str,
1206 value: &str,
1207 style: MappingEntryStyle,
1208 comment: Option<&str>,
1209 ) -> Result<(), YamlError> {
1210 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1211 let indent = match style {
1212 MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1213 MappingEntryStyle::Indent(indent) => indent,
1214 };
1215 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1216 let needs_leading_break =
1217 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1218 let preserve_paragraph_break = comment.is_some()
1219 && insertion_offset == self.source.len()
1220 && self.mapping_has_blank_line(mapping_node);
1221 let replacement = self.format_mapping_entry_replacement(
1222 indent,
1223 key,
1224 value,
1225 comment,
1226 needs_leading_break,
1227 preserve_paragraph_break,
1228 )?;
1229
1230 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1231 }
1232
1233 pub fn insert_mapping_value_with_comment<T>(
1241 &mut self,
1242 mapping: NodeId,
1243 key: &str,
1244 value: &T,
1245 style: MappingEntryStyle,
1246 comment: Option<&str>,
1247 ) -> Result<(), YamlError>
1248 where
1249 T: ToYamlFragment,
1250 {
1251 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1252 let indent = match style {
1253 MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1254 MappingEntryStyle::Indent(indent) => indent,
1255 };
1256 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1257 let needs_leading_break =
1258 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1259 let preserve_paragraph_break = comment.is_some()
1260 && insertion_offset == self.source.len()
1261 && self.mapping_has_blank_line(mapping_node);
1262 let replacement = self.format_mapping_value_replacement(
1263 indent,
1264 key,
1265 value,
1266 comment,
1267 needs_leading_break,
1268 preserve_paragraph_break,
1269 )?;
1270
1271 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1272 }
1273
1274 pub fn insert_mapping_value_ordered_with_comment<T>(
1281 &mut self,
1282 mapping: NodeId,
1283 key: &str,
1284 value: &T,
1285 style: MappingEntryStyle,
1286 comment: Option<&str>,
1287 ordered_keys: &[&str],
1288 ) -> Result<(), YamlError>
1289 where
1290 T: ToYamlFragment,
1291 {
1292 let mut next_entry = None;
1293 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1294 for later_key in &ordered_keys[position + 1..] {
1295 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1296 next_entry = Some(entry);
1297 break;
1298 }
1299 }
1300 }
1301
1302 if let Some(next_entry) = next_entry {
1303 self.insert_mapping_value_before_with_comment(next_entry, key, value, style, comment)
1304 } else {
1305 self.insert_mapping_value_with_comment(mapping, key, value, style, comment)
1306 }
1307 }
1308
1309 pub fn insert_mapping_entry_before_with_comment(
1317 &mut self,
1318 before_entry: NodeId,
1319 key: &str,
1320 value: &str,
1321 style: MappingEntryStyle,
1322 comment: Option<&str>,
1323 ) -> Result<(), YamlError> {
1324 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1325 let indent = match style {
1326 MappingEntryStyle::Inherit => self.node_indent(before_node),
1327 MappingEntryStyle::Indent(indent) => indent,
1328 };
1329 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1330 let replacement =
1331 self.format_mapping_entry_replacement(indent, key, value, comment, false, false)?;
1332
1333 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1334 }
1335
1336 pub fn insert_mapping_value_before_with_comment<T>(
1343 &mut self,
1344 before_entry: NodeId,
1345 key: &str,
1346 value: &T,
1347 style: MappingEntryStyle,
1348 comment: Option<&str>,
1349 ) -> Result<(), YamlError>
1350 where
1351 T: ToYamlFragment,
1352 {
1353 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1354 let indent = match style {
1355 MappingEntryStyle::Inherit => self.node_indent(before_node),
1356 MappingEntryStyle::Indent(indent) => indent,
1357 };
1358 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1359 let replacement =
1360 self.format_mapping_value_replacement(indent, key, value, comment, false, false)?;
1361
1362 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1363 }
1364
1365 pub fn insert_mapping_entry_ordered_with_comment(
1377 &mut self,
1378 mapping: NodeId,
1379 key: &str,
1380 value: &str,
1381 style: MappingEntryStyle,
1382 comment: Option<&str>,
1383 ordered_keys: &[&str],
1384 ) -> Result<(), YamlError> {
1385 let mut next_entry = None;
1386 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1387 for later_key in &ordered_keys[position + 1..] {
1388 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1389 next_entry = Some(entry);
1390 break;
1391 }
1392 }
1393 }
1394
1395 if let Some(next_entry) = next_entry {
1396 self.insert_mapping_entry_before_with_comment(next_entry, key, value, style, comment)
1397 } else {
1398 self.insert_mapping_entry_with_comment(mapping, key, value, style, comment)
1399 }
1400 }
1401
1402 pub fn remove_mapping_entry(&mut self, mapping: NodeId, key: &str) -> Result<(), YamlError> {
1412 let Some(entry) = self.get_mapping_entry(mapping, key)? else {
1413 return Ok(());
1414 };
1415 self.remove_node(entry)
1416 }
1417
1418 pub fn retain_mapping_entries(
1429 &mut self,
1430 mapping: NodeId,
1431 allowed_keys: &[&str],
1432 ) -> Result<(), YamlError> {
1433 self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1434 let mut removals = Vec::new();
1435
1436 for entry in self.children(mapping) {
1437 let entry_node = self.expect_node(entry)?;
1438 if entry_node.kind != NodeKind::MappingEntry {
1439 continue;
1440 }
1441
1442 let Some(key_node) = self.children(entry).next() else {
1443 continue;
1444 };
1445 let key = self.scalar_text(key_node)?;
1446 if !allowed_keys.contains(&key) {
1447 removals.push(entry);
1448 }
1449 }
1450
1451 for entry in removals {
1452 self.remove_node(entry)?;
1453 }
1454
1455 Ok(())
1456 }
1457
1458 pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1468 let node = self.expect_node(node)?;
1469 let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1470 self.line_span_including_break(node.span)
1471 } else {
1472 node.span
1473 };
1474 self.queue_edit(span, String::new())
1475 }
1476
1477 pub(crate) fn scalar_replacement_target(
1478 &self,
1479 node: NodeId,
1480 ) -> Result<(Span, ScalarStyle), YamlError> {
1481 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1482 let text = self.source.slice(node.span);
1483 let properties = parse_node_properties(text, node.span)?;
1484 let value_text = &text[properties.value_start..];
1485 let value_start = Span::offset_from_usize(node.span.start, properties.value_start);
1486
1487 if value_text.starts_with('"') {
1488 let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1489 YamlError::new(
1490 Diagnostic::new(
1491 DiagnosticKind::Emitter,
1492 "could not find the end of the double-quoted scalar",
1493 node.span,
1494 )
1495 .with_expected("a closed double-quoted scalar"),
1496 )
1497 })?;
1498 return Ok((
1499 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1500 ScalarStyle::DoubleQuoted,
1501 ));
1502 }
1503
1504 if value_text.starts_with('\'') {
1505 let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1506 YamlError::new(
1507 Diagnostic::new(
1508 DiagnosticKind::Emitter,
1509 "could not find the end of the single-quoted scalar",
1510 node.span,
1511 )
1512 .with_expected("a closed single-quoted scalar"),
1513 )
1514 })?;
1515 return Ok((
1516 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1517 ScalarStyle::SingleQuoted,
1518 ));
1519 }
1520
1521 let end = plain_scalar_end(value_text);
1522 if end == 0 {
1523 return Err(YamlError::new(
1524 Diagnostic::new(
1525 DiagnosticKind::Emitter,
1526 "could not find plain scalar text to replace",
1527 node.span,
1528 )
1529 .with_expected("plain scalar text"),
1530 ));
1531 }
1532
1533 Ok((
1534 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1535 ScalarStyle::Plain,
1536 ))
1537 }
1538
1539 pub(crate) fn collection_replacement_target(
1540 &self,
1541 node: NodeId,
1542 ) -> Result<CollectionTarget, YamlError> {
1543 let node = self.expect_node(node)?;
1544 let text = self.source.slice(node.span);
1545 let properties = parse_node_properties(text, node.span)?;
1546 let mut body_start = properties.value_start;
1547
1548 if text[body_start..].starts_with(['\r', '\n']) {
1549 body_start = next_line_content_start(text, body_start);
1550 }
1551
1552 let start = Span::offset_from_usize(node.span.start, body_start);
1553 Ok(CollectionTarget {
1554 span: Span::new(start, node.span.end),
1555 indent: self.line_indent_for_offset(start as usize),
1556 })
1557 }
1558
1559 fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1560 self.root()
1561 .into_iter()
1562 .flat_map(|root| self.children(root))
1563 .filter(|node| {
1564 self.node(*node)
1565 .is_some_and(|node| node.kind == NodeKind::Directive)
1566 })
1567 }
1568
1569 fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1570 let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1571 let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1572 let mut parts = body.split_whitespace();
1573 let Some(name) = parts.next() else {
1574 return Err(directive_emit_error(
1575 "directive is missing a name",
1576 node_ref.span,
1577 "%YAML, %TAG, or reserved directive syntax",
1578 )
1579 .with_position_from(&self.source));
1580 };
1581
1582 Ok(match name {
1583 "%YAML" => ParsedDirective::Yaml(YamlDirective {
1584 version: parts.next().unwrap_or_default().to_owned(),
1585 node,
1586 }),
1587 "%TAG" => ParsedDirective::Tag(TagDirective {
1588 handle: parts.next().unwrap_or_default().to_owned(),
1589 prefix: parts.next().unwrap_or_default().to_owned(),
1590 node,
1591 }),
1592 _ => ParsedDirective::Reserved(ReservedDirective {
1593 name: name.to_owned(),
1594 parameters: parts.map(str::to_owned).collect(),
1595 node,
1596 }),
1597 })
1598 }
1599
1600 fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1601 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1602 let text = self.source.slice(node.span);
1603 let end = strip_inline_comment(text)
1604 .trim_end_matches([' ', '\t'])
1605 .len();
1606 Ok(Span::new(
1607 node.span.start,
1608 Span::offset_from_usize(node.span.start, end),
1609 ))
1610 }
1611
1612 fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1613 let insertion_offset = self.directive_insertion_offset();
1614 let mut line = replacement;
1615 line.push_str(self.preferred_line_ending());
1616 self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1617 }
1618
1619 fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1620 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1621 self.queue_edit(self.line_span_including_break(node.span), String::new())
1622 }
1623
1624 fn directive_insertion_offset(&self) -> usize {
1625 if let Some(last_directive) = self
1626 .directive_nodes()
1627 .filter_map(|node| self.node(node))
1628 .max_by_key(|node| node.span.start)
1629 {
1630 return self.line_span_including_break(last_directive.span).end as usize;
1631 }
1632
1633 self.root()
1634 .and_then(|root| self.children(root).next())
1635 .and_then(|node| self.node(node))
1636 .map_or(0, |node| {
1637 self.line_start_for_offset(node.span.start as usize)
1638 })
1639 }
1640
1641 pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1642 self.node(node).ok_or_else(|| {
1643 YamlError::new(Diagnostic::new(
1644 DiagnosticKind::Semantic,
1645 format!("unknown node id {}", node.0),
1646 Span::empty_from_usize(self.source.len()),
1647 ))
1648 })
1649 }
1650
1651 pub(crate) fn expect_node_kind(
1652 &self,
1653 node: NodeId,
1654 expected: NodeKind,
1655 ) -> Result<&Node, YamlError> {
1656 let actual = self.expect_node(node)?;
1657 if actual.kind == expected {
1658 Ok(actual)
1659 } else {
1660 Err(YamlError::new(
1661 Diagnostic::new(
1662 DiagnosticKind::Semantic,
1663 format!("expected {expected:?}, found {:?}", actual.kind),
1664 actual.span,
1665 )
1666 .with_expected(format!("{expected:?}")),
1667 )
1668 .with_position_from(&self.source))
1669 }
1670 }
1671
1672 pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1673 self.node(value).and_then(Node::parent).filter(|parent| {
1674 self.node(*parent).is_some_and(|node| {
1675 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1676 })
1677 })
1678 }
1679
1680 fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1681 let start = self.line_start_for_offset(mapping.span.start as usize);
1682 let end = mapping.span.end as usize;
1683 let text = &self.source.as_str()[start..end];
1684 text.contains("\n\n") || text.contains("\r\n\r\n")
1685 }
1686
1687 fn format_mapping_entry_replacement(
1688 &self,
1689 indent: usize,
1690 key: &str,
1691 value: &str,
1692 comment: Option<&str>,
1693 needs_leading_break: bool,
1694 preserve_paragraph_break: bool,
1695 ) -> Result<String, YamlError> {
1696 validate_plain_mapping_fragment(key, "mapping key")?;
1697 validate_plain_mapping_fragment(value, "mapping value")?;
1698 if let Some(comment) = comment {
1699 validate_yaml_chars(comment)?;
1700 }
1701
1702 let line_ending = self.preferred_line_ending();
1703 let indent_text = " ".repeat(indent);
1704 let mut replacement = String::new();
1705 if needs_leading_break {
1706 replacement.push_str(line_ending);
1707 }
1708 if preserve_paragraph_break {
1709 replacement.push_str(line_ending);
1710 }
1711 if let Some(comment) = comment {
1712 for line in comment.lines() {
1713 replacement.push_str(&indent_text);
1714 replacement.push('#');
1715 if !line.is_empty() {
1716 replacement.push(' ');
1717 replacement.push_str(line.trim());
1718 }
1719 replacement.push_str(line_ending);
1720 }
1721 }
1722 replacement.push_str(&indent_text);
1723 replacement.push_str(key);
1724 replacement.push_str(": ");
1725 replacement.push_str(value);
1726 replacement.push_str(line_ending);
1727 Ok(replacement)
1728 }
1729
1730 fn format_mapping_value_replacement<T>(
1731 &self,
1732 indent: usize,
1733 key: &str,
1734 value: &T,
1735 comment: Option<&str>,
1736 needs_leading_break: bool,
1737 preserve_paragraph_break: bool,
1738 ) -> Result<String, YamlError>
1739 where
1740 T: ToYamlFragment,
1741 {
1742 validate_plain_mapping_fragment(key, "mapping key")?;
1743 if let Some(comment) = comment {
1744 validate_yaml_chars(comment)?;
1745 }
1746
1747 let line_ending = self.preferred_line_ending();
1748 let indent_text = " ".repeat(indent);
1749 let child_indent = indent + 2;
1750 let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
1751 let mut replacement = String::new();
1752 if needs_leading_break {
1753 replacement.push_str(line_ending);
1754 }
1755 if preserve_paragraph_break {
1756 replacement.push_str(line_ending);
1757 }
1758 if let Some(comment) = comment {
1759 for line in comment.lines() {
1760 replacement.push_str(&indent_text);
1761 replacement.push('#');
1762 if !line.is_empty() {
1763 replacement.push(' ');
1764 replacement.push_str(line.trim());
1765 }
1766 replacement.push_str(line_ending);
1767 }
1768 }
1769 replacement.push_str(&indent_text);
1770 replacement.push_str(key);
1771 if fragment.contains('\n') || fragment.starts_with(' ') {
1772 replacement.push(':');
1773 replacement.push_str(line_ending);
1774 replacement.push_str(&fragment);
1775 } else {
1776 replacement.push_str(": ");
1777 replacement.push_str(&fragment);
1778 }
1779 replacement.push_str(line_ending);
1780 Ok(replacement)
1781 }
1782
1783 pub(crate) fn node_indent(&self, node: &Node) -> usize {
1784 let line_start = self.line_start_for_offset(node.span.start as usize);
1785 self.source.as_str()[line_start..node.span.start as usize]
1786 .bytes()
1787 .filter(|byte| *byte == b' ')
1788 .count()
1789 }
1790
1791 fn line_indent_for_offset(&self, offset: usize) -> usize {
1792 let line_start = self.line_start_for_offset(offset);
1793 self.source.as_str()[line_start..offset]
1794 .bytes()
1795 .filter(|byte| *byte == b' ')
1796 .count()
1797 }
1798
1799 fn line_start_for_offset(&self, offset: usize) -> usize {
1800 let offset = Span::usize_to_u32(offset);
1801 match self.source.line_starts().binary_search(&offset) {
1802 Ok(index) => self.source.line_starts()[index] as usize,
1803 Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
1804 }
1805 }
1806
1807 pub(crate) fn find_nested_collection_after(
1808 &self,
1809 entry: &Node,
1810 parent_indent: usize,
1811 ) -> Option<NodeId> {
1812 self.nodes
1813 .iter()
1814 .enumerate()
1815 .filter(|(_, node)| {
1816 matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
1817 && node.span.start >= entry.span.end
1818 && self.node_indent(node) > parent_indent
1819 })
1820 .min_by_key(|(_, node)| node.span.start)
1821 .map(|(index, _)| NodeId::from_usize(index))
1822 }
1823
1824 pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
1825 let text = self.source.slice(scalar.span);
1826 let header_end = text.find(['\r', '\n'])?;
1827 let mut rest = &text[header_end..];
1828 while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
1829 rest = stripped;
1830 }
1831 for line in rest.lines() {
1832 if line.trim().is_empty() {
1833 continue;
1834 }
1835 return Some(line.bytes().take_while(|byte| *byte == b' ').count());
1836 }
1837 None
1838 }
1839
1840 pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
1841 self.source.try_slice(span)?;
1842 validate_yaml_chars(&replacement)?;
1843
1844 if span.is_empty()
1845 && let Some(existing) = self
1846 .edits
1847 .iter_mut()
1848 .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
1849 {
1850 existing.replacement.push_str(&replacement);
1851 return Ok(());
1852 }
1853
1854 if let Some(existing) = self
1855 .edits
1856 .iter()
1857 .find(|edit| edits_conflict(edit.span, span))
1858 {
1859 return Err(YamlError::new(
1860 Diagnostic::new(
1861 DiagnosticKind::Emitter,
1862 "edit overlaps an existing pending edit",
1863 span,
1864 )
1865 .with_note(format!(
1866 "existing edit covers bytes {}..{}",
1867 existing.span.start, existing.span.end
1868 )),
1869 )
1870 .with_position_from(&self.source));
1871 }
1872
1873 self.edits.push(Edit { span, replacement });
1874 Ok(())
1875 }
1876
1877 pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
1878 node_link(mapping.last_child)
1879 .and_then(|child| self.node(child))
1880 .map_or(mapping.span.end as usize, |last_child| {
1881 self.line_span_including_break(last_child.span).end as usize
1882 })
1883 }
1884
1885 pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
1886 node_link(sequence.last_child)
1887 .and_then(|child| self.node(child))
1888 .map_or(sequence.span.end as usize, |last_child| {
1889 self.line_span_including_break(last_child.span).end as usize
1890 })
1891 }
1892
1893 fn line_span_including_break(&self, span: Span) -> Span {
1894 let start = self.line_start_for_offset(span.start as usize);
1895 let mut end = span.end as usize;
1896 let bytes = self.source.as_str().as_bytes();
1897
1898 if end < bytes.len() {
1899 if bytes[end] == b'\r' {
1900 end += 1;
1901 if end < bytes.len() && bytes[end] == b'\n' {
1902 end += 1;
1903 }
1904 } else if bytes[end] == b'\n' {
1905 end += 1;
1906 }
1907 }
1908
1909 Span::from_usize(start, end)
1910 }
1911
1912 pub(crate) fn preferred_line_ending(&self) -> &str {
1913 let bytes = self.source.as_str().as_bytes();
1914 for (index, byte) in bytes.iter().enumerate() {
1915 if *byte == b'\r' {
1916 return if bytes.get(index + 1) == Some(&b'\n') {
1917 "\r\n"
1918 } else {
1919 "\r"
1920 };
1921 }
1922 if *byte == b'\n' {
1923 return if index > 0 && bytes[index - 1] == b'\r' {
1924 "\r\n"
1925 } else {
1926 "\n"
1927 };
1928 }
1929 }
1930 "\n"
1931 }
1932
1933 fn source_ends_with_line_break(&self) -> bool {
1934 self.source
1935 .as_str()
1936 .as_bytes()
1937 .last()
1938 .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
1939 }
1940
1941 fn document_append_prefix(&self, line_ending: &str) -> String {
1942 if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
1943 String::new()
1944 } else {
1945 line_ending.to_owned()
1946 }
1947 }
1948}
1949impl fmt::Display for YamlDoc {
1950 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1951 if self.edits.is_empty() {
1952 return formatter.write_str(self.source.as_str());
1953 }
1954
1955 let mut output = self.source.as_str().to_owned();
1956 let mut edits = self.edits.clone();
1957 edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
1958
1959 for edit in edits {
1960 output.replace_range(
1961 edit.span.start as usize..edit.span.end as usize,
1962 &edit.replacement,
1963 );
1964 }
1965
1966 formatter.write_str(&output)
1967 }
1968}