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