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