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    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    /// Returns an anchor name without its leading `&`.
656    #[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    /// Returns an alias name without its leading `*`.
663    #[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    /// Resolves an alias to the most recent matching anchor in its document.
670    #[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    /// Iterates over semantic document CST nodes in stream order.
721    pub fn documents(&self) -> impl Iterator<Item = NodeId> + '_ {
722        self.semantics.documents.iter().copied()
723    }
724
725    /// Returns the semantic root value of a selected document.
726    ///
727    /// Empty documents have no root value and return `Ok(None)`. Unlike
728    /// [`YamlDoc::document_root_mapping`], this method accepts scalar, sequence,
729    /// mapping, and alias roots.
730    ///
731    /// # Errors
732    ///
733    /// Returns an error when `index` is outside the YAML stream.
734    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    /// Iterates over mapping key/value CST node pairs in source order.
754    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    /// Iterates over sequence item CST nodes in source order.
769    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    /// Returns the root-level mapping in the document.
783    ///
784    /// # Errors
785    ///
786    /// Returns an error when no root mapping exists or when the semantic
787    /// root mapping is not linked back to a CST node.
788    pub fn root_mapping(&self) -> Result<NodeId, YamlError> {
789        self.document_root_mapping(0)
790    }
791
792    /// Returns the root-level mapping in a selected document.
793    ///
794    /// # Errors
795    ///
796    /// Returns an error when the selected document does not exist, has no
797    /// mapping root, or the root mapping is not linked back to the CST.
798    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).is_some_and(|node| {
813                    matches!(node.kind, NodeKind::BlockMapping | NodeKind::FlowMapping)
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 or flow mapping node"),
827                )
828            })
829    }
830
831    /// Reads a typed overlay from a selected document.
832    ///
833    /// # Errors
834    ///
835    /// Returns an error when the selected document is missing or empty, or the
836    /// typed overlay cannot be read.
837    pub fn read_document<T>(&self, index: usize) -> Result<T, YamlError>
838    where
839        T: FromYamlDoc,
840    {
841        let root = self
842            .document_root(index)?
843            .ok_or_else(|| self.empty_document_error(index))?;
844        let nested = self.rerooted_at(root)?;
845        T::from_yaml_doc(&nested)
846    }
847
848    /// Writes a typed overlay to a selected document.
849    ///
850    /// # Errors
851    ///
852    /// Returns an error when the selected document is missing or empty, or the
853    /// typed overlay cannot be written.
854    pub fn write_document<T>(&mut self, index: usize, value: &T) -> Result<(), YamlError>
855    where
856        T: ToYamlDoc,
857    {
858        let root = self
859            .document_root(index)?
860            .ok_or_else(|| self.empty_document_error(index))?;
861        let mut nested = self.rerooted_at(root)?;
862        value.apply_to_yaml_doc(&mut nested)?;
863        self.queue_edits_from(&nested)
864    }
865
866    /// Looks up a mapping entry by key inside `mapping`.
867    ///
868    /// # Errors
869    ///
870    /// Returns an error when a scalar key cannot be decoded.
871    pub fn get_mapping_entry(
872        &self,
873        mapping: NodeId,
874        key: &str,
875    ) -> Result<Option<NodeId>, YamlError> {
876        Ok(self
877            .find_mapping_pair(mapping, key)?
878            .and_then(|(key, _)| self.containing_entry(key)))
879    }
880
881    /// Looks up a mapping value by key inside `mapping`.
882    ///
883    /// # Errors
884    ///
885    /// Returns an error when a scalar key cannot be decoded.
886    pub fn get_mapping_value(
887        &self,
888        mapping: NodeId,
889        key: &str,
890    ) -> Result<Option<NodeId>, YamlError> {
891        Ok(self
892            .find_mapping_pair(mapping, key)?
893            .map(|(_, value)| value))
894    }
895
896    /// Looks up a nested path of mapping keys.
897    ///
898    /// # Errors
899    ///
900    /// Returns an error when semantic path lookup cannot decode a mapping key.
901    pub fn get_path(&self, path: &[&str]) -> Result<Option<NodeId>, YamlError> {
902        self.get_path_in_document(0, path)
903    }
904
905    /// Looks up a nested path of mapping keys in a selected document.
906    ///
907    /// # Errors
908    ///
909    /// Returns an error when semantic path lookup fails while resolving the
910    /// graph path.
911    pub fn get_path_in_document(
912        &self,
913        index: usize,
914        path: &[&str],
915    ) -> Result<Option<NodeId>, YamlError> {
916        let Some((first, rest)) = path.split_first() else {
917            return Ok(None);
918        };
919        let Some((_, mut current)) =
920            self.find_mapping_pair(self.document_root_mapping(index)?, first)?
921        else {
922            return Ok(None);
923        };
924        for segment in rest {
925            let Some((_, value)) = self.find_mapping_pair(current, segment)? else {
926                return Ok(None);
927            };
928            current = value;
929        }
930        Ok(Some(current))
931    }
932
933    fn document_index_error(&self, index: usize) -> YamlError {
934        YamlError::new(
935            Diagnostic::new(
936                DiagnosticKind::Semantic,
937                format!("document index {index} is out of range"),
938                Span::empty_from_usize(self.source.len()),
939            )
940            .with_expected("an existing document index"),
941        )
942    }
943
944    fn empty_document_error(&self, index: usize) -> YamlError {
945        YamlError::new(
946            Diagnostic::new(
947                DiagnosticKind::Typed,
948                format!("document {index} does not contain a YAML value"),
949                Span::empty_from_usize(self.source.len()),
950            )
951            .with_expected("a scalar, sequence, or mapping document root"),
952        )
953    }
954
955    fn find_mapping_pair(
956        &self,
957        mapping: NodeId,
958        key: &str,
959    ) -> Result<Option<(NodeId, NodeId)>, YamlError> {
960        for (key_node, value_node) in self.mapping_entries(mapping) {
961            if self.scalar_value(key_node)? == key {
962                return Ok(Some((key_node, value_node)));
963            }
964        }
965        Ok(None)
966    }
967
968    pub(crate) fn rerooted_at(&self, root: NodeId) -> Result<Self, YamlError> {
969        let root_node = self.expect_node(root)?;
970        if self.semantic_kind(root).is_none() {
971            return Err(YamlError::new(
972                Diagnostic::new(
973                    DiagnosticKind::Semantic,
974                    "typed overlay root does not have semantic metadata",
975                    root_node.span,
976                )
977                .with_expected("a semantic YAML value"),
978            )
979            .with_position_from(&self.source));
980        }
981        let mut doc = self.clone();
982        doc.root_override = Some(root);
983        doc.edits.clear();
984        Ok(doc)
985    }
986
987    pub(crate) fn rerooted_without_tag(&self, root: NodeId) -> Result<Self, YamlError> {
988        let mut doc = self.rerooted_at(root)?;
989        doc.semantics.clear_tag(root);
990        Ok(doc)
991    }
992
993    pub(crate) fn rerooted_at_mapping(&self, mapping: NodeId) -> Result<Self, YamlError> {
994        let mapping_node = self.expect_node(mapping)?;
995        if !matches!(
996            mapping_node.kind,
997            NodeKind::BlockMapping | NodeKind::FlowMapping
998        ) {
999            return Err(YamlError::new(
1000                Diagnostic::new(
1001                    DiagnosticKind::Semantic,
1002                    format!("expected mapping, found {:?}", mapping_node.kind),
1003                    mapping_node.span,
1004                )
1005                .with_expected("BlockMapping or FlowMapping"),
1006            )
1007            .with_position_from(&self.source));
1008        }
1009        if !matches!(
1010            self.semantic_kind(mapping),
1011            Some(SemanticKind::Mapping { .. })
1012        ) {
1013            return Err(YamlError::new(
1014                Diagnostic::new(
1015                    DiagnosticKind::Semantic,
1016                    "mapping does not have semantic metadata",
1017                    self.expect_node(mapping)?.span,
1018                )
1019                .with_expected("a semantic mapping"),
1020            )
1021            .with_position_from(&self.source));
1022        }
1023        self.rerooted_at(mapping)
1024    }
1025
1026    pub(crate) fn queue_edits_from(&mut self, other: &YamlDoc) -> Result<(), YamlError> {
1027        for edit in &other.edits {
1028            self.queue_edit(edit.span, edit.replacement.clone())?;
1029        }
1030        Ok(())
1031    }
1032
1033    /// Returns the source text for a scalar node.
1034    ///
1035    /// # Errors
1036    ///
1037    /// Returns an error when `node` is unknown or does not identify a plain CST
1038    /// scalar node.
1039    pub fn scalar_text(&self, node: NodeId) -> Result<&str, YamlError> {
1040        let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1041        Ok(self.source.slice(node.span))
1042    }
1043
1044    /// Returns the decoded value text for a scalar node.
1045    ///
1046    /// Plain scalars have trailing inline comments stripped, single-quoted
1047    /// scalars unescape doubled apostrophes, and double-quoted scalars unescape
1048    /// the common JSON/YAML escapes used by typed overlays.
1049    ///
1050    /// # Errors
1051    ///
1052    /// Returns an error when `node` is unknown, is not a scalar node, has
1053    /// malformed node properties, or contains unsupported scalar escape syntax.
1054    pub fn scalar_value(&self, node: NodeId) -> Result<Cow<'_, str>, YamlError> {
1055        let node_ref = self.expect_node(node)?;
1056        if !matches!(
1057            node_ref.kind,
1058            NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1059        ) {
1060            return Err(YamlError::new(
1061                Diagnostic::new(
1062                    DiagnosticKind::Semantic,
1063                    format!("expected scalar value, found {:?}", node_ref.kind),
1064                    node_ref.span,
1065                )
1066                .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1067            )
1068            .with_position_from(&self.source));
1069        }
1070        let text = self.source.slice(node_ref.span);
1071        let properties = parse_node_properties(text, node_ref.span)?;
1072        let value_text = &text[properties.value_start..];
1073        if matches!(
1074            self.semantic_kind(node),
1075            Some(SemanticKind::Scalar {
1076                style: crate::YamlScalarStyle::Plain,
1077                ..
1078            })
1079        ) && !value_text.contains(['\n', '\r'])
1080        {
1081            return Ok(Cow::Borrowed(&value_text[..plain_scalar_end(value_text)]));
1082        }
1083        decode_scalar_value_with_content_indent(
1084            value_text,
1085            self.semantics
1086                .properties(node)
1087                .and_then(|properties| properties.content_indent)
1088                .map(|indent| indent as usize),
1089        )
1090        .map(Cow::Owned)
1091    }
1092
1093    /// Returns the source span of a scalar whose decoded value can be borrowed
1094    /// byte-for-byte from the original input.
1095    ///
1096    /// This currently returns a span only for single-line plain scalars. Quoted,
1097    /// escaped, folded, literal, and multiline scalars require decoding and
1098    /// return `Ok(None)`.
1099    ///
1100    /// # Errors
1101    ///
1102    /// Returns an error when `node` is unknown, is not a scalar, or has malformed
1103    /// node properties.
1104    pub fn borrowable_scalar_span(&self, node: NodeId) -> Result<Option<Span>, YamlError> {
1105        let node_ref = self.expect_node(node)?;
1106        if !matches!(
1107            self.semantic_kind(node),
1108            Some(SemanticKind::Scalar {
1109                style: crate::YamlScalarStyle::Plain,
1110            })
1111        ) {
1112            if matches!(
1113                node_ref.kind,
1114                NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1115            ) {
1116                return Ok(None);
1117            }
1118            return Err(YamlError::new(
1119                Diagnostic::new(
1120                    DiagnosticKind::Semantic,
1121                    format!("expected scalar value, found {:?}", node_ref.kind),
1122                    node_ref.span,
1123                )
1124                .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1125            )
1126            .with_position_from(&self.source));
1127        }
1128
1129        let text = self.source.slice(node_ref.span);
1130        if text.contains(['\n', '\r']) {
1131            return Ok(None);
1132        }
1133        let properties = parse_node_properties(text, node_ref.span)?;
1134        let value_text = &text[properties.value_start..];
1135        let value_len = plain_scalar_end(value_text);
1136        let start = Span::offset_from_usize(node_ref.span.start, properties.value_start);
1137        Ok(Some(Span::new(
1138            start,
1139            Span::offset_from_usize(start, value_len),
1140        )))
1141    }
1142
1143    /// Queues a scalar value replacement at `path` while preserving the existing
1144    /// scalar style where the editor can do so safely.
1145    ///
1146    /// Plain scalars remain plain, single-quoted scalars remain single-quoted,
1147    /// and double-quoted scalars remain double-quoted. Inline comments and
1148    /// trailing whitespace outside the scalar spelling are left untouched.
1149    ///
1150    /// # Errors
1151    ///
1152    /// Returns an error when `path` does not resolve to an existing scalar, the
1153    /// current scalar style cannot be rewritten safely, `value` cannot be
1154    /// represented in that style, or the queued edit conflicts with another
1155    /// pending edit.
1156    pub fn set_scalar(&mut self, path: &[&str], value: &str) -> Result<(), YamlError> {
1157        let node = self.get_path(path)?.ok_or_else(|| {
1158            YamlError::new(
1159                Diagnostic::new(
1160                    DiagnosticKind::Semantic,
1161                    format!("path `{}` does not exist", path.join(".")),
1162                    Span::empty(0),
1163                )
1164                .with_expected("an existing scalar node"),
1165            )
1166        })?;
1167
1168        let (span, style) = self.scalar_replacement_target(node)?;
1169        let replacement = format_scalar_value(value, style)?;
1170        self.queue_edit(span, replacement)
1171    }
1172
1173    /// Queues a patch that replaces the exact source span covered by `node`.
1174    ///
1175    /// The CST remains unchanged until the edited text is parsed again; callers
1176    /// can inspect the pending minimal-diff output through `doc.to_string()`.
1177    ///
1178    /// # Errors
1179    ///
1180    /// Returns an error when `node` is unknown, `text` contains invalid YAML
1181    /// characters, or the replacement overlaps an existing pending edit.
1182    pub fn replace_node_text(
1183        &mut self,
1184        node: NodeId,
1185        text: impl Into<String>,
1186    ) -> Result<(), YamlError> {
1187        let span = self.expect_node(node)?.span;
1188        self.queue_edit(span, text.into())
1189    }
1190
1191    /// Queues insertion of a plain `key: value` entry into a block mapping.
1192    ///
1193    /// This low-level writer accepts raw plain scalar text. Use typed values or
1194    /// node fragments when quoting or schema-aware formatting is required.
1195    ///
1196    /// # Errors
1197    ///
1198    /// Returns an error when `mapping` is not a block mapping, `key` or `value`
1199    /// is not valid as a plain mapping fragment, or the insertion conflicts with
1200    /// another pending edit.
1201    pub fn insert_mapping_entry(
1202        &mut self,
1203        mapping: NodeId,
1204        key: &str,
1205        value: &str,
1206        style: MappingEntryStyle,
1207    ) -> Result<(), YamlError> {
1208        self.insert_mapping_entry_with_comment(mapping, key, value, style, None)
1209    }
1210
1211    /// Queues insertion of a plain `key: value` entry with optional preceding
1212    /// comment lines.
1213    ///
1214    /// Comments are emitted only for inserted entries; existing YAML comments are
1215    /// never overwritten by this helper.
1216    ///
1217    /// # Errors
1218    ///
1219    /// Returns an error when `mapping` is not a block mapping, `key`, `value`, or
1220    /// `comment` cannot be emitted as valid YAML text, or the insertion conflicts
1221    /// with another pending edit.
1222    pub fn insert_mapping_entry_with_comment(
1223        &mut self,
1224        mapping: NodeId,
1225        key: &str,
1226        value: &str,
1227        style: MappingEntryStyle,
1228        comment: Option<&str>,
1229    ) -> Result<(), YamlError> {
1230        let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1231        let indent = match style {
1232            MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1233            MappingEntryStyle::Indent(indent) => indent,
1234        };
1235        let insertion_offset = self.mapping_insertion_offset(mapping_node);
1236        let needs_leading_break =
1237            insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1238        let preserve_paragraph_break = comment.is_some()
1239            && insertion_offset == self.source.len()
1240            && self.mapping_has_blank_line(mapping_node);
1241        let replacement = self.format_mapping_entry_replacement(
1242            indent,
1243            key,
1244            value,
1245            comment,
1246            needs_leading_break,
1247            preserve_paragraph_break,
1248        )?;
1249
1250        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1251    }
1252
1253    /// Queues insertion of a typed YAML value under `key` in a block mapping.
1254    ///
1255    /// # Errors
1256    ///
1257    /// Returns an error when `mapping` is not a block mapping, the value cannot
1258    /// be formatted as a block YAML fragment, or the insertion conflicts with an
1259    /// existing pending edit.
1260    pub fn insert_mapping_value_with_comment<T>(
1261        &mut self,
1262        mapping: NodeId,
1263        key: &str,
1264        value: &T,
1265        style: MappingEntryStyle,
1266        comment: Option<&str>,
1267    ) -> Result<(), YamlError>
1268    where
1269        T: ToYamlFragment,
1270    {
1271        if matches!(
1272            self.semantic_kind(mapping),
1273            Some(SemanticKind::Mapping {
1274                style: CollectionStyle::Flow
1275            })
1276        ) {
1277            let fragment = self.typed_value_fragment(value)?;
1278            return self
1279                .queue_mapping_insert(mapping, key, &fragment)
1280                .map_err(YamlEditError::into_yaml_error);
1281        }
1282        let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1283        let indent = match style {
1284            MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1285            MappingEntryStyle::Indent(indent) => indent,
1286        };
1287        let insertion_offset = self.mapping_insertion_offset(mapping_node);
1288        let needs_leading_break =
1289            insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1290        let preserve_paragraph_break = comment.is_some()
1291            && insertion_offset == self.source.len()
1292            && self.mapping_has_blank_line(mapping_node);
1293        let replacement = self.format_mapping_value_replacement(
1294            indent,
1295            key,
1296            value,
1297            comment,
1298            needs_leading_break,
1299            preserve_paragraph_break,
1300        )?;
1301
1302        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1303    }
1304
1305    fn typed_value_fragment<T>(&self, value: &T) -> Result<YamlFragment, YamlError>
1306    where
1307        T: ToYamlFragment,
1308    {
1309        let yaml = value.to_yaml_fragment(0, self.preferred_line_ending())?;
1310        YamlFragment::parse(&yaml).map_err(|error| {
1311            YamlError::new(
1312                Diagnostic::new(
1313                    DiagnosticKind::Emitter,
1314                    format!("typed YAML fragment is invalid: {error}"),
1315                    Span::empty(0),
1316                )
1317                .with_expected("one valid YAML value"),
1318            )
1319        })
1320    }
1321
1322    /// Queues insertion of a typed YAML value according to declaration order.
1323    ///
1324    /// # Errors
1325    ///
1326    /// Returns an error when mapping lookup fails or the selected insertion
1327    /// cannot be formatted or queued.
1328    pub fn insert_mapping_value_ordered_with_comment<T>(
1329        &mut self,
1330        mapping: NodeId,
1331        key: &str,
1332        value: &T,
1333        style: MappingEntryStyle,
1334        comment: Option<&str>,
1335        ordered_keys: &[&str],
1336    ) -> Result<(), YamlError>
1337    where
1338        T: ToYamlFragment,
1339    {
1340        let mut next_entry = None;
1341        if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1342            for later_key in &ordered_keys[position + 1..] {
1343                if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1344                    next_entry = Some(entry);
1345                    break;
1346                }
1347            }
1348        }
1349
1350        if let Some(next_entry) = next_entry {
1351            self.insert_mapping_value_before_with_comment(next_entry, key, value, style, comment)
1352        } else {
1353            self.insert_mapping_value_with_comment(mapping, key, value, style, comment)
1354        }
1355    }
1356
1357    /// Queues insertion of a plain `key: value` entry before `before_entry`.
1358    ///
1359    /// # Errors
1360    ///
1361    /// Returns an error when `before_entry` is not a mapping entry, `key`,
1362    /// `value`, or `comment` cannot be emitted as valid YAML text, or the
1363    /// insertion conflicts with another pending edit.
1364    pub fn insert_mapping_entry_before_with_comment(
1365        &mut self,
1366        before_entry: NodeId,
1367        key: &str,
1368        value: &str,
1369        style: MappingEntryStyle,
1370        comment: Option<&str>,
1371    ) -> Result<(), YamlError> {
1372        let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1373        let indent = match style {
1374            MappingEntryStyle::Inherit => self.node_indent(before_node),
1375            MappingEntryStyle::Indent(indent) => indent,
1376        };
1377        let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1378        let replacement =
1379            self.format_mapping_entry_replacement(indent, key, value, comment, false, false)?;
1380
1381        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1382    }
1383
1384    /// Queues insertion of a typed YAML value before an existing mapping entry.
1385    ///
1386    /// # Errors
1387    ///
1388    /// Returns an error when the insertion target is invalid, the value cannot
1389    /// be formatted, or the insertion conflicts with an existing pending edit.
1390    pub fn insert_mapping_value_before_with_comment<T>(
1391        &mut self,
1392        before_entry: NodeId,
1393        key: &str,
1394        value: &T,
1395        style: MappingEntryStyle,
1396        comment: Option<&str>,
1397    ) -> Result<(), YamlError>
1398    where
1399        T: ToYamlFragment,
1400    {
1401        let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1402        let mapping = before_node.parent().ok_or_else(|| {
1403            YamlError::new(
1404                Diagnostic::new(
1405                    DiagnosticKind::Semantic,
1406                    "mapping entry has no parent mapping",
1407                    before_node.span,
1408                )
1409                .with_expected("a mapping parent"),
1410            )
1411        })?;
1412        if matches!(
1413            self.semantic_kind(mapping),
1414            Some(SemanticKind::Mapping {
1415                style: CollectionStyle::Flow
1416            })
1417        ) {
1418            let fragment = self.typed_value_fragment(value)?;
1419            return self
1420                .queue_mapping_insert_before(mapping, before_entry, key, &fragment)
1421                .map_err(YamlEditError::into_yaml_error);
1422        }
1423        let indent = match style {
1424            MappingEntryStyle::Inherit => self.node_indent(before_node),
1425            MappingEntryStyle::Indent(indent) => indent,
1426        };
1427        let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1428        let replacement =
1429            self.format_mapping_value_replacement(indent, key, value, comment, false, false)?;
1430
1431        self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1432    }
1433
1434    /// Queues insertion according to a declaration-order key list.
1435    ///
1436    /// If a later key from `ordered_keys` already exists in `mapping`, the new
1437    /// entry is inserted before that entry. Otherwise this falls back to append
1438    /// insertion. This is the primitive behind `insert_order = "struct"`.
1439    ///
1440    /// # Errors
1441    ///
1442    /// Returns an error when mapping lookup fails, the selected insertion target
1443    /// has the wrong node kind, inserted text is invalid YAML, or the queued edit
1444    /// conflicts with another pending edit.
1445    pub fn insert_mapping_entry_ordered_with_comment(
1446        &mut self,
1447        mapping: NodeId,
1448        key: &str,
1449        value: &str,
1450        style: MappingEntryStyle,
1451        comment: Option<&str>,
1452        ordered_keys: &[&str],
1453    ) -> Result<(), YamlError> {
1454        let mut next_entry = None;
1455        if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1456            for later_key in &ordered_keys[position + 1..] {
1457                if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1458                    next_entry = Some(entry);
1459                    break;
1460                }
1461            }
1462        }
1463
1464        if let Some(next_entry) = next_entry {
1465            self.insert_mapping_entry_before_with_comment(next_entry, key, value, style, comment)
1466        } else {
1467            self.insert_mapping_entry_with_comment(mapping, key, value, style, comment)
1468        }
1469    }
1470
1471    /// Queues removal of the mapping entry with `key` from `mapping` when it exists.
1472    ///
1473    /// The removal is line-wise, so comments and fields outside the selected entry
1474    /// remain byte-for-byte unchanged. Missing keys are a no-op.
1475    ///
1476    /// # Errors
1477    ///
1478    /// Returns an error when mapping lookup fails, the selected entry cannot be
1479    /// removed, or the removal overlaps an existing pending edit.
1480    pub fn remove_mapping_entry(&mut self, mapping: NodeId, key: &str) -> Result<(), YamlError> {
1481        let Some(entry) = self.get_mapping_entry(mapping, key)? else {
1482            return Ok(());
1483        };
1484        self.remove_collection_entries(mapping, &[entry])
1485    }
1486
1487    /// Queues line-wise removal edits for mapping entries whose keys are not allowed.
1488    ///
1489    /// This is the patch-emitter primitive used by typed overlays that choose to
1490    /// prune unknown fields. It preserves the order and bytes of retained entries.
1491    ///
1492    /// # Errors
1493    ///
1494    /// Returns an error when `mapping` is not a block mapping, a retained entry
1495    /// cannot be inspected as a scalar key, or a removal edit conflicts with
1496    /// another pending edit.
1497    pub fn retain_mapping_entries(
1498        &mut self,
1499        mapping: NodeId,
1500        allowed_keys: &[&str],
1501    ) -> Result<(), YamlError> {
1502        let mapping_node = self.expect_node(mapping)?;
1503        if !matches!(
1504            mapping_node.kind,
1505            NodeKind::BlockMapping | NodeKind::FlowMapping
1506        ) {
1507            return Err(YamlError::new(
1508                Diagnostic::new(
1509                    DiagnosticKind::Semantic,
1510                    format!("expected mapping, found {:?}", mapping_node.kind),
1511                    mapping_node.span,
1512                )
1513                .with_expected("BlockMapping or FlowMapping"),
1514            ));
1515        }
1516        let mut removals = Vec::new();
1517
1518        for entry in self.children(mapping) {
1519            let entry_node = self.expect_node(entry)?;
1520            if entry_node.kind != NodeKind::MappingEntry {
1521                continue;
1522            }
1523
1524            let Some(key_node) = self.children(entry).next() else {
1525                continue;
1526            };
1527            let key = self.scalar_value(key_node)?;
1528            if !allowed_keys.contains(&key.as_ref()) {
1529                removals.push(entry);
1530            }
1531        }
1532
1533        self.remove_collection_entries(mapping, &removals)
1534    }
1535
1536    pub(crate) fn remove_collection_entries(
1537        &mut self,
1538        collection: NodeId,
1539        removals: &[NodeId],
1540    ) -> Result<(), YamlError> {
1541        if removals.is_empty() {
1542            return Ok(());
1543        }
1544        let Some(style) = (match self.semantic_kind(collection) {
1545            Some(SemanticKind::Mapping { style } | SemanticKind::Sequence { style }) => Some(style),
1546            _ => None,
1547        }) else {
1548            return Err(YamlError::new(
1549                Diagnostic::new(
1550                    DiagnosticKind::Semantic,
1551                    "collection entry removal target is not a mapping or sequence",
1552                    self.expect_node(collection)?.span,
1553                )
1554                .with_expected("a mapping or sequence"),
1555            ));
1556        };
1557        if style == CollectionStyle::Block {
1558            for entry in removals {
1559                self.remove_node(*entry)?;
1560            }
1561            return Ok(());
1562        }
1563
1564        let entries = self
1565            .children(collection)
1566            .filter(|node| {
1567                self.node(*node).is_some_and(|node| {
1568                    matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1569                })
1570            })
1571            .collect::<Vec<_>>();
1572        if removals.len() == entries.len() {
1573            let collection_node = self.expect_node(collection)?;
1574            let delimiter = match collection_node.kind {
1575                NodeKind::FlowMapping => '}',
1576                NodeKind::FlowSequence => ']',
1577                _ => unreachable!("flow semantic collection must have a flow CST node"),
1578            };
1579            if let Some(relative) = self.source.slice(collection_node.span).rfind(delimiter) {
1580                let close = Span::offset_from_usize(collection_node.span.start, relative);
1581                if let Some(edit) = self
1582                    .edits
1583                    .iter_mut()
1584                    .find(|edit| edit.span == Span::empty(close))
1585                    && let Some(replacement) = edit.replacement.strip_prefix(", ")
1586                {
1587                    edit.replacement = replacement.to_owned();
1588                }
1589            }
1590        }
1591        let mut index = 0;
1592        while index < entries.len() {
1593            if !removals.contains(&entries[index]) {
1594                index += 1;
1595                continue;
1596            }
1597            let start = index;
1598            while index < entries.len() && removals.contains(&entries[index]) {
1599                index += 1;
1600            }
1601            let end = index;
1602            let first = self.expect_node(entries[start])?.span;
1603            let last = self.expect_node(entries[end - 1])?.span;
1604            let span = if let Some(next) = entries.get(end).copied() {
1605                Span::new(first.start, self.expect_node(next)?.span.start)
1606            } else if start > 0 {
1607                Span::new(self.expect_node(entries[start - 1])?.span.end, last.end)
1608            } else {
1609                Span::new(first.start, last.end)
1610            };
1611            self.queue_edit(span, String::new())?;
1612        }
1613        Ok(())
1614    }
1615
1616    /// Queues removal of `node` from the rendered document.
1617    ///
1618    /// Mapping and sequence entries are removed line-wise, including their line
1619    /// break when one is present. Other nodes use their exact source span.
1620    ///
1621    /// # Errors
1622    ///
1623    /// Returns an error when `node` is unknown or the removal overlaps an
1624    /// existing pending edit.
1625    pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1626        let node = self.expect_node(node)?;
1627        let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1628            self.line_span_including_break(node.span)
1629        } else {
1630            node.span
1631        };
1632        self.queue_edit(span, String::new())
1633    }
1634
1635    pub(crate) fn scalar_replacement_target(
1636        &self,
1637        node: NodeId,
1638    ) -> Result<(Span, ScalarStyle), YamlError> {
1639        let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1640        let text = self.source.slice(node.span);
1641        let properties = parse_node_properties(text, node.span)?;
1642        let value_text = &text[properties.value_start..];
1643        let value_start = Span::offset_from_usize(node.span.start, properties.value_start);
1644
1645        if value_text.starts_with('"') {
1646            let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1647                YamlError::new(
1648                    Diagnostic::new(
1649                        DiagnosticKind::Emitter,
1650                        "could not find the end of the double-quoted scalar",
1651                        node.span,
1652                    )
1653                    .with_expected("a closed double-quoted scalar"),
1654                )
1655            })?;
1656            return Ok((
1657                Span::new(value_start, Span::offset_from_usize(value_start, end)),
1658                ScalarStyle::DoubleQuoted,
1659            ));
1660        }
1661
1662        if value_text.starts_with('\'') {
1663            let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1664                YamlError::new(
1665                    Diagnostic::new(
1666                        DiagnosticKind::Emitter,
1667                        "could not find the end of the single-quoted scalar",
1668                        node.span,
1669                    )
1670                    .with_expected("a closed single-quoted scalar"),
1671                )
1672            })?;
1673            return Ok((
1674                Span::new(value_start, Span::offset_from_usize(value_start, end)),
1675                ScalarStyle::SingleQuoted,
1676            ));
1677        }
1678
1679        let end = plain_scalar_end(value_text);
1680        if end == 0 {
1681            return Err(YamlError::new(
1682                Diagnostic::new(
1683                    DiagnosticKind::Emitter,
1684                    "could not find plain scalar text to replace",
1685                    node.span,
1686                )
1687                .with_expected("plain scalar text"),
1688            ));
1689        }
1690
1691        Ok((
1692            Span::new(value_start, Span::offset_from_usize(value_start, end)),
1693            ScalarStyle::Plain,
1694        ))
1695    }
1696
1697    fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1698        self.root()
1699            .into_iter()
1700            .flat_map(|root| self.children(root))
1701            .filter(|node| {
1702                self.node(*node)
1703                    .is_some_and(|node| node.kind == NodeKind::Directive)
1704            })
1705    }
1706
1707    fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1708        let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1709        let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1710        let mut parts = body.split_whitespace();
1711        let Some(name) = parts.next() else {
1712            return Err(directive_emit_error(
1713                "directive is missing a name",
1714                node_ref.span,
1715                "%YAML, %TAG, or reserved directive syntax",
1716            )
1717            .with_position_from(&self.source));
1718        };
1719
1720        Ok(match name {
1721            "%YAML" => ParsedDirective::Yaml(YamlDirective {
1722                version: parts.next().unwrap_or_default().to_owned(),
1723                node,
1724            }),
1725            "%TAG" => ParsedDirective::Tag(TagDirective {
1726                handle: parts.next().unwrap_or_default().to_owned(),
1727                prefix: parts.next().unwrap_or_default().to_owned(),
1728                node,
1729            }),
1730            _ => ParsedDirective::Reserved(ReservedDirective {
1731                name: name.to_owned(),
1732                parameters: parts.map(str::to_owned).collect(),
1733                node,
1734            }),
1735        })
1736    }
1737
1738    fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1739        let node = self.expect_node_kind(node, NodeKind::Directive)?;
1740        let text = self.source.slice(node.span);
1741        let end = strip_inline_comment(text)
1742            .trim_end_matches([' ', '\t'])
1743            .len();
1744        Ok(Span::new(
1745            node.span.start,
1746            Span::offset_from_usize(node.span.start, end),
1747        ))
1748    }
1749
1750    fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1751        let insertion_offset = self.directive_insertion_offset();
1752        let mut line = replacement;
1753        line.push_str(self.preferred_line_ending());
1754        self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1755    }
1756
1757    fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1758        let node = self.expect_node_kind(node, NodeKind::Directive)?;
1759        self.queue_edit(self.line_span_including_break(node.span), String::new())
1760    }
1761
1762    fn directive_insertion_offset(&self) -> usize {
1763        if let Some(last_directive) = self
1764            .directive_nodes()
1765            .filter_map(|node| self.node(node))
1766            .max_by_key(|node| node.span.start)
1767        {
1768            return self.line_span_including_break(last_directive.span).end as usize;
1769        }
1770
1771        self.root()
1772            .and_then(|root| self.children(root).next())
1773            .and_then(|node| self.node(node))
1774            .map_or(0, |node| {
1775                self.line_start_for_offset(node.span.start as usize)
1776            })
1777    }
1778
1779    pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1780        self.node(node).ok_or_else(|| {
1781            YamlError::new(Diagnostic::new(
1782                DiagnosticKind::Semantic,
1783                format!("unknown node id {}", node.0),
1784                Span::empty_from_usize(self.source.len()),
1785            ))
1786        })
1787    }
1788
1789    pub(crate) fn expect_node_kind(
1790        &self,
1791        node: NodeId,
1792        expected: NodeKind,
1793    ) -> Result<&Node, YamlError> {
1794        let actual = self.expect_node(node)?;
1795        if actual.kind == expected {
1796            Ok(actual)
1797        } else {
1798            Err(YamlError::new(
1799                Diagnostic::new(
1800                    DiagnosticKind::Semantic,
1801                    format!("expected {expected:?}, found {:?}", actual.kind),
1802                    actual.span,
1803                )
1804                .with_expected(format!("{expected:?}")),
1805            )
1806            .with_position_from(&self.source))
1807        }
1808    }
1809
1810    pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1811        self.node(value).and_then(Node::parent).filter(|parent| {
1812            self.node(*parent).is_some_and(|node| {
1813                matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1814            })
1815        })
1816    }
1817
1818    fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1819        let start = self.line_start_for_offset(mapping.span.start as usize);
1820        let end = mapping.span.end as usize;
1821        let text = &self.source.as_str()[start..end];
1822        text.contains("\n\n") || text.contains("\r\n\r\n")
1823    }
1824
1825    fn format_mapping_entry_replacement(
1826        &self,
1827        indent: usize,
1828        key: &str,
1829        value: &str,
1830        comment: Option<&str>,
1831        needs_leading_break: bool,
1832        preserve_paragraph_break: bool,
1833    ) -> Result<String, YamlError> {
1834        validate_plain_mapping_fragment(key, "mapping key")?;
1835        validate_plain_mapping_fragment(value, "mapping value")?;
1836        if let Some(comment) = comment {
1837            validate_yaml_chars(comment)?;
1838        }
1839
1840        let line_ending = self.preferred_line_ending();
1841        let indent_text = " ".repeat(indent);
1842        let mut replacement = String::new();
1843        if needs_leading_break {
1844            replacement.push_str(line_ending);
1845        }
1846        if preserve_paragraph_break {
1847            replacement.push_str(line_ending);
1848        }
1849        if let Some(comment) = comment {
1850            for line in comment.lines() {
1851                replacement.push_str(&indent_text);
1852                replacement.push('#');
1853                if !line.is_empty() {
1854                    replacement.push(' ');
1855                    replacement.push_str(line.trim());
1856                }
1857                replacement.push_str(line_ending);
1858            }
1859        }
1860        replacement.push_str(&indent_text);
1861        replacement.push_str(key);
1862        replacement.push_str(": ");
1863        replacement.push_str(value);
1864        replacement.push_str(line_ending);
1865        Ok(replacement)
1866    }
1867
1868    fn format_mapping_value_replacement<T>(
1869        &self,
1870        indent: usize,
1871        key: &str,
1872        value: &T,
1873        comment: Option<&str>,
1874        needs_leading_break: bool,
1875        preserve_paragraph_break: bool,
1876    ) -> Result<String, YamlError>
1877    where
1878        T: ToYamlFragment,
1879    {
1880        validate_yaml_chars(key)?;
1881        if let Some(comment) = comment {
1882            validate_yaml_chars(comment)?;
1883        }
1884
1885        let line_ending = self.preferred_line_ending();
1886        let indent_text = " ".repeat(indent);
1887        let child_indent = indent + 2;
1888        let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
1889        let mut replacement = String::new();
1890        if needs_leading_break {
1891            replacement.push_str(line_ending);
1892        }
1893        if preserve_paragraph_break {
1894            replacement.push_str(line_ending);
1895        }
1896        if let Some(comment) = comment {
1897            for line in comment.lines() {
1898                replacement.push_str(&indent_text);
1899                replacement.push('#');
1900                if !line.is_empty() {
1901                    replacement.push(' ');
1902                    replacement.push_str(line.trim());
1903                }
1904                replacement.push_str(line_ending);
1905            }
1906        }
1907        replacement.push_str(&indent_text);
1908        replacement.push_str(&crate::edit::emit_string_key(key));
1909        if fragment.contains('\n') || fragment.starts_with(' ') {
1910            replacement.push(':');
1911            replacement.push_str(line_ending);
1912            replacement.push_str(&fragment);
1913        } else {
1914            replacement.push_str(": ");
1915            replacement.push_str(&fragment);
1916        }
1917        replacement.push_str(line_ending);
1918        Ok(replacement)
1919    }
1920
1921    pub(crate) fn node_indent(&self, node: &Node) -> usize {
1922        let line_start = self.line_start_for_offset(node.span.start as usize);
1923        self.source.as_str()[line_start..node.span.start as usize]
1924            .bytes()
1925            .filter(|byte| *byte == b' ')
1926            .count()
1927    }
1928
1929    fn line_start_for_offset(&self, offset: usize) -> usize {
1930        let offset = Span::usize_to_u32(offset);
1931        match self.source.line_starts().binary_search(&offset) {
1932            Ok(index) => self.source.line_starts()[index] as usize,
1933            Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
1934        }
1935    }
1936
1937    pub(crate) fn find_nested_collection_after(
1938        &self,
1939        entry: &Node,
1940        parent_indent: usize,
1941    ) -> Option<NodeId> {
1942        self.nodes
1943            .iter()
1944            .enumerate()
1945            .filter(|(_, node)| {
1946                matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
1947                    && node.span.start >= entry.span.end
1948                    && self.node_indent(node) > parent_indent
1949            })
1950            .min_by_key(|(_, node)| node.span.start)
1951            .map(|(index, _)| NodeId::from_usize(index))
1952    }
1953
1954    pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
1955        let text = self.source.slice(scalar.span);
1956        let header_end = text.find(['\r', '\n'])?;
1957        let mut rest = &text[header_end..];
1958        while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
1959            rest = stripped;
1960        }
1961        for line in rest.lines() {
1962            if line.trim().is_empty() {
1963                continue;
1964            }
1965            return Some(line.bytes().take_while(|byte| *byte == b' ').count());
1966        }
1967        None
1968    }
1969
1970    pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
1971        self.source.try_slice(span)?;
1972        validate_yaml_chars(&replacement)?;
1973
1974        if span.is_empty()
1975            && let Some(existing) = self
1976                .edits
1977                .iter_mut()
1978                .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
1979        {
1980            existing.replacement.push_str(&replacement);
1981            return Ok(());
1982        }
1983
1984        if let Some(existing) = self
1985            .edits
1986            .iter()
1987            .find(|edit| edits_conflict(edit.span, span))
1988        {
1989            return Err(YamlError::new(
1990                Diagnostic::new(
1991                    DiagnosticKind::Emitter,
1992                    "edit overlaps an existing pending edit",
1993                    span,
1994                )
1995                .with_note(format!(
1996                    "existing edit covers bytes {}..{}",
1997                    existing.span.start, existing.span.end
1998                )),
1999            )
2000            .with_position_from(&self.source));
2001        }
2002
2003        self.edits.push(Edit { span, replacement });
2004        Ok(())
2005    }
2006
2007    pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2008        node_link(mapping.last_child)
2009            .and_then(|child| self.node(child))
2010            .map_or(mapping.span.end as usize, |last_child| {
2011                self.line_span_including_break(last_child.span).end as usize
2012            })
2013    }
2014
2015    pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2016        node_link(sequence.last_child)
2017            .and_then(|child| self.node(child))
2018            .map_or(sequence.span.end as usize, |last_child| {
2019                self.line_span_including_break(last_child.span).end as usize
2020            })
2021    }
2022
2023    fn line_span_including_break(&self, span: Span) -> Span {
2024        let start = self.line_start_for_offset(span.start as usize);
2025        let mut end = span.end as usize;
2026        let bytes = self.source.as_str().as_bytes();
2027
2028        if end < bytes.len() {
2029            if bytes[end] == b'\r' {
2030                end += 1;
2031                if end < bytes.len() && bytes[end] == b'\n' {
2032                    end += 1;
2033                }
2034            } else if bytes[end] == b'\n' {
2035                end += 1;
2036            }
2037        }
2038
2039        Span::from_usize(start, end)
2040    }
2041
2042    pub(crate) fn preferred_line_ending(&self) -> &str {
2043        let bytes = self.source.as_str().as_bytes();
2044        for (index, byte) in bytes.iter().enumerate() {
2045            if *byte == b'\r' {
2046                return if bytes.get(index + 1) == Some(&b'\n') {
2047                    "\r\n"
2048                } else {
2049                    "\r"
2050                };
2051            }
2052            if *byte == b'\n' {
2053                return if index > 0 && bytes[index - 1] == b'\r' {
2054                    "\r\n"
2055                } else {
2056                    "\n"
2057                };
2058            }
2059        }
2060        "\n"
2061    }
2062
2063    fn source_ends_with_line_break(&self) -> bool {
2064        self.source
2065            .as_str()
2066            .as_bytes()
2067            .last()
2068            .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2069    }
2070
2071    fn document_append_prefix(&self, line_ending: &str) -> String {
2072        if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2073            String::new()
2074        } else {
2075            line_ending.to_owned()
2076        }
2077    }
2078}
2079impl fmt::Display for YamlDoc {
2080    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2081        if self.edits.is_empty() {
2082            return formatter.write_str(self.source.as_str());
2083        }
2084
2085        let mut output = self.source.as_str().to_owned();
2086        let mut edits = self.edits.clone();
2087        edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2088
2089        for edit in edits {
2090            output.replace_range(
2091                edit.span.start as usize..edit.span.end as usize,
2092                &edit.replacement,
2093            );
2094        }
2095
2096        formatter.write_str(&output)
2097    }
2098}