Skip to main content

yaml_rt_core/
doc.rs

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, SemanticStore, Source, Span, ToYamlDoc, ToYamlFragment,
9    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/// Pending source edit used by the patch-based emitter.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Edit {
20    /// Span to replace. Empty spans represent insertions.
21    pub span: Span,
22    /// Replacement text.
23    pub replacement: String,
24}
25
26/// On-demand iterator over semantic YAML events.
27pub 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/// Parsed `%YAML` directive metadata.
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct YamlDirective {
299    /// Directive version text, such as `1.2`.
300    pub version: String,
301    /// CST directive node.
302    pub node: NodeId,
303}
304
305/// Parsed `%TAG` directive metadata.
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct TagDirective {
308    /// Tag handle, such as `!` or `!e!`.
309    pub handle: String,
310    /// Tag prefix text.
311    pub prefix: String,
312    /// CST directive node.
313    pub node: NodeId,
314}
315
316/// Parsed reserved directive metadata.
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct ReservedDirective {
319    /// Directive name, including the leading `%`.
320    pub name: String,
321    /// Whitespace-separated directive parameters.
322    pub parameters: Vec<String>,
323    /// CST directive node.
324    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/// Formatting controls for inserting a block mapping entry.
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
336pub enum MappingEntryStyle {
337    /// Reuse the target mapping indentation and the document line ending.
338    #[default]
339    Inherit,
340    /// Insert with an explicit indentation width, in spaces.
341    Indent(usize),
342}
343
344/// A source-preserving YAML document.
345#[derive(Debug, Clone, PartialEq, Eq)]
346pub struct YamlDoc {
347    /// Original source buffer.
348    pub(crate) source: Source,
349    /// CST and semantic nodes. The CST remains the source of truth.
350    pub(crate) nodes: Vec<Node>,
351    /// Compact semantic metadata keyed by CST node IDs.
352    pub(crate) semantics: SemanticStore,
353    /// Optional scalar, sequence, or mapping root used by nested typed overlays.
354    pub(crate) root_override: Option<NodeId>,
355    /// Pending patch edits applied from highest offset to lowest offset.
356    pub(crate) edits: Vec<Edit>,
357}
358
359impl YamlDoc {
360    /// Parses a YAML stream into a round-trip document.
361    ///
362    /// This bootstrap parser preserves the input text exactly and records a root
363    /// stream node. Real lexing and parsing will replace this placeholder.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error when source validation, CST parsing, or semantic view
368    /// composition fails.
369    pub fn parse(input: &str) -> Result<Self, YamlError> {
370        Self::parse_owned(input.to_owned())
371    }
372
373    /// Parses an owned YAML stream without copying its source buffer.
374    ///
375    /// # Errors
376    ///
377    /// Returns an error when source validation, CST parsing, or semantic view
378    /// composition fails.
379    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    /// Applies pending edits, reparses the rendered YAML, and clears the edit queue.
394    ///
395    /// `YamlDoc::to_string` only previews the original source with queued byte
396    /// patches applied. It does not prove that the patched stream is still
397    /// valid YAML, because low-level edit APIs can replace arbitrary node spans
398    /// or insert conservative-but-raw fragments. Reparse on commit is the point
399    /// where the document regains a validated CST and semantic view.
400    ///
401    /// # Errors
402    ///
403    /// Returns an error when the patched YAML cannot be parsed.
404    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    /// Returns the original source text.
415    #[must_use]
416    pub fn as_source(&self) -> &str {
417        self.source.as_str()
418    }
419
420    /// Returns the original source buffer and its line index.
421    #[must_use]
422    pub const fn source(&self) -> &Source {
423        &self.source
424    }
425
426    /// Returns a freshly owned copy of the lossless token stream.
427    ///
428    /// The owned return type keeps this API stable when tokenization becomes
429    /// on demand rather than retained by every document.
430    ///
431    /// # Errors
432    ///
433    /// Returns a lexer diagnostic if the source cannot be tokenized.
434    pub fn tokens(&self) -> Result<Vec<Token>, YamlError> {
435        lex(&self.source).map_err(|error| error.with_position_from(&self.source))
436    }
437
438    /// Returns the root node identifier when present.
439    #[must_use]
440    pub fn root(&self) -> Option<NodeId> {
441        (!self.nodes.is_empty()).then_some(NodeId(0))
442    }
443
444    /// Derives the semantic event stream from CST-linked metadata.
445    #[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    /// Renders semantic events in the YAML Test Suite `test.event` format.
455    #[must_use]
456    pub fn events_to_test_string(&self) -> String {
457        events_to_test_string(self.events())
458    }
459
460    /// Returns the number of documents in this YAML stream.
461    #[must_use]
462    pub fn document_count(&self) -> usize {
463        self.root_override
464            .map_or(self.semantics.documents.len(), |_| 1)
465    }
466
467    /// Queues an explicit document append at the end of this YAML stream.
468    ///
469    /// The appended document becomes visible to document-indexed lookup after
470    /// [`YamlDoc::commit_edits`] reparses the stream.
471    ///
472    /// # Errors
473    ///
474    /// Returns an error when `value` cannot be formatted as a YAML fragment or
475    /// the append conflicts with another pending edit at the stream end.
476    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    /// Queues an explicit empty mapping document append.
491    ///
492    /// # Errors
493    ///
494    /// Returns an error when the append conflicts with another pending edit at
495    /// the stream end.
496    pub fn append_empty_mapping_document(&mut self) -> Result<(), YamlError> {
497        self.append_document(&std::collections::BTreeMap::<String, String>::new())
498    }
499
500    /// Returns the `%YAML` directive when one is present in the stream prologue.
501    #[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    /// Returns `%TAG` directives from the stream prologue in source order.
512    #[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    /// Returns reserved directives from the stream prologue in source order.
524    #[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    /// Queues insertion or update of the stream `%YAML` directive.
536    ///
537    /// # Errors
538    ///
539    /// Returns an error when `version` is not valid YAML directive version
540    /// syntax or when the edit overlaps an existing pending edit.
541    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    /// Queues insertion or update of a stream `%TAG` directive.
553    ///
554    /// # Errors
555    ///
556    /// Returns an error when `handle` or `prefix` is invalid or when the edit
557    /// overlaps an existing pending edit.
558    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    /// Queues removal of the stream `%YAML` directive when present.
574    ///
575    /// # Errors
576    ///
577    /// Returns an error when the removal edit overlaps an existing pending edit.
578    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    /// Queues removal of the stream `%TAG` directive with `handle` when present.
586    ///
587    /// # Errors
588    ///
589    /// Returns an error when the removal edit overlaps an existing pending edit.
590    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    /// Returns a node by identifier.
602    #[must_use]
603    pub fn node(&self, node: NodeId) -> Option<&Node> {
604        self.nodes.get(node.0 as usize)
605    }
606
607    /// Iterates over a node's children in source order.
608    #[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    /// Returns a node's semantic interpretation.
619    #[must_use]
620    pub fn semantic_kind(&self, node: NodeId) -> Option<SemanticKind> {
621        self.semantics.get(node).map(|node| node.kind)
622    }
623
624    /// Returns the explicit tag spelling, including its leading `!`.
625    #[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    /// Resolves an explicit tag through the built-in or document-local handle table.
632    ///
633    /// # Errors
634    ///
635    /// Returns an error when the tag spelling or its document-local handle is invalid.
636    pub fn resolved_tag(&self, node: NodeId) -> Result<Option<Cow<'_, str>>, YamlError> {
637        let Some(raw) = self.raw_tag(node) else {
638            return Ok(None);
639        };
640        let document = self.semantics.property_document(node).unwrap_or(node);
641        let handles = self
642            .semantics
643            .tag_directives(document)
644            .map(|(handle, prefix)| {
645                (
646                    self.source.slice(handle).to_owned(),
647                    self.source.slice(prefix).to_owned(),
648                )
649            })
650            .collect::<BTreeMap<_, _>>();
651        let span = self
652            .semantics
653            .properties(node)
654            .and_then(|properties| properties.tag)
655            .unwrap_or_else(|| self.node(node).map_or(Span::empty(0), Node::span));
656        resolve_tag(raw, &handles, span).map(|tag| Some(Cow::Owned(tag)))
657    }
658
659    /// Returns an anchor name without its leading `&`.
660    #[must_use]
661    pub fn anchor(&self, node: NodeId) -> Option<&str> {
662        let span = self.semantics.properties(node)?.anchor?;
663        Some(self.source.slice(span))
664    }
665
666    /// Returns an alias name without its leading `*`.
667    #[must_use]
668    pub fn alias_name(&self, node: NodeId) -> Option<&str> {
669        let span = self.semantics.properties(node)?.alias?;
670        Some(self.source.slice(span))
671    }
672
673    /// Resolves an alias to the most recent matching anchor in its document.
674    #[must_use]
675    pub fn resolve_alias(&self, node: NodeId) -> Option<NodeId> {
676        let name = self.alias_name(node)?;
677        let document = self.semantics.property_document(node)?;
678        let alias_start = self.node(node)?.span.start;
679        self.semantics
680            .anchors()
681            .rev()
682            .find(|(span, target, anchor_document)| {
683                *anchor_document == document
684                    && self
685                        .node(*target)
686                        .is_some_and(|node| node.span.start <= alias_start)
687                    && self.source.slice(*span) == name
688            })
689            .map(|(_, target, _)| target)
690    }
691
692    fn semantic_span(&self, node: NodeId, metadata: &crate::semantic::SemanticNode) -> Span {
693        Span::new(
694            metadata.span_start,
695            self.node(node)
696                .map_or(metadata.end_offset, |node| node.span.end),
697        )
698    }
699
700    fn semantic_end_span(&self, node: NodeId, metadata: &crate::semantic::SemanticNode) -> Span {
701        if metadata.explicit_end()
702            && let Some(marker) = self.children(node).find_map(|child| {
703                let child = self.node(child)?;
704                (child.kind == NodeKind::DocumentMarker
705                    && self.source.slice(child.span).starts_with("..."))
706                .then_some(child.span)
707            })
708        {
709            return marker;
710        }
711        if matches!(
712            metadata.kind,
713            SemanticKind::Mapping {
714                style: CollectionStyle::Flow
715            } | SemanticKind::Sequence {
716                style: CollectionStyle::Flow
717            }
718        ) {
719            return self.semantic_span(node, metadata);
720        }
721        Span::empty(metadata.end_offset)
722    }
723
724    /// Iterates over semantic document CST nodes in stream order.
725    pub fn documents(&self) -> impl Iterator<Item = NodeId> + '_ {
726        self.semantics.documents.iter().copied()
727    }
728
729    /// Returns the semantic root value of a selected document.
730    ///
731    /// Empty documents have no root value and return `Ok(None)`. Unlike
732    /// [`YamlDoc::document_root_mapping`], this method accepts scalar, sequence,
733    /// mapping, and alias roots.
734    ///
735    /// # Errors
736    ///
737    /// Returns an error when `index` is outside the YAML stream.
738    pub fn document_root(&self, index: usize) -> Result<Option<NodeId>, YamlError> {
739        if let Some(root) = self.root_override {
740            return (index == 0)
741                .then_some(Some(root))
742                .ok_or_else(|| self.document_index_error(index));
743        }
744        let document = self
745            .semantics
746            .documents
747            .get(index)
748            .copied()
749            .ok_or_else(|| self.document_index_error(index))?;
750        let root = self.semantic_children(document).next();
751        Ok(root.filter(|root| {
752            !matches!(self.semantic_kind(*root), Some(SemanticKind::Scalar { .. }))
753                || self.node(*root).is_some_and(|node| !node.span.is_empty())
754        }))
755    }
756
757    /// Iterates over mapping key/value CST node pairs in source order.
758    pub fn mapping_entries(&self, mapping: NodeId) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
759        let is_mapping = matches!(
760            self.semantic_kind(mapping),
761            Some(SemanticKind::Mapping { .. })
762        );
763        self.children(mapping).filter_map(move |entry| {
764            if !is_mapping || self.node(entry)?.kind != NodeKind::MappingEntry {
765                return None;
766            }
767            let mut children = self.semantic_children(entry);
768            Some((children.next()?, children.next()?))
769        })
770    }
771
772    /// Iterates over sequence item CST nodes in source order.
773    pub fn sequence_items(&self, sequence: NodeId) -> impl Iterator<Item = NodeId> + '_ {
774        let is_sequence = matches!(
775            self.semantic_kind(sequence),
776            Some(SemanticKind::Sequence { .. })
777        );
778        self.children(sequence).filter_map(move |entry| {
779            if !is_sequence || self.node(entry)?.kind != NodeKind::SequenceEntry {
780                return None;
781            }
782            self.semantic_children(entry).next()
783        })
784    }
785
786    /// Returns the root-level mapping in the document.
787    ///
788    /// # Errors
789    ///
790    /// Returns an error when no root mapping exists or when the semantic
791    /// root mapping is not linked back to a CST node.
792    pub fn root_mapping(&self) -> Result<NodeId, YamlError> {
793        self.document_root_mapping(0)
794    }
795
796    /// Returns the root-level mapping in a selected document.
797    ///
798    /// # Errors
799    ///
800    /// Returns an error when the selected document does not exist, has no
801    /// mapping root, or the root mapping is not linked back to the CST.
802    pub fn document_root_mapping(&self, index: usize) -> Result<NodeId, YamlError> {
803        if let Some(root) = self.root_override {
804            return (index == 0)
805                .then_some(root)
806                .ok_or_else(|| self.document_index_error(index));
807        }
808        let document = self
809            .semantics
810            .documents
811            .get(index)
812            .copied()
813            .ok_or_else(|| self.document_index_error(index))?;
814        self.semantic_children(document)
815            .find(|child| {
816                self.node(*child).is_some_and(|node| {
817                    matches!(node.kind, NodeKind::BlockMapping | NodeKind::FlowMapping)
818                }) && matches!(
819                    self.semantic_kind(*child),
820                    Some(SemanticKind::Mapping { .. })
821                )
822            })
823            .ok_or_else(|| {
824                YamlError::new(
825                    Diagnostic::new(
826                        DiagnosticKind::Semantic,
827                        "document does not contain a root mapping",
828                        self.node(document).map_or(Span::empty(0), |node| node.span),
829                    )
830                    .with_expected("a block or flow mapping node"),
831                )
832            })
833    }
834
835    /// Reads a typed overlay from a selected document.
836    ///
837    /// # Errors
838    ///
839    /// Returns an error when the selected document is missing or empty, or the
840    /// typed overlay cannot be read.
841    pub fn read_document<T>(&self, index: usize) -> Result<T, YamlError>
842    where
843        T: FromYamlDoc,
844    {
845        let root = self
846            .document_root(index)?
847            .ok_or_else(|| self.empty_document_error(index))?;
848        let nested = self.rerooted_at(root)?;
849        T::from_yaml_doc(&nested)
850    }
851
852    /// Writes a typed overlay to a selected document.
853    ///
854    /// # Errors
855    ///
856    /// Returns an error when the selected document is missing or empty, or the
857    /// typed overlay cannot be written.
858    pub fn write_document<T>(&mut self, index: usize, value: &T) -> Result<(), YamlError>
859    where
860        T: ToYamlDoc,
861    {
862        let root = self
863            .document_root(index)?
864            .ok_or_else(|| self.empty_document_error(index))?;
865        let mut nested = self.rerooted_at(root)?;
866        value.apply_to_yaml_doc(&mut nested)?;
867        self.queue_edits_from(&nested)
868    }
869
870    /// Looks up a mapping entry by key inside `mapping`.
871    ///
872    /// # Errors
873    ///
874    /// Returns an error when a scalar key cannot be decoded.
875    pub fn get_mapping_entry(
876        &self,
877        mapping: NodeId,
878        key: &str,
879    ) -> Result<Option<NodeId>, YamlError> {
880        Ok(self
881            .find_mapping_pair(mapping, key)?
882            .and_then(|(key, _)| self.containing_entry(key)))
883    }
884
885    /// Looks up a mapping value by key inside `mapping`.
886    ///
887    /// # Errors
888    ///
889    /// Returns an error when a scalar key cannot be decoded.
890    pub fn get_mapping_value(
891        &self,
892        mapping: NodeId,
893        key: &str,
894    ) -> Result<Option<NodeId>, YamlError> {
895        Ok(self
896            .find_mapping_pair(mapping, key)?
897            .map(|(_, value)| value))
898    }
899
900    /// Looks up a nested path of mapping keys.
901    ///
902    /// # Errors
903    ///
904    /// Returns an error when semantic path lookup cannot decode a mapping key.
905    pub fn get_path(&self, path: &[&str]) -> Result<Option<NodeId>, YamlError> {
906        self.get_path_in_document(0, path)
907    }
908
909    /// Looks up a nested path of mapping keys in a selected document.
910    ///
911    /// # Errors
912    ///
913    /// Returns an error when semantic path lookup fails while resolving the
914    /// graph path.
915    pub fn get_path_in_document(
916        &self,
917        index: usize,
918        path: &[&str],
919    ) -> Result<Option<NodeId>, YamlError> {
920        let Some((first, rest)) = path.split_first() else {
921            return Ok(None);
922        };
923        let Some((_, mut current)) =
924            self.find_mapping_pair(self.document_root_mapping(index)?, first)?
925        else {
926            return Ok(None);
927        };
928        for segment in rest {
929            let Some((_, value)) = self.find_mapping_pair(current, segment)? else {
930                return Ok(None);
931            };
932            current = value;
933        }
934        Ok(Some(current))
935    }
936
937    fn document_index_error(&self, index: usize) -> YamlError {
938        YamlError::new(
939            Diagnostic::new(
940                DiagnosticKind::Semantic,
941                format!("document index {index} is out of range"),
942                Span::empty_from_usize(self.source.len()),
943            )
944            .with_expected("an existing document index"),
945        )
946    }
947
948    fn empty_document_error(&self, index: usize) -> YamlError {
949        YamlError::new(
950            Diagnostic::new(
951                DiagnosticKind::Typed,
952                format!("document {index} does not contain a YAML value"),
953                Span::empty_from_usize(self.source.len()),
954            )
955            .with_expected("a scalar, sequence, or mapping document root"),
956        )
957    }
958
959    fn find_mapping_pair(
960        &self,
961        mapping: NodeId,
962        key: &str,
963    ) -> Result<Option<(NodeId, NodeId)>, YamlError> {
964        for (key_node, value_node) in self.mapping_entries(mapping) {
965            if self.scalar_value(key_node)? == key {
966                return Ok(Some((key_node, value_node)));
967            }
968        }
969        Ok(None)
970    }
971
972    pub(crate) fn rerooted_at(&self, root: NodeId) -> Result<Self, YamlError> {
973        let root_node = self.expect_node(root)?;
974        if self.semantic_kind(root).is_none() {
975            return Err(YamlError::new(
976                Diagnostic::new(
977                    DiagnosticKind::Semantic,
978                    "typed overlay root does not have semantic metadata",
979                    root_node.span,
980                )
981                .with_expected("a semantic YAML value"),
982            )
983            .with_position_from(&self.source));
984        }
985        let mut doc = self.clone();
986        doc.root_override = Some(root);
987        doc.edits.clear();
988        Ok(doc)
989    }
990
991    pub(crate) fn rerooted_without_tag(&self, root: NodeId) -> Result<Self, YamlError> {
992        let mut doc = self.rerooted_at(root)?;
993        doc.semantics.clear_tag(root);
994        Ok(doc)
995    }
996
997    pub(crate) fn rerooted_at_mapping(&self, mapping: NodeId) -> Result<Self, YamlError> {
998        let mapping_node = self.expect_node(mapping)?;
999        if !matches!(
1000            mapping_node.kind,
1001            NodeKind::BlockMapping | NodeKind::FlowMapping
1002        ) {
1003            return Err(YamlError::new(
1004                Diagnostic::new(
1005                    DiagnosticKind::Semantic,
1006                    format!("expected mapping, found {:?}", mapping_node.kind),
1007                    mapping_node.span,
1008                )
1009                .with_expected("BlockMapping or FlowMapping"),
1010            )
1011            .with_position_from(&self.source));
1012        }
1013        if !matches!(
1014            self.semantic_kind(mapping),
1015            Some(SemanticKind::Mapping { .. })
1016        ) {
1017            return Err(YamlError::new(
1018                Diagnostic::new(
1019                    DiagnosticKind::Semantic,
1020                    "mapping does not have semantic metadata",
1021                    self.expect_node(mapping)?.span,
1022                )
1023                .with_expected("a semantic mapping"),
1024            )
1025            .with_position_from(&self.source));
1026        }
1027        self.rerooted_at(mapping)
1028    }
1029
1030    pub(crate) fn queue_edits_from(&mut self, other: &YamlDoc) -> Result<(), YamlError> {
1031        for edit in &other.edits {
1032            self.queue_edit(edit.span, edit.replacement.clone())?;
1033        }
1034        Ok(())
1035    }
1036
1037    /// Returns the source text for a scalar node.
1038    ///
1039    /// # Errors
1040    ///
1041    /// Returns an error when `node` is unknown or does not identify a plain CST
1042    /// scalar node.
1043    pub fn scalar_text(&self, node: NodeId) -> Result<&str, YamlError> {
1044        let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1045        Ok(self.source.slice(node.span))
1046    }
1047
1048    /// Returns the decoded value text for a scalar node.
1049    ///
1050    /// Plain scalars have trailing inline comments stripped, single-quoted
1051    /// scalars unescape doubled apostrophes, and double-quoted scalars unescape
1052    /// the common JSON/YAML escapes used by typed overlays.
1053    ///
1054    /// # Errors
1055    ///
1056    /// Returns an error when `node` is unknown, is not a scalar node, has
1057    /// malformed node properties, or contains unsupported scalar escape syntax.
1058    pub fn scalar_value(&self, node: NodeId) -> Result<Cow<'_, str>, YamlError> {
1059        let node_ref = self.expect_node(node)?;
1060        if !matches!(
1061            node_ref.kind,
1062            NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1063        ) {
1064            return Err(YamlError::new(
1065                Diagnostic::new(
1066                    DiagnosticKind::Semantic,
1067                    format!("expected scalar value, found {:?}", node_ref.kind),
1068                    node_ref.span,
1069                )
1070                .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1071            )
1072            .with_position_from(&self.source));
1073        }
1074        let text = self.source.slice(node_ref.span);
1075        let properties = parse_node_properties(text, node_ref.span)?;
1076        let value_text = &text[properties.value_start..];
1077        if matches!(
1078            self.semantic_kind(node),
1079            Some(SemanticKind::Scalar {
1080                style: crate::YamlScalarStyle::Plain,
1081                ..
1082            })
1083        ) && !value_text.contains(['\n', '\r'])
1084        {
1085            return Ok(Cow::Borrowed(&value_text[..plain_scalar_end(value_text)]));
1086        }
1087        decode_scalar_value_with_content_indent(
1088            value_text,
1089            self.semantics
1090                .properties(node)
1091                .and_then(|properties| properties.content_indent)
1092                .map(|indent| indent as usize),
1093        )
1094        .map(Cow::Owned)
1095    }
1096
1097    /// Returns the source span of a scalar whose decoded value can be borrowed
1098    /// byte-for-byte from the original input.
1099    ///
1100    /// This currently returns a span only for single-line plain scalars. Quoted,
1101    /// escaped, folded, literal, and multiline scalars require decoding and
1102    /// return `Ok(None)`.
1103    ///
1104    /// # Errors
1105    ///
1106    /// Returns an error when `node` is unknown, is not a scalar, or has malformed
1107    /// node properties.
1108    pub fn borrowable_scalar_span(&self, node: NodeId) -> Result<Option<Span>, YamlError> {
1109        let node_ref = self.expect_node(node)?;
1110        if !matches!(
1111            self.semantic_kind(node),
1112            Some(SemanticKind::Scalar {
1113                style: crate::YamlScalarStyle::Plain,
1114            })
1115        ) {
1116            if matches!(
1117                node_ref.kind,
1118                NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1119            ) {
1120                return Ok(None);
1121            }
1122            return Err(YamlError::new(
1123                Diagnostic::new(
1124                    DiagnosticKind::Semantic,
1125                    format!("expected scalar value, found {:?}", node_ref.kind),
1126                    node_ref.span,
1127                )
1128                .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1129            )
1130            .with_position_from(&self.source));
1131        }
1132
1133        let text = self.source.slice(node_ref.span);
1134        if text.contains(['\n', '\r']) {
1135            return Ok(None);
1136        }
1137        let properties = parse_node_properties(text, node_ref.span)?;
1138        let value_text = &text[properties.value_start..];
1139        let value_len = plain_scalar_end(value_text);
1140        let start = Span::offset_from_usize(node_ref.span.start, properties.value_start);
1141        Ok(Some(Span::new(
1142            start,
1143            Span::offset_from_usize(start, value_len),
1144        )))
1145    }
1146
1147    /// Queues a scalar value replacement at `path` while preserving the existing
1148    /// scalar style where the editor can do so safely.
1149    ///
1150    /// Plain scalars remain plain, single-quoted scalars remain single-quoted,
1151    /// and double-quoted scalars remain double-quoted. Inline comments and
1152    /// trailing whitespace outside the scalar spelling are left untouched.
1153    ///
1154    /// # Errors
1155    ///
1156    /// Returns an error when `path` does not resolve to an existing scalar, the
1157    /// current scalar style cannot be rewritten safely, `value` cannot be
1158    /// represented in that style, or the queued edit conflicts with another
1159    /// pending edit.
1160    pub fn set_scalar(&mut self, path: &[&str], value: &str) -> Result<(), YamlError> {
1161        let node = self.get_path(path)?.ok_or_else(|| {
1162            YamlError::new(
1163                Diagnostic::new(
1164                    DiagnosticKind::Semantic,
1165                    format!("path `{}` does not exist", path.join(".")),
1166                    Span::empty(0),
1167                )
1168                .with_expected("an existing scalar node"),
1169            )
1170        })?;
1171
1172        let (span, style) = self.scalar_replacement_target(node)?;
1173        let replacement = format_scalar_value(value, style)?;
1174        self.queue_edit(span, replacement)
1175    }
1176
1177    /// Queues a patch that replaces the exact source span covered by `node`.
1178    ///
1179    /// The CST remains unchanged until the edited text is parsed again; callers
1180    /// can inspect the pending minimal-diff output through `doc.to_string()`.
1181    ///
1182    /// # Errors
1183    ///
1184    /// Returns an error when `node` is unknown, `text` contains invalid YAML
1185    /// characters, or the replacement overlaps an existing pending edit.
1186    pub fn replace_node_text(
1187        &mut self,
1188        node: NodeId,
1189        text: impl Into<String>,
1190    ) -> Result<(), YamlError> {
1191        let span = self.expect_node(node)?.span;
1192        self.queue_edit(span, text.into())
1193    }
1194
1195    /// Queues insertion of a plain `key: value` entry into a block mapping.
1196    ///
1197    /// This low-level writer accepts raw plain scalar text. Use typed values or
1198    /// node fragments when quoting or schema-aware formatting is required.
1199    ///
1200    /// # Errors
1201    ///
1202    /// Returns an error when `mapping` is not a block mapping, `key` or `value`
1203    /// is not valid as a plain mapping fragment, or the insertion conflicts with
1204    /// another pending edit.
1205    pub fn insert_mapping_entry(
1206        &mut self,
1207        mapping: NodeId,
1208        key: &str,
1209        value: &str,
1210        style: MappingEntryStyle,
1211    ) -> Result<(), YamlError> {
1212        self.insert_mapping_entry_with_comment(mapping, key, value, style, None)
1213    }
1214
1215    /// Queues insertion of a plain `key: value` entry with optional preceding
1216    /// comment lines.
1217    ///
1218    /// Comments are emitted only for inserted entries; existing YAML comments are
1219    /// never overwritten by this helper.
1220    ///
1221    /// # Errors
1222    ///
1223    /// Returns an error when `mapping` is not a block mapping, `key`, `value`, or
1224    /// `comment` cannot be emitted as valid YAML text, or the insertion conflicts
1225    /// with another pending edit.
1226    pub fn insert_mapping_entry_with_comment(
1227        &mut self,
1228        mapping: NodeId,
1229        key: &str,
1230        value: &str,
1231        style: MappingEntryStyle,
1232        comment: Option<&str>,
1233    ) -> Result<(), YamlError> {
1234        let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1235        let indent = match style {
1236            MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1237            MappingEntryStyle::Indent(indent) => indent,
1238        };
1239        let insertion_offset = self.mapping_insertion_offset(mapping_node);
1240        let needs_leading_break =
1241            insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1242        let preserve_paragraph_break = comment.is_some()
1243            && insertion_offset == self.source.len()
1244            && self.mapping_has_blank_line(mapping_node);
1245        let replacement = self.format_mapping_entry_replacement(
1246            indent,
1247            key,
1248            value,
1249            comment,
1250            needs_leading_break,
1251            preserve_paragraph_break,
1252        )?;
1253
1254        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1255    }
1256
1257    /// Queues insertion of a typed YAML value under `key` in a block mapping.
1258    ///
1259    /// # Errors
1260    ///
1261    /// Returns an error when `mapping` is not a block mapping, the value cannot
1262    /// be formatted as a block YAML fragment, or the insertion conflicts with an
1263    /// existing pending edit.
1264    pub fn insert_mapping_value_with_comment<T>(
1265        &mut self,
1266        mapping: NodeId,
1267        key: &str,
1268        value: &T,
1269        style: MappingEntryStyle,
1270        comment: Option<&str>,
1271    ) -> Result<(), YamlError>
1272    where
1273        T: ToYamlFragment,
1274    {
1275        if matches!(
1276            self.semantic_kind(mapping),
1277            Some(SemanticKind::Mapping {
1278                style: CollectionStyle::Flow
1279            })
1280        ) {
1281            let fragment = self.typed_value_fragment(value)?;
1282            return self
1283                .queue_mapping_insert(mapping, key, &fragment)
1284                .map_err(YamlEditError::into_yaml_error);
1285        }
1286        let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1287        let indent = match style {
1288            MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1289            MappingEntryStyle::Indent(indent) => indent,
1290        };
1291        let insertion_offset = self.mapping_insertion_offset(mapping_node);
1292        let needs_leading_break =
1293            insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1294        let preserve_paragraph_break = comment.is_some()
1295            && insertion_offset == self.source.len()
1296            && self.mapping_has_blank_line(mapping_node);
1297        let replacement = self.format_mapping_value_replacement(
1298            indent,
1299            key,
1300            value,
1301            comment,
1302            needs_leading_break,
1303            preserve_paragraph_break,
1304        )?;
1305
1306        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1307    }
1308
1309    fn typed_value_fragment<T>(&self, value: &T) -> Result<YamlFragment, YamlError>
1310    where
1311        T: ToYamlFragment,
1312    {
1313        let yaml = value.to_yaml_fragment(0, self.preferred_line_ending())?;
1314        YamlFragment::parse(&yaml).map_err(|error| {
1315            YamlError::new(
1316                Diagnostic::new(
1317                    DiagnosticKind::Emitter,
1318                    format!("typed YAML fragment is invalid: {error}"),
1319                    Span::empty(0),
1320                )
1321                .with_expected("one valid YAML value"),
1322            )
1323        })
1324    }
1325
1326    /// Queues insertion of a typed YAML value according to declaration order.
1327    ///
1328    /// # Errors
1329    ///
1330    /// Returns an error when mapping lookup fails or the selected insertion
1331    /// cannot be formatted or queued.
1332    pub fn insert_mapping_value_ordered_with_comment<T>(
1333        &mut self,
1334        mapping: NodeId,
1335        key: &str,
1336        value: &T,
1337        style: MappingEntryStyle,
1338        comment: Option<&str>,
1339        ordered_keys: &[&str],
1340    ) -> Result<(), YamlError>
1341    where
1342        T: ToYamlFragment,
1343    {
1344        let mut next_entry = None;
1345        if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1346            for later_key in &ordered_keys[position + 1..] {
1347                if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1348                    next_entry = Some(entry);
1349                    break;
1350                }
1351            }
1352        }
1353
1354        if let Some(next_entry) = next_entry {
1355            self.insert_mapping_value_before_with_comment(next_entry, key, value, style, comment)
1356        } else {
1357            self.insert_mapping_value_with_comment(mapping, key, value, style, comment)
1358        }
1359    }
1360
1361    /// Queues insertion of a plain `key: value` entry before `before_entry`.
1362    ///
1363    /// # Errors
1364    ///
1365    /// Returns an error when `before_entry` is not a mapping entry, `key`,
1366    /// `value`, or `comment` cannot be emitted as valid YAML text, or the
1367    /// insertion conflicts with another pending edit.
1368    pub fn insert_mapping_entry_before_with_comment(
1369        &mut self,
1370        before_entry: NodeId,
1371        key: &str,
1372        value: &str,
1373        style: MappingEntryStyle,
1374        comment: Option<&str>,
1375    ) -> Result<(), YamlError> {
1376        let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1377        let indent = match style {
1378            MappingEntryStyle::Inherit => self.node_indent(before_node),
1379            MappingEntryStyle::Indent(indent) => indent,
1380        };
1381        let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1382        let replacement =
1383            self.format_mapping_entry_replacement(indent, key, value, comment, false, false)?;
1384
1385        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1386    }
1387
1388    /// Queues insertion of a typed YAML value before an existing mapping entry.
1389    ///
1390    /// # Errors
1391    ///
1392    /// Returns an error when the insertion target is invalid, the value cannot
1393    /// be formatted, or the insertion conflicts with an existing pending edit.
1394    pub fn insert_mapping_value_before_with_comment<T>(
1395        &mut self,
1396        before_entry: NodeId,
1397        key: &str,
1398        value: &T,
1399        style: MappingEntryStyle,
1400        comment: Option<&str>,
1401    ) -> Result<(), YamlError>
1402    where
1403        T: ToYamlFragment,
1404    {
1405        let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1406        let mapping = before_node.parent().ok_or_else(|| {
1407            YamlError::new(
1408                Diagnostic::new(
1409                    DiagnosticKind::Semantic,
1410                    "mapping entry has no parent mapping",
1411                    before_node.span,
1412                )
1413                .with_expected("a mapping parent"),
1414            )
1415        })?;
1416        if matches!(
1417            self.semantic_kind(mapping),
1418            Some(SemanticKind::Mapping {
1419                style: CollectionStyle::Flow
1420            })
1421        ) {
1422            let fragment = self.typed_value_fragment(value)?;
1423            return self
1424                .queue_mapping_insert_before(mapping, before_entry, key, &fragment)
1425                .map_err(YamlEditError::into_yaml_error);
1426        }
1427        let indent = match style {
1428            MappingEntryStyle::Inherit => self.node_indent(before_node),
1429            MappingEntryStyle::Indent(indent) => indent,
1430        };
1431        let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1432        let replacement =
1433            self.format_mapping_value_replacement(indent, key, value, comment, false, false)?;
1434
1435        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1436    }
1437
1438    /// Queues insertion according to a declaration-order key list.
1439    ///
1440    /// If a later key from `ordered_keys` already exists in `mapping`, the new
1441    /// entry is inserted before that entry. Otherwise this falls back to append
1442    /// insertion. This is the primitive behind `insert_order = "struct"`.
1443    ///
1444    /// # Errors
1445    ///
1446    /// Returns an error when mapping lookup fails, the selected insertion target
1447    /// has the wrong node kind, inserted text is invalid YAML, or the queued edit
1448    /// conflicts with another pending edit.
1449    pub fn insert_mapping_entry_ordered_with_comment(
1450        &mut self,
1451        mapping: NodeId,
1452        key: &str,
1453        value: &str,
1454        style: MappingEntryStyle,
1455        comment: Option<&str>,
1456        ordered_keys: &[&str],
1457    ) -> Result<(), YamlError> {
1458        let mut next_entry = None;
1459        if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1460            for later_key in &ordered_keys[position + 1..] {
1461                if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1462                    next_entry = Some(entry);
1463                    break;
1464                }
1465            }
1466        }
1467
1468        if let Some(next_entry) = next_entry {
1469            self.insert_mapping_entry_before_with_comment(next_entry, key, value, style, comment)
1470        } else {
1471            self.insert_mapping_entry_with_comment(mapping, key, value, style, comment)
1472        }
1473    }
1474
1475    /// Queues removal of the mapping entry with `key` from `mapping` when it exists.
1476    ///
1477    /// The removal is line-wise, so comments and fields outside the selected entry
1478    /// remain byte-for-byte unchanged. Missing keys are a no-op.
1479    ///
1480    /// # Errors
1481    ///
1482    /// Returns an error when mapping lookup fails, the selected entry cannot be
1483    /// removed, or the removal overlaps an existing pending edit.
1484    pub fn remove_mapping_entry(&mut self, mapping: NodeId, key: &str) -> Result<(), YamlError> {
1485        let Some(entry) = self.get_mapping_entry(mapping, key)? else {
1486            return Ok(());
1487        };
1488        self.remove_collection_entries(mapping, &[entry])
1489    }
1490
1491    /// Queues line-wise removal edits for mapping entries whose keys are not allowed.
1492    ///
1493    /// This is the patch-emitter primitive used by typed overlays that choose to
1494    /// prune unknown fields. It preserves the order and bytes of retained entries.
1495    ///
1496    /// # Errors
1497    ///
1498    /// Returns an error when `mapping` is not a block mapping, a retained entry
1499    /// cannot be inspected as a scalar key, or a removal edit conflicts with
1500    /// another pending edit.
1501    pub fn retain_mapping_entries(
1502        &mut self,
1503        mapping: NodeId,
1504        allowed_keys: &[&str],
1505    ) -> Result<(), YamlError> {
1506        let mapping_node = self.expect_node(mapping)?;
1507        if !matches!(
1508            mapping_node.kind,
1509            NodeKind::BlockMapping | NodeKind::FlowMapping
1510        ) {
1511            return Err(YamlError::new(
1512                Diagnostic::new(
1513                    DiagnosticKind::Semantic,
1514                    format!("expected mapping, found {:?}", mapping_node.kind),
1515                    mapping_node.span,
1516                )
1517                .with_expected("BlockMapping or FlowMapping"),
1518            ));
1519        }
1520        let mut removals = Vec::new();
1521
1522        for entry in self.children(mapping) {
1523            let entry_node = self.expect_node(entry)?;
1524            if entry_node.kind != NodeKind::MappingEntry {
1525                continue;
1526            }
1527
1528            let Some(key_node) = self.children(entry).next() else {
1529                continue;
1530            };
1531            let key = self.scalar_value(key_node)?;
1532            if !allowed_keys.contains(&key.as_ref()) {
1533                removals.push(entry);
1534            }
1535        }
1536
1537        self.remove_collection_entries(mapping, &removals)
1538    }
1539
1540    pub(crate) fn remove_collection_entries(
1541        &mut self,
1542        collection: NodeId,
1543        removals: &[NodeId],
1544    ) -> Result<(), YamlError> {
1545        if removals.is_empty() {
1546            return Ok(());
1547        }
1548        let Some(style) = (match self.semantic_kind(collection) {
1549            Some(SemanticKind::Mapping { style } | SemanticKind::Sequence { style }) => Some(style),
1550            _ => None,
1551        }) else {
1552            return Err(YamlError::new(
1553                Diagnostic::new(
1554                    DiagnosticKind::Semantic,
1555                    "collection entry removal target is not a mapping or sequence",
1556                    self.expect_node(collection)?.span,
1557                )
1558                .with_expected("a mapping or sequence"),
1559            ));
1560        };
1561        if style == CollectionStyle::Block {
1562            for entry in removals {
1563                self.remove_node(*entry)?;
1564            }
1565            return Ok(());
1566        }
1567
1568        let entries = self
1569            .children(collection)
1570            .filter(|node| {
1571                self.node(*node).is_some_and(|node| {
1572                    matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1573                })
1574            })
1575            .collect::<Vec<_>>();
1576        if removals.len() == entries.len() {
1577            let collection_node = self.expect_node(collection)?;
1578            let delimiter = match collection_node.kind {
1579                NodeKind::FlowMapping => '}',
1580                NodeKind::FlowSequence => ']',
1581                _ => unreachable!("flow semantic collection must have a flow CST node"),
1582            };
1583            if let Some(relative) = self.source.slice(collection_node.span).rfind(delimiter) {
1584                let close = Span::offset_from_usize(collection_node.span.start, relative);
1585                if let Some(edit) = self
1586                    .edits
1587                    .iter_mut()
1588                    .find(|edit| edit.span == Span::empty(close))
1589                    && let Some(replacement) = edit.replacement.strip_prefix(", ")
1590                {
1591                    edit.replacement = replacement.to_owned();
1592                }
1593            }
1594        }
1595        let mut index = 0;
1596        while index < entries.len() {
1597            if !removals.contains(&entries[index]) {
1598                index += 1;
1599                continue;
1600            }
1601            let start = index;
1602            while index < entries.len() && removals.contains(&entries[index]) {
1603                index += 1;
1604            }
1605            let end = index;
1606            let first = self.expect_node(entries[start])?.span;
1607            let last = self.expect_node(entries[end - 1])?.span;
1608            let span = if let Some(next) = entries.get(end).copied() {
1609                Span::new(first.start, self.expect_node(next)?.span.start)
1610            } else if start > 0 {
1611                Span::new(self.expect_node(entries[start - 1])?.span.end, last.end)
1612            } else {
1613                Span::new(first.start, last.end)
1614            };
1615            self.queue_edit(span, String::new())?;
1616        }
1617        Ok(())
1618    }
1619
1620    /// Queues removal of `node` from the rendered document.
1621    ///
1622    /// Mapping and sequence entries are removed line-wise, including their line
1623    /// break when one is present. Other nodes use their exact source span.
1624    ///
1625    /// # Errors
1626    ///
1627    /// Returns an error when `node` is unknown or the removal overlaps an
1628    /// existing pending edit.
1629    pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1630        let node = self.expect_node(node)?;
1631        let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1632            self.line_span_including_break(node.span)
1633        } else {
1634            node.span
1635        };
1636        self.queue_edit(span, String::new())
1637    }
1638
1639    pub(crate) fn scalar_replacement_target(
1640        &self,
1641        node: NodeId,
1642    ) -> Result<(Span, ScalarStyle), YamlError> {
1643        let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1644        let text = self.source.slice(node.span);
1645        let properties = parse_node_properties(text, node.span)?;
1646        let value_text = &text[properties.value_start..];
1647        let value_start = Span::offset_from_usize(node.span.start, properties.value_start);
1648
1649        if value_text.starts_with('"') {
1650            let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1651                YamlError::new(
1652                    Diagnostic::new(
1653                        DiagnosticKind::Emitter,
1654                        "could not find the end of the double-quoted scalar",
1655                        node.span,
1656                    )
1657                    .with_expected("a closed double-quoted scalar"),
1658                )
1659            })?;
1660            return Ok((
1661                Span::new(value_start, Span::offset_from_usize(value_start, end)),
1662                ScalarStyle::DoubleQuoted,
1663            ));
1664        }
1665
1666        if value_text.starts_with('\'') {
1667            let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1668                YamlError::new(
1669                    Diagnostic::new(
1670                        DiagnosticKind::Emitter,
1671                        "could not find the end of the single-quoted scalar",
1672                        node.span,
1673                    )
1674                    .with_expected("a closed single-quoted scalar"),
1675                )
1676            })?;
1677            return Ok((
1678                Span::new(value_start, Span::offset_from_usize(value_start, end)),
1679                ScalarStyle::SingleQuoted,
1680            ));
1681        }
1682
1683        let end = plain_scalar_end(value_text);
1684        if end == 0 {
1685            return Err(YamlError::new(
1686                Diagnostic::new(
1687                    DiagnosticKind::Emitter,
1688                    "could not find plain scalar text to replace",
1689                    node.span,
1690                )
1691                .with_expected("plain scalar text"),
1692            ));
1693        }
1694
1695        Ok((
1696            Span::new(value_start, Span::offset_from_usize(value_start, end)),
1697            ScalarStyle::Plain,
1698        ))
1699    }
1700
1701    fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1702        self.root()
1703            .into_iter()
1704            .flat_map(|root| self.children(root))
1705            .filter(|node| {
1706                self.node(*node)
1707                    .is_some_and(|node| node.kind == NodeKind::Directive)
1708            })
1709    }
1710
1711    fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1712        let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1713        let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1714        let mut parts = body.split_whitespace();
1715        let Some(name) = parts.next() else {
1716            return Err(directive_emit_error(
1717                "directive is missing a name",
1718                node_ref.span,
1719                "%YAML, %TAG, or reserved directive syntax",
1720            )
1721            .with_position_from(&self.source));
1722        };
1723
1724        Ok(match name {
1725            "%YAML" => ParsedDirective::Yaml(YamlDirective {
1726                version: parts.next().unwrap_or_default().to_owned(),
1727                node,
1728            }),
1729            "%TAG" => ParsedDirective::Tag(TagDirective {
1730                handle: parts.next().unwrap_or_default().to_owned(),
1731                prefix: parts.next().unwrap_or_default().to_owned(),
1732                node,
1733            }),
1734            _ => ParsedDirective::Reserved(ReservedDirective {
1735                name: name.to_owned(),
1736                parameters: parts.map(str::to_owned).collect(),
1737                node,
1738            }),
1739        })
1740    }
1741
1742    fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1743        let node = self.expect_node_kind(node, NodeKind::Directive)?;
1744        let text = self.source.slice(node.span);
1745        let end = strip_inline_comment(text)
1746            .trim_end_matches([' ', '\t'])
1747            .len();
1748        Ok(Span::new(
1749            node.span.start,
1750            Span::offset_from_usize(node.span.start, end),
1751        ))
1752    }
1753
1754    fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1755        let insertion_offset = self.directive_insertion_offset();
1756        let mut line = replacement;
1757        line.push_str(self.preferred_line_ending());
1758        self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1759    }
1760
1761    fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1762        let node = self.expect_node_kind(node, NodeKind::Directive)?;
1763        self.queue_edit(self.line_span_including_break(node.span), String::new())
1764    }
1765
1766    fn directive_insertion_offset(&self) -> usize {
1767        if let Some(last_directive) = self
1768            .directive_nodes()
1769            .filter_map(|node| self.node(node))
1770            .max_by_key(|node| node.span.start)
1771        {
1772            return self.line_span_including_break(last_directive.span).end as usize;
1773        }
1774
1775        self.root()
1776            .and_then(|root| self.children(root).next())
1777            .and_then(|node| self.node(node))
1778            .map_or(0, |node| {
1779                self.line_start_for_offset(node.span.start as usize)
1780            })
1781    }
1782
1783    pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1784        self.node(node).ok_or_else(|| {
1785            YamlError::new(Diagnostic::new(
1786                DiagnosticKind::Semantic,
1787                format!("unknown node id {}", node.0),
1788                Span::empty_from_usize(self.source.len()),
1789            ))
1790        })
1791    }
1792
1793    pub(crate) fn expect_node_kind(
1794        &self,
1795        node: NodeId,
1796        expected: NodeKind,
1797    ) -> Result<&Node, YamlError> {
1798        let actual = self.expect_node(node)?;
1799        if actual.kind == expected {
1800            Ok(actual)
1801        } else {
1802            Err(YamlError::new(
1803                Diagnostic::new(
1804                    DiagnosticKind::Semantic,
1805                    format!("expected {expected:?}, found {:?}", actual.kind),
1806                    actual.span,
1807                )
1808                .with_expected(format!("{expected:?}")),
1809            )
1810            .with_position_from(&self.source))
1811        }
1812    }
1813
1814    pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1815        self.node(value).and_then(Node::parent).filter(|parent| {
1816            self.node(*parent).is_some_and(|node| {
1817                matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1818            })
1819        })
1820    }
1821
1822    fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1823        let start = self.line_start_for_offset(mapping.span.start as usize);
1824        let end = mapping.span.end as usize;
1825        let text = &self.source.as_str()[start..end];
1826        text.contains("\n\n") || text.contains("\r\n\r\n")
1827    }
1828
1829    fn format_mapping_entry_replacement(
1830        &self,
1831        indent: usize,
1832        key: &str,
1833        value: &str,
1834        comment: Option<&str>,
1835        needs_leading_break: bool,
1836        preserve_paragraph_break: bool,
1837    ) -> Result<String, YamlError> {
1838        validate_plain_mapping_fragment(key, "mapping key")?;
1839        validate_plain_mapping_fragment(value, "mapping value")?;
1840        if let Some(comment) = comment {
1841            validate_yaml_chars(comment)?;
1842        }
1843
1844        let line_ending = self.preferred_line_ending();
1845        let indent_text = " ".repeat(indent);
1846        let mut replacement = String::new();
1847        if needs_leading_break {
1848            replacement.push_str(line_ending);
1849        }
1850        if preserve_paragraph_break {
1851            replacement.push_str(line_ending);
1852        }
1853        if let Some(comment) = comment {
1854            for line in comment.lines() {
1855                replacement.push_str(&indent_text);
1856                replacement.push('#');
1857                if !line.is_empty() {
1858                    replacement.push(' ');
1859                    replacement.push_str(line.trim());
1860                }
1861                replacement.push_str(line_ending);
1862            }
1863        }
1864        replacement.push_str(&indent_text);
1865        replacement.push_str(key);
1866        replacement.push_str(": ");
1867        replacement.push_str(value);
1868        replacement.push_str(line_ending);
1869        Ok(replacement)
1870    }
1871
1872    fn format_mapping_value_replacement<T>(
1873        &self,
1874        indent: usize,
1875        key: &str,
1876        value: &T,
1877        comment: Option<&str>,
1878        needs_leading_break: bool,
1879        preserve_paragraph_break: bool,
1880    ) -> Result<String, YamlError>
1881    where
1882        T: ToYamlFragment,
1883    {
1884        validate_yaml_chars(key)?;
1885        if let Some(comment) = comment {
1886            validate_yaml_chars(comment)?;
1887        }
1888
1889        let line_ending = self.preferred_line_ending();
1890        let indent_text = " ".repeat(indent);
1891        let child_indent = indent + 2;
1892        let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
1893        let mut replacement = String::new();
1894        if needs_leading_break {
1895            replacement.push_str(line_ending);
1896        }
1897        if preserve_paragraph_break {
1898            replacement.push_str(line_ending);
1899        }
1900        if let Some(comment) = comment {
1901            for line in comment.lines() {
1902                replacement.push_str(&indent_text);
1903                replacement.push('#');
1904                if !line.is_empty() {
1905                    replacement.push(' ');
1906                    replacement.push_str(line.trim());
1907                }
1908                replacement.push_str(line_ending);
1909            }
1910        }
1911        replacement.push_str(&indent_text);
1912        replacement.push_str(&crate::edit::emit_string_key(key));
1913        if fragment.contains('\n') || fragment.starts_with(' ') {
1914            replacement.push(':');
1915            replacement.push_str(line_ending);
1916            replacement.push_str(&fragment);
1917        } else {
1918            replacement.push_str(": ");
1919            replacement.push_str(&fragment);
1920        }
1921        replacement.push_str(line_ending);
1922        Ok(replacement)
1923    }
1924
1925    pub(crate) fn node_indent(&self, node: &Node) -> usize {
1926        let line_start = self.line_start_for_offset(node.span.start as usize);
1927        self.source.as_str()[line_start..node.span.start as usize]
1928            .bytes()
1929            .filter(|byte| *byte == b' ')
1930            .count()
1931    }
1932
1933    fn line_start_for_offset(&self, offset: usize) -> usize {
1934        let offset = Span::usize_to_u32(offset);
1935        match self.source.line_starts().binary_search(&offset) {
1936            Ok(index) => self.source.line_starts()[index] as usize,
1937            Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
1938        }
1939    }
1940
1941    pub(crate) fn find_nested_collection_after(
1942        &self,
1943        entry: &Node,
1944        parent_indent: usize,
1945    ) -> Option<NodeId> {
1946        self.nodes
1947            .iter()
1948            .enumerate()
1949            .filter(|(_, node)| {
1950                matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
1951                    && node.span.start >= entry.span.end
1952                    && self.node_indent(node) > parent_indent
1953            })
1954            .min_by_key(|(_, node)| node.span.start)
1955            .map(|(index, _)| NodeId::from_usize(index))
1956    }
1957
1958    pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
1959        let text = self.source.slice(scalar.span);
1960        let header_end = text.find(['\r', '\n'])?;
1961        let mut rest = &text[header_end..];
1962        while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
1963            rest = stripped;
1964        }
1965        for line in rest.lines() {
1966            if line.trim().is_empty() {
1967                continue;
1968            }
1969            return Some(line.bytes().take_while(|byte| *byte == b' ').count());
1970        }
1971        None
1972    }
1973
1974    pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
1975        self.source.try_slice(span)?;
1976        validate_yaml_chars(&replacement)?;
1977
1978        if span.is_empty()
1979            && let Some(existing) = self
1980                .edits
1981                .iter_mut()
1982                .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
1983        {
1984            existing.replacement.push_str(&replacement);
1985            return Ok(());
1986        }
1987
1988        if let Some(existing) = self
1989            .edits
1990            .iter()
1991            .find(|edit| edits_conflict(edit.span, span))
1992        {
1993            return Err(YamlError::new(
1994                Diagnostic::new(
1995                    DiagnosticKind::Emitter,
1996                    "edit overlaps an existing pending edit",
1997                    span,
1998                )
1999                .with_note(format!(
2000                    "existing edit covers bytes {}..{}",
2001                    existing.span.start, existing.span.end
2002                )),
2003            )
2004            .with_position_from(&self.source));
2005        }
2006
2007        self.edits.push(Edit { span, replacement });
2008        Ok(())
2009    }
2010
2011    pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2012        node_link(mapping.last_child)
2013            .and_then(|child| self.node(child))
2014            .map_or(mapping.span.end as usize, |last_child| {
2015                self.line_span_including_break(last_child.span).end as usize
2016            })
2017    }
2018
2019    pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2020        node_link(sequence.last_child)
2021            .and_then(|child| self.node(child))
2022            .map_or(sequence.span.end as usize, |last_child| {
2023                self.line_span_including_break(last_child.span).end as usize
2024            })
2025    }
2026
2027    fn line_span_including_break(&self, span: Span) -> Span {
2028        let start = self.line_start_for_offset(span.start as usize);
2029        let mut end = span.end as usize;
2030        let bytes = self.source.as_str().as_bytes();
2031
2032        if end < bytes.len() {
2033            if bytes[end] == b'\r' {
2034                end += 1;
2035                if end < bytes.len() && bytes[end] == b'\n' {
2036                    end += 1;
2037                }
2038            } else if bytes[end] == b'\n' {
2039                end += 1;
2040            }
2041        }
2042
2043        Span::from_usize(start, end)
2044    }
2045
2046    pub(crate) fn preferred_line_ending(&self) -> &str {
2047        let bytes = self.source.as_str().as_bytes();
2048        for (index, byte) in bytes.iter().enumerate() {
2049            if *byte == b'\r' {
2050                return if bytes.get(index + 1) == Some(&b'\n') {
2051                    "\r\n"
2052                } else {
2053                    "\r"
2054                };
2055            }
2056            if *byte == b'\n' {
2057                return if index > 0 && bytes[index - 1] == b'\r' {
2058                    "\r\n"
2059                } else {
2060                    "\n"
2061                };
2062            }
2063        }
2064        "\n"
2065    }
2066
2067    fn source_ends_with_line_break(&self) -> bool {
2068        self.source
2069            .as_str()
2070            .as_bytes()
2071            .last()
2072            .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2073    }
2074
2075    fn document_append_prefix(&self, line_ending: &str) -> String {
2076        if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2077            String::new()
2078        } else {
2079            line_ending.to_owned()
2080        }
2081    }
2082}
2083impl fmt::Display for YamlDoc {
2084    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2085        if self.edits.is_empty() {
2086            return formatter.write_str(self.source.as_str());
2087        }
2088
2089        let mut output = self.source.as_str().to_owned();
2090        let mut edits = self.edits.clone();
2091        edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2092
2093        for edit in edits {
2094            output.replace_range(
2095                edit.span.start as usize..edit.span.end as usize,
2096                &edit.replacement,
2097            );
2098        }
2099
2100        formatter.write_str(&output)
2101    }
2102}