Skip to main content

yaml_edit/nodes/
mapping.rs

1use super::{Lang, Scalar, Sequence, SyntaxNode};
2use crate::as_yaml::{AsYaml, YamlKind};
3use crate::lex::SyntaxKind;
4use crate::yaml::{
5    add_newline_token, add_node_children_to, dump_cst_to_string, ends_with_newline, Document,
6    ValueNode,
7};
8use rowan::ast::AstNode;
9use rowan::GreenNodeBuilder;
10
11ast_node!(
12    MappingEntry,
13    MAPPING_ENTRY,
14    "A key-value pair in a YAML mapping"
15);
16
17/// Build a standalone `SyntaxToken` of `kind` with `text`, ready to splice
18/// into a parent's child list via `splice_children`.
19fn fresh_token(kind: SyntaxKind, text: &str) -> rowan::SyntaxToken<Lang> {
20    let mut builder = GreenNodeBuilder::new();
21    builder.start_node(SyntaxKind::ROOT.into());
22    builder.token(kind.into(), text);
23    builder.finish_node();
24    SyntaxNode::new_root_mut(builder.finish())
25        .first_token()
26        .expect("just built a token")
27}
28
29/// Walk `entry`'s tokens in document order (descending into child nodes)
30/// looking for a COMMENT on the same line as the key — that is, the first
31/// COMMENT after the entry's COLON and before the first NEWLINE. Returns
32/// the comment's text plus the WHITESPACE that precedes it (defaulting to
33/// `"  "` for comments with no leading space); `None` if the key line has
34/// no comment.
35///
36/// The comment may sit inside VALUE (`k: v  # note`, `k:  # note\n  block`)
37/// or at MAPPING_ENTRY level between COLON and VALUE (`k:  # note\n  block`
38/// with the parser choosing to store the comment as a sibling of VALUE).
39fn find_key_line_comment(entry: &SyntaxNode) -> Option<(String, String)> {
40    let mut seen_colon = false;
41    let mut last_ws: Option<String> = None;
42    for tok in entry
43        .descendants_with_tokens()
44        .filter_map(|c| c.into_token())
45    {
46        match tok.kind() {
47            SyntaxKind::COLON if !seen_colon => {
48                seen_colon = true;
49                last_ws = None;
50            }
51            _ if !seen_colon => {}
52            SyntaxKind::WHITESPACE => last_ws = Some(tok.text().to_string()),
53            SyntaxKind::COMMENT => {
54                return Some((
55                    last_ws.unwrap_or_else(|| "  ".to_string()),
56                    tok.text().to_string(),
57                ));
58            }
59            SyntaxKind::NEWLINE => return None,
60            _ => last_ws = None,
61        }
62    }
63    None
64}
65
66/// True if `source` is a block scalar (literal `|` or folded `>`) — its
67/// first token is the block indicator that must sit on the same line as
68/// the key.
69fn is_block_scalar(source: &SyntaxNode) -> bool {
70    source.kind() == SyntaxKind::SCALAR
71        && source
72            .first_token()
73            .is_some_and(|t| matches!(t.kind(), SyntaxKind::PIPE | SyntaxKind::GREATER))
74}
75
76/// Build a VALUE node wrapping `source` (a block sequence, mapping, scalar,
77/// or tagged node from another CST) so it renders correctly under a key
78/// whose content column is `target`. Block collections go on the line
79/// after the key with content at `target`; block scalars keep their `|`/`>`
80/// indicator on the key line and put content on the next line at `target`;
81/// tagged nodes keep the `!tag` on the key line and place the wrapped
82/// content on the next line. The source's interior indentation is shifted
83/// so nested lines line up. If the source is preceded by an `&anchor`
84/// inside its parent VALUE, the anchor is preserved on the key line so
85/// aliases still resolve. Returns the new VALUE node plus whether it ends
86/// with a NEWLINE.
87fn build_block_value_node(source: &SyntaxNode, target: usize) -> (SyntaxNode, bool) {
88    if source.kind() == SyntaxKind::TAGGED_NODE {
89        return build_tagged_value_node(source, target);
90    }
91    // Block scalars store their content column *inside* the SCALAR
92    // (after `|`/`>` + NEWLINE). Block collections have it in the INDENT
93    // that precedes them in their parent.
94    let is_scalar = is_block_scalar(source);
95    let source_base = if is_scalar {
96        block_scalar_content_indent(source).unwrap_or(0)
97    } else {
98        crate::as_yaml::source_base_indent(source)
99    };
100    let delta = target as isize - source_base as isize;
101    let anchor = source_anchor_text(source);
102
103    let mut value_builder = GreenNodeBuilder::new();
104    value_builder.start_node(SyntaxKind::VALUE.into());
105    // "key: &anchor" — a leading space then the anchor sits on the key line.
106    if let Some(a) = anchor.as_deref() {
107        value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
108        value_builder.token(SyntaxKind::ANCHOR.into(), a);
109    }
110    // Block scalars keep the `|`/`>` indicator on the key line (one space
111    // after the anchor or the colon); block collections drop to the next
112    // line indented to `target`.
113    if is_scalar {
114        value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
115    } else {
116        value_builder.token(SyntaxKind::NEWLINE.into(), "\n");
117        value_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(target));
118    }
119    value_builder.start_node(source.kind().into());
120    let ends_with_newline =
121        crate::as_yaml::copy_node_content_reindent(&mut value_builder, source, delta);
122    value_builder.finish_node();
123    value_builder.finish_node();
124    (
125        SyntaxNode::new_root_mut(value_builder.finish()),
126        ends_with_newline,
127    )
128}
129
130/// True if `node` (any yaml value node) renders inline (same line as its key).
131fn node_is_inline(node: &SyntaxNode) -> bool {
132    match node.kind() {
133        SyntaxKind::MAPPING => {
134            Mapping::cast(node.clone()).is_some_and(|m| ValueNode::is_inline(&m))
135        }
136        SyntaxKind::SEQUENCE => {
137            Sequence::cast(node.clone()).is_some_and(|s| ValueNode::is_inline(&s))
138        }
139        SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| ValueNode::is_inline(&s)),
140        _ => true,
141    }
142}
143
144/// Build a VALUE node wrapping a TAGGED_NODE source. The tag stays on the
145/// key line; the wrapped inner value (mapping/sequence/scalar) goes on the
146/// next line at `target` for block content, or stays inline with a leading
147/// space for inline content, with interior indentation re-shifted so
148/// nested lines line up.
149fn build_tagged_value_node(source: &SyntaxNode, target: usize) -> (SyntaxNode, bool) {
150    // A TAGGED_NODE holds the TAG token followed by the wrapped value node.
151    let tag_text = source
152        .children_with_tokens()
153        .filter_map(|c| c.into_token())
154        .find(|t| t.kind() == SyntaxKind::TAG)
155        .map(|t| t.text().to_string());
156    let inner = source.first_child();
157    let anchor = source_anchor_text(source);
158
159    let mut value_builder = GreenNodeBuilder::new();
160    value_builder.start_node(SyntaxKind::VALUE.into());
161    value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
162    if let Some(a) = anchor.as_deref() {
163        value_builder.token(SyntaxKind::ANCHOR.into(), a);
164        value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
165    }
166    value_builder.start_node(SyntaxKind::TAGGED_NODE.into());
167    if let Some(tag) = tag_text.as_deref() {
168        value_builder.token(SyntaxKind::TAG.into(), tag);
169    }
170    let ends_with_newline = match inner {
171        None => false,
172        Some(inner) if node_is_inline(&inner) => {
173            // e.g. `!!str value` — content on same line, verbatim copy.
174            value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
175            value_builder.start_node(inner.kind().into());
176            let n = crate::as_yaml::copy_node_content_reindent(&mut value_builder, &inner, 0);
177            value_builder.finish_node();
178            n
179        }
180        Some(inner) => {
181            // Block content: next line, re-indent by (target - source_base).
182            value_builder.token(SyntaxKind::NEWLINE.into(), "\n");
183            value_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(target));
184            let delta = target as isize - crate::as_yaml::source_base_indent(&inner) as isize;
185            value_builder.start_node(inner.kind().into());
186            let n = crate::as_yaml::copy_node_content_reindent(&mut value_builder, &inner, delta);
187            value_builder.finish_node();
188            n
189        }
190    };
191    value_builder.finish_node(); // TAGGED_NODE
192    value_builder.finish_node(); // VALUE
193    (
194        SyntaxNode::new_root_mut(value_builder.finish()),
195        ends_with_newline,
196    )
197}
198
199/// If `source` is preceded by an `&anchor` token in its parent VALUE, return
200/// the anchor's text (including the leading `&`). Otherwise `None`.
201fn source_anchor_text(source: &SyntaxNode) -> Option<String> {
202    let mut cursor = source.prev_sibling_or_token();
203    while let Some(item) = cursor {
204        let tok = item.as_token()?;
205        match tok.kind() {
206            SyntaxKind::ANCHOR => return Some(tok.text().to_string()),
207            SyntaxKind::WHITESPACE | SyntaxKind::INDENT | SyntaxKind::NEWLINE => {}
208            _ => return None,
209        }
210        cursor = item.prev_sibling_or_token();
211    }
212    None
213}
214
215/// Find the content indent of a block scalar — the INDENT token right after
216/// the `|`/`>` indicator and its NEWLINE. Returns `None` if the scalar is
217/// empty (no content lines).
218fn block_scalar_content_indent(scalar: &SyntaxNode) -> Option<usize> {
219    let mut after_newline = false;
220    for child in scalar.children_with_tokens() {
221        if let Some(tok) = child.as_token() {
222            match tok.kind() {
223                SyntaxKind::NEWLINE => after_newline = true,
224                SyntaxKind::WHITESPACE | SyntaxKind::INDENT if after_newline => {
225                    return Some(tok.text().len());
226                }
227                _ => after_newline = false,
228            }
229        }
230    }
231    None
232}
233
234impl MappingEntry {
235    /// Get the underlying syntax node (for debugging/testing)
236    #[cfg(test)]
237    pub(crate) fn syntax(&self) -> &SyntaxNode {
238        &self.0
239    }
240
241    /// Return the raw `KEY` wrapper node of this entry.
242    ///
243    /// The returned node has kind `KEY` and wraps the actual key content
244    /// (a scalar, mapping, or sequence node). Returns `None` for malformed
245    /// entries that have no key node.
246    ///
247    /// To compare the key against a value, prefer [`key_matches`](Self::key_matches).
248    pub(crate) fn key(&self) -> Option<SyntaxNode> {
249        self.0.children().find(|n| n.kind() == SyntaxKind::KEY)
250    }
251
252    /// Return `true` if the key of this entry matches `key`.
253    ///
254    /// Uses semantic YAML equality, so quoting style differences are ignored:
255    /// `"foo"`, `'foo'`, and `foo` all match the scalar `"foo"`. Returns
256    /// `false` if this entry has no key node.
257    pub fn key_matches(&self, key: impl crate::AsYaml) -> bool {
258        self.key().is_some_and(|k| key_content_matches(&k, key))
259    }
260
261    /// Return the raw `VALUE` wrapper node of this entry.
262    ///
263    /// The returned node has kind `VALUE` and wraps the actual value content
264    /// (a scalar, mapping, or sequence node). Returns `None` for malformed
265    /// entries that have no value node.
266    pub(crate) fn value(&self) -> Option<SyntaxNode> {
267        self.0.children().find(|n| n.kind() == SyntaxKind::VALUE)
268    }
269
270    /// Get the key of this entry as a [`YamlNode`](crate::as_yaml::YamlNode).
271    ///
272    /// Returns `None` for malformed entries that have no key.
273    pub fn key_node(&self) -> Option<crate::as_yaml::YamlNode> {
274        self.key()
275            .and_then(|k| k.children().next())
276            .and_then(crate::as_yaml::YamlNode::from_syntax)
277    }
278
279    /// Get the value of this entry as a [`YamlNode`](crate::as_yaml::YamlNode).
280    ///
281    /// Returns `None` for malformed entries that have no value.
282    pub fn value_node(&self) -> Option<crate::as_yaml::YamlNode> {
283        self.value()
284            .and_then(|v| v.children().next())
285            .and_then(crate::as_yaml::YamlNode::from_syntax)
286    }
287
288    /// Create a new mapping entry (key-value pair) not yet attached to any mapping.
289    ///
290    /// The entry is built as a standalone CST node; attach it to a mapping with
291    /// one of the `insert_*` methods. Block-style values (mappings, sequences)
292    /// are indented with 2 spaces relative to the key.
293    ///
294    /// This builds the entry assuming the key will sit at column 0. If the
295    /// entry will be inserted into a mapping at a deeper column, use
296    /// [`new_at_indent`](Self::new_at_indent) instead so a node-backed block
297    /// value's interior indentation lines up with the target column.
298    pub fn new(
299        key: impl crate::AsYaml,
300        value: impl crate::AsYaml,
301        flow_context: bool,
302        use_explicit_key: bool,
303    ) -> Self {
304        Self::new_at_indent(key, value, flow_context, use_explicit_key, 0)
305    }
306
307    /// Like [`new`](Self::new) but builds the entry as if its key were
308    /// placed at column `key_indent`. Only affects how a node-backed block
309    /// collection value is re-indented; other value kinds render the same
310    /// as with [`new`](Self::new).
311    pub fn new_at_indent(
312        key: impl crate::AsYaml,
313        value: impl crate::AsYaml,
314        flow_context: bool,
315        use_explicit_key: bool,
316        key_indent: usize,
317    ) -> Self {
318        let mut builder = GreenNodeBuilder::new();
319        builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
320
321        if use_explicit_key {
322            // Add explicit key indicator as child of MAPPING_ENTRY
323            builder.token(SyntaxKind::QUESTION.into(), "?");
324            builder.token(SyntaxKind::WHITESPACE.into(), " ");
325        }
326
327        // Build KEY
328        builder.start_node(SyntaxKind::KEY.into());
329        let key_has_newline = key.build_content(&mut builder, 0, false);
330        debug_assert!(!key_has_newline, "Keys should not end with newlines");
331        builder.finish_node();
332
333        if use_explicit_key {
334            // Add newline after key for explicit format
335            builder.token(SyntaxKind::NEWLINE.into(), "\n");
336        }
337
338        builder.token(SyntaxKind::COLON.into(), ":");
339
340        // Node-backed block collections and block scalars need the same
341        // re-indent treatment that MappingEntry::set_value applies when
342        // the source's own base indent doesn't already put it at the
343        // correct target column: put the value on the next line and shift
344        // its interior indentation to line up under this entry.
345        //
346        // When key_indent == 0 (root-level insertion), col-0 dashes render
347        // as valid compact-style block content, so we leave sources with
348        // source_base == 0 alone to preserve existing formatting choices.
349        // For nested insertion (key_indent > 0), col-0 content would be
350        // parsed as escaping the parent's scope, so we must re-indent.
351        let block_source = (!flow_context && !value.is_inline())
352            .then(|| value.as_node())
353            .flatten()
354            .filter(|_| {
355                matches!(
356                    value.kind(),
357                    crate::as_yaml::YamlKind::Mapping
358                        | crate::as_yaml::YamlKind::Sequence
359                        | crate::as_yaml::YamlKind::Scalar
360                        | crate::as_yaml::YamlKind::Tagged(_)
361                )
362            })
363            // Route through build_block_value_node whenever indentation
364            // needs to shift, OR when the source is empty (so a future
365            // insertion has an INDENT hint to drop into rather than
366            // landing at column 0).
367            .filter(|src| {
368                key_indent > 0
369                    || crate::as_yaml::source_base_indent(src) > 0
370                    || src.children().next().is_none()
371            });
372
373        let value_ends_with_newline = if let Some(source) = block_source {
374            let (value_node, ends_with_newline) = build_block_value_node(source, key_indent + 2);
375            builder.start_node(value_node.kind().into());
376            add_node_children_to(&mut builder, &value_node);
377            builder.finish_node();
378            ends_with_newline
379        } else {
380            // Build VALUE
381            // Note: For explicit keys, we don't add a space here because
382            // the VALUE building logic below will add it for inline values
383            builder.start_node(SyntaxKind::VALUE.into());
384            let ends_with_newline = match (value.is_inline(), value.kind()) {
385                // Inline values (scalars, flow collections) go on same line with space
386                (true, _) => {
387                    builder.token(SyntaxKind::WHITESPACE.into(), " ");
388                    // Note: TAGGED_NODE values (!!set, !!omap, !!pairs) are inline but may
389                    // end with newlines from their block-style content
390                    value.build_content(&mut builder, 0, flow_context)
391                }
392                // Block mappings and sequences start on new line but don't get pre-indented
393                // They handle their own indentation via copy_node_content_with_indent
394                (false, crate::as_yaml::YamlKind::Mapping)
395                | (false, crate::as_yaml::YamlKind::Sequence) => {
396                    builder.token(SyntaxKind::NEWLINE.into(), "\n");
397                    value.build_content(&mut builder, 0, flow_context)
398                }
399                // Block scalars (literal/folded) get newline and indent
400                (false, _) => {
401                    builder.token(SyntaxKind::NEWLINE.into(), "\n");
402                    builder.token(SyntaxKind::INDENT.into(), "  ");
403                    value.build_content(&mut builder, 2, flow_context)
404                }
405            };
406            builder.finish_node(); // VALUE
407            ends_with_newline
408        };
409
410        // Every block-style MAPPING_ENTRY ends with NEWLINE (newline ownership model)
411        if !value_ends_with_newline {
412            builder.token(SyntaxKind::NEWLINE.into(), "\n");
413        }
414
415        builder.finish_node(); // MAPPING_ENTRY
416        MappingEntry(SyntaxNode::new_root_mut(builder.finish()))
417    }
418
419    /// Column at which this entry's key sits on its line.
420    ///
421    /// Returns 0 for root-level entries or entries that share a line with
422    /// something else (an explicit-key form, a flow-style parent, etc.).
423    fn indent_of_line(&self) -> usize {
424        crate::as_yaml::source_base_indent(&self.0)
425    }
426
427    /// Index of the entry's VALUE child in its own children list, if any.
428    fn value_index(&self) -> Option<usize> {
429        self.0
430            .children_with_tokens()
431            .position(|c| c.as_node().is_some_and(|n| n.kind() == SyntaxKind::VALUE))
432    }
433
434    /// Insert `tokens` at position `at` in `self.0`.
435    fn insert_at(&self, at: usize, tokens: Vec<rowan::SyntaxToken<Lang>>) {
436        let elems: Vec<_> = tokens.into_iter().map(Into::into).collect();
437        self.0.splice_children(at..at, elems);
438    }
439
440    /// Append `tokens` at the end of `self.0`.
441    fn append(&self, tokens: Vec<rowan::SyntaxToken<Lang>>) {
442        let end = self.0.children_with_tokens().count();
443        self.insert_at(end, tokens);
444    }
445
446    /// Detach the last child of `self.0` if it's a NEWLINE token.
447    fn detach_last_if_newline(&self) {
448        if let Some(last) = self.0.last_child_or_token() {
449            if last
450                .as_token()
451                .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
452            {
453                last.detach();
454            }
455        }
456    }
457
458    /// True if the entry ends with a NEWLINE (anywhere inside).
459    fn ends_with_newline(&self) -> bool {
460        self.0
461            .last_token()
462            .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
463    }
464
465    /// Replace this entry's value with a block sequence or mapping from
466    /// another CST. Emits the value on the line after the key, indented to
467    /// this entry's nesting depth + 2, with the source's interior
468    /// indentation shifted to match. Called only from [`set_value`] for
469    /// non-flow parents whose new value is a node-backed block collection.
470    ///
471    /// Uses targeted CST edits (splice the VALUE, detach specific
472    /// surrounding tokens) rather than rebuilding the whole entry, so
473    /// unrelated tokens (leading comments, alignment whitespace) survive.
474    fn set_block_value(&self, source: &SyntaxNode) {
475        let (new_value_node, value_ends_with_newline) =
476            build_block_value_node(source, self.indent_of_line() + 2);
477
478        // For explicit-key entries the parser sometimes stores the entry's
479        // terminating NEWLINE at the MAPPING level (as a sibling of this
480        // MAPPING_ENTRY) rather than inside it. Track this so we can drop
481        // the sibling NEWLINE below if the new block value provides its own.
482        let old_entry_owned_trailing_nl = self.ends_with_newline();
483
484        let Some(value_idx) = self.value_index() else {
485            return;
486        };
487        let trailing_comment = find_key_line_comment(&self.0);
488        // Grab the WHITESPACE between COLON and VALUE before we splice so
489        // we can detach it later without re-walking. It's the only shape
490        // that "COLON followed by WHITESPACE followed by VALUE" takes.
491        let ws_between = self
492            .0
493            .children_with_tokens()
494            .nth(value_idx.wrapping_sub(1))
495            .and_then(|c| c.into_token())
496            .filter(|t| t.kind() == SyntaxKind::WHITESPACE);
497
498        self.0
499            .splice_children(value_idx..value_idx + 1, vec![new_value_node.into()]);
500        // Block content lives on the next line, so drop the inline space.
501        if let Some(ws) = ws_between {
502            ws.detach();
503        }
504        // Slot the rescued comment (`WHITESPACE COMMENT`) right before the
505        // new VALUE so it stays on the key line.
506        if let Some((ws, comment)) = trailing_comment {
507            if let Some(idx) = self.value_index() {
508                self.insert_at(
509                    idx,
510                    vec![
511                        fresh_token(SyntaxKind::WHITESPACE, &ws),
512                        fresh_token(SyntaxKind::COMMENT, &comment),
513                    ],
514                );
515            }
516        }
517
518        // Keep exactly one trailing NEWLINE on the entry, either inside
519        // VALUE (block content usually provides one), at MAPPING_ENTRY
520        // level, or at MAPPING level (for explicit-key entries).
521        if value_ends_with_newline {
522            // Drop the MAPPING_ENTRY-level trailing NEWLINE that was the
523            // OLD entry's terminator — the new VALUE has its own.
524            self.detach_last_if_newline();
525        } else if !self.ends_with_newline() {
526            // New value doesn't provide one and the entry doesn't have
527            // one — append so we don't glue onto the next entry.
528            self.append(vec![fresh_token(SyntaxKind::NEWLINE, "\n")]);
529        }
530        let entry_ends_with_nl = self.ends_with_newline();
531        if entry_ends_with_nl && !old_entry_owned_trailing_nl {
532            if let Some(next) = self.0.next_sibling_or_token() {
533                if next
534                    .as_token()
535                    .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
536                {
537                    next.detach();
538                }
539            }
540        }
541    }
542
543    /// Replace the value of this entry in place, preserving the key and surrounding whitespace.
544    pub fn set_value(&self, new_value: impl crate::AsYaml, flow_context: bool) {
545        use crate::as_yaml::YamlKind;
546
547        // A block collection or block scalar coming from another CST goes
548        // through set_block_value, which does targeted CST edits to swap
549        // the VALUE and adjust the surrounding tokens (drop the WHITESPACE
550        // between COLON and VALUE, drop the redundant trailing NEWLINE,
551        // rescue any inline comment on the old value line). Flow context
552        // (parent mapping is flow-style) means block content isn't valid
553        // there, so we fall through to the default path.
554        let block_source = (!flow_context && !new_value.is_inline())
555            .then(|| new_value.as_node())
556            .flatten()
557            .filter(|_| {
558                matches!(
559                    new_value.kind(),
560                    YamlKind::Mapping | YamlKind::Sequence | YamlKind::Scalar | YamlKind::Tagged(_)
561                )
562            });
563        if let Some(source) = block_source {
564            self.set_block_value(source);
565            return;
566        }
567
568        // Build the new VALUE. If the source is a node-backed inline value
569        // preceded by an `&anchor` in its parent VALUE, preserve that
570        // anchor so aliases pointing at the moved value still resolve.
571        // Any key-line comment from the old entry (whether it lived inside
572        // the old VALUE or at MAPPING_ENTRY level between COLON and VALUE)
573        // is embedded inside the new inline VALUE so it stays on the key
574        // line and doesn't swallow anything.
575        let anchor = new_value.as_node().and_then(source_anchor_text);
576        let trailing_comment = find_key_line_comment(&self.0);
577        let mut value_builder = GreenNodeBuilder::new();
578        value_builder.start_node(SyntaxKind::VALUE.into());
579        if let Some(a) = anchor.as_deref() {
580            value_builder.token(SyntaxKind::ANCHOR.into(), a);
581            value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
582        }
583        new_value.build_content(&mut value_builder, 0, flow_context);
584        if let Some((ws, comment)) = &trailing_comment {
585            value_builder.token(SyntaxKind::WHITESPACE.into(), ws);
586            value_builder.token(SyntaxKind::COMMENT.into(), comment);
587        }
588        value_builder.finish_node();
589        let new_value_node = SyntaxNode::new_root_mut(value_builder.finish());
590
591        let old_was_block = self.value().is_some_and(|v| v.text().contains_char('\n'));
592        if old_was_block {
593            self.rebuild_with_inline_value(&new_value_node);
594            return;
595        }
596
597        // Targeted splice: replace the VALUE child in place; siblings and
598        // surrounding tokens (comments, alignment WS) survive untouched.
599        if let Some(value_idx) = self
600            .0
601            .children_with_tokens()
602            .position(|c| c.as_node().is_some_and(|n| n.kind() == SyntaxKind::VALUE))
603        {
604            self.0
605                .splice_children(value_idx..value_idx + 1, vec![new_value_node.into()]);
606            return;
607        }
608
609        // No VALUE node in this entry (malformed input). Insert one right
610        // after the COLON with a leading WHITESPACE.
611        if let Some(colon_idx) = self
612            .0
613            .children_with_tokens()
614            .position(|c| c.as_token().is_some_and(|t| t.kind() == SyntaxKind::COLON))
615        {
616            let insert_at = colon_idx + 1;
617            let ws = fresh_token(SyntaxKind::WHITESPACE, " ");
618            self.0
619                .splice_children(insert_at..insert_at, vec![ws.into(), new_value_node.into()]);
620        }
621    }
622
623    /// Replace this entry's block value with `new_value_node` (an inline
624    /// VALUE) that sits on the same line as the key. Uses targeted CST
625    /// edits: splice the VALUE, ensure a single WHITESPACE sits between
626    /// COLON and it, rescue any inline comment on the key line so it
627    /// survives, and append a trailing NEWLINE if the entry doesn't
628    /// already end with one.
629    fn rebuild_with_inline_value(&self, new_value_node: &SyntaxNode) {
630        let Some(value_idx) = self.value_index() else {
631            return;
632        };
633
634        // If the parser stored a key-line comment as a sibling of the old
635        // block VALUE (`k:  # note\n  - block`), detach it: the new inline
636        // `new_value_node` already carries the rescued comment inside
637        // itself. Leaving the old tokens would duplicate the comment and
638        // — since comments extend to end of line — swallow the new value.
639        let stale: Vec<_> = self
640            .0
641            .children_with_tokens()
642            .take(value_idx)
643            .filter_map(|c| c.into_token())
644            .filter(|t| matches!(t.kind(), SyntaxKind::WHITESPACE | SyntaxKind::COMMENT))
645            .collect();
646        if stale.iter().any(|t| t.kind() == SyntaxKind::COMMENT) {
647            for tok in stale {
648                tok.detach();
649            }
650        }
651
652        // Swap VALUE (its index may have moved if we detached above).
653        let value_idx = self.value_index().expect("VALUE was here a moment ago");
654        self.0.splice_children(
655            value_idx..value_idx + 1,
656            vec![new_value_node.clone().into()],
657        );
658
659        // Ensure a single WHITESPACE sits between COLON and VALUE — the
660        // old block VALUE typically had none.
661        if let Some(idx) = self.value_index() {
662            let prev_is_ws = idx.checked_sub(1).is_some_and(|p| {
663                self.0
664                    .children_with_tokens()
665                    .nth(p)
666                    .and_then(|c| c.into_token())
667                    .is_some_and(|t| t.kind() == SyntaxKind::WHITESPACE)
668            });
669            if !prev_is_ws {
670                self.insert_at(idx, vec![fresh_token(SyntaxKind::WHITESPACE, " ")]);
671            }
672        }
673
674        // Inline values don't own a trailing NEWLINE; ensure the entry has
675        // one.
676        if !self.ends_with_newline() {
677            self.append(vec![fresh_token(SyntaxKind::NEWLINE, "\n")]);
678        }
679    }
680
681    /// Detach this entry from its parent mapping, effectively removing it.
682    ///
683    /// The entry node is detached from the tree; the `MappingEntry` value is
684    /// consumed. To retrieve the removed entry from a mapping (and get back a
685    /// `MappingEntry` you can inspect), use [`Mapping::remove`] instead.
686    pub fn discard(self) {
687        self.0.detach();
688    }
689
690    /// Remove this entry from its parent mapping.
691    ///
692    /// This is a convenience method that calls [`discard`](Self::discard)
693    /// internally. It's useful when you have a [`MappingEntry`] (e.g., from
694    /// [`find_all_entries_by_key`](Mapping::find_all_entries_by_key)) and want
695    /// to remove it without retrieving it from the mapping again.
696    ///
697    /// Consumes `self` and detaches the entry from the parent mapping.
698    pub fn remove(self) {
699        self.discard();
700    }
701}
702
703ast_node!(Mapping, MAPPING, "A YAML mapping (key-value pairs)");
704
705impl Mapping {
706    /// Dump the CST (Concrete Syntax Tree) structure to a human-readable string.
707    ///
708    /// This is intended for debugging and testing. The output shows the full
709    /// node hierarchy with indentation.
710    pub fn dump_cst(&self) -> String {
711        dump_cst_to_string(&self.0, 0)
712    }
713
714    /// Iterate over all keys in this mapping as [`YamlNode`](crate::as_yaml::YamlNode)s.
715    ///
716    /// Each key is returned as a [`YamlNode`](crate::as_yaml::YamlNode) wrapping
717    /// the underlying CST node, preserving quoting style and other formatting.
718    /// The nodes implement [`AsYaml`](crate::AsYaml), so they can be passed
719    /// back to [`get`](Self::get), [`contains_key`](Self::contains_key), etc.,
720    /// and compared semantically with [`yaml_eq`](crate::yaml_eq).
721    ///
722    /// Prefer [`entries`](Self::entries) when you also need the values, or
723    /// [`iter`](Self::iter) for `(key, value)` pairs as `(YamlNode, YamlNode)`.
724    /// For raw CST nodes, use `pairs()`.
725    pub fn keys(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
726        self.pairs().filter_map(|(k, _)| {
727            k.children()
728                .next()
729                .and_then(crate::as_yaml::YamlNode::from_syntax)
730        })
731    }
732
733    /// Iterate over raw KEY/VALUE syntax nodes for each mapping entry.
734    ///
735    /// Each item is `(key_node, value_node)`, both raw CST wrapper nodes
736    /// (`KEY`/`VALUE`), not the content nodes inside them. Entries with a
737    /// missing key or value (which indicate a parse error in the source) are
738    /// silently skipped.
739    ///
740    /// For most use cases prefer [`iter`](Self::iter) (which yields
741    /// `(YamlNode, YamlNode)` pairs) or [`entries`](Self::entries) (which
742    /// yields typed [`MappingEntry`] handles that give access to the full
743    /// entry including key, value, and mutation methods).
744    pub(crate) fn pairs(&self) -> impl Iterator<Item = (SyntaxNode, SyntaxNode)> + '_ {
745        self.0
746            .children()
747            .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
748            .filter_map(|entry| {
749                let key = entry.children().find(|n| n.kind() == SyntaxKind::KEY)?;
750                let value = entry.children().find(|n| n.kind() == SyntaxKind::VALUE)?;
751                Some((key, value))
752            })
753    }
754
755    /// Get the value associated with `key` as a [`YamlNode`](crate::as_yaml::YamlNode).
756    ///
757    /// Returns `None` if the key does not exist.
758    pub fn get(&self, key: impl crate::AsYaml) -> Option<crate::as_yaml::YamlNode> {
759        self.get_node(key)
760            .and_then(crate::as_yaml::YamlNode::from_syntax)
761    }
762
763    /// Get the raw content syntax node for `key` (for advanced CST access).
764    ///
765    /// Returns the content node inside the `VALUE` wrapper — i.e. the actual
766    /// `SCALAR`, `MAPPING`, or `SEQUENCE` node. Returns `None` if the key does
767    /// not exist. For most use cases prefer [`get`](Self::get).
768    pub(crate) fn get_node(&self, key: impl crate::AsYaml) -> Option<SyntaxNode> {
769        self.find_entry_by_key(key)
770            .and_then(|entry| entry.value())
771            .and_then(|value_node| {
772                // VALUE nodes wrap the actual content, return the content instead
773                value_node.children().next()
774            })
775    }
776
777    /// Get the value for `key` as a nested [`Mapping`].
778    ///
779    /// Returns `None` if the key does not exist or its value is not a mapping.
780    pub fn get_mapping(&self, key: impl crate::AsYaml) -> Option<Mapping> {
781        self.get(key).and_then(|n| n.as_mapping().cloned())
782    }
783
784    /// Modify a nested mapping in place by applying a closure to it.
785    ///
786    /// Returns `true` if `key` exists and its value is a mapping (and `f` was
787    /// called); returns `false` if the key is missing or its value is not a
788    /// mapping.
789    ///
790    /// Because [`Mapping`] uses interior mutability via rowan's `SyntaxNode`,
791    /// the closure receives a shared reference — mutations are still possible
792    /// through the node's `set`, `remove`, and other `&self` methods.
793    pub fn modify_mapping<F>(&self, key: impl crate::AsYaml, f: F) -> bool
794    where
795        F: FnOnce(&Mapping),
796    {
797        // Find the MAPPING_ENTRY for this key
798        let children: Vec<_> = self.0.children_with_tokens().collect();
799        for (i, child) in children.iter().enumerate() {
800            if let Some(node) = child.as_node() {
801                if node.kind() == SyntaxKind::MAPPING_ENTRY {
802                    if let Some(key_node) = node.children().find(|n| n.kind() == SyntaxKind::KEY) {
803                        if key_content_matches(&key_node, &key) {
804                            // Found the entry, now find the VALUE node
805                            if let Some(value_node) =
806                                node.children().find(|n| n.kind() == SyntaxKind::VALUE)
807                            {
808                                // Check if the value is a mapping
809                                if let Some(mapping_node) = value_node
810                                    .children()
811                                    .find(|n| n.kind() == SyntaxKind::MAPPING)
812                                {
813                                    // Create a Mapping and apply the function
814                                    let mapping = Mapping(mapping_node);
815                                    f(&mapping);
816
817                                    // Replace the old MAPPING_ENTRY with updated one
818                                    let entry_children: Vec<_> =
819                                        node.children_with_tokens().collect();
820                                    let mut builder = GreenNodeBuilder::new();
821                                    builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
822
823                                    for entry_child in entry_children {
824                                        match entry_child {
825                                            rowan::NodeOrToken::Node(n)
826                                                if n.kind() == SyntaxKind::VALUE =>
827                                            {
828                                                // Replace the VALUE node
829                                                builder.start_node(SyntaxKind::VALUE.into());
830
831                                                // First copy all non-MAPPING children from the original VALUE node (preserving structure)
832                                                for value_child in n.children_with_tokens() {
833                                                    match value_child {
834                                                        rowan::NodeOrToken::Node(child_node)
835                                                            if child_node.kind()
836                                                                == SyntaxKind::MAPPING =>
837                                                        {
838                                                            // Replace the MAPPING node with our updated mapping
839                                                            crate::yaml::copy_node_to_builder(
840                                                                &mut builder,
841                                                                &mapping.0,
842                                                            );
843                                                        }
844                                                        rowan::NodeOrToken::Node(child_node) => {
845                                                            // Copy other nodes as-is (preserving formatting)
846                                                            crate::yaml::copy_node_to_builder(
847                                                                &mut builder,
848                                                                &child_node,
849                                                            );
850                                                        }
851                                                        rowan::NodeOrToken::Token(token) => {
852                                                            // Copy tokens as-is (preserving newlines, indents, etc)
853                                                            builder.token(
854                                                                token.kind().into(),
855                                                                token.text(),
856                                                            );
857                                                        }
858                                                    }
859                                                }
860
861                                                builder.finish_node(); // VALUE
862                                            }
863                                            rowan::NodeOrToken::Node(n) => {
864                                                crate::yaml::copy_node_to_builder(&mut builder, &n);
865                                            }
866                                            rowan::NodeOrToken::Token(t) => {
867                                                builder.token(t.kind().into(), t.text());
868                                            }
869                                        }
870                                    }
871
872                                    builder.finish_node(); // MAPPING_ENTRY
873                                    let new_entry = SyntaxNode::new_root_mut(builder.finish());
874                                    self.0.splice_children(i..i + 1, vec![new_entry.into()]);
875                                    return true;
876                                }
877                            }
878                        }
879                    }
880                }
881            }
882        }
883        false
884    }
885
886    /// Get the value for `key` as a nested [`Sequence`].
887    ///
888    /// Returns `None` if the key does not exist or its value is not a sequence.
889    pub fn get_sequence(&self, key: impl crate::AsYaml) -> Option<Sequence> {
890        self.get(key).and_then(|n| n.as_sequence().cloned())
891    }
892
893    /// Returns `true` if this mapping contains an entry with the given key.
894    pub fn contains_key(&self, key: impl crate::AsYaml) -> bool {
895        self.find_entry_by_key(key).is_some()
896    }
897
898    /// Iterate over the raw `KEY` wrapper nodes for all entries.
899    ///
900    /// For most use cases prefer [`keys`](Self::keys) which yields
901    /// [`YamlNode`](crate::as_yaml::YamlNode) keys (formatting-preserving but
902    /// comparable via `yaml_eq`), or [`entries`](Self::entries) which yields
903    /// full [`MappingEntry`] handles.
904    pub(crate) fn key_nodes(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
905        self.pairs().map(|(k, _)| k)
906    }
907
908    /// Check if the mapping is empty
909    pub fn is_empty(&self) -> bool {
910        self.pairs().next().is_none()
911    }
912
913    /// Get the number of key-value pairs in this mapping
914    pub fn len(&self) -> usize {
915        self.pairs().count()
916    }
917
918    /// Iterate over the values in this mapping as [`YamlNode`](crate::as_yaml::YamlNode)s.
919    ///
920    /// Only entries whose value can be successfully wrapped in a `YamlNode` are
921    /// yielded; malformed or unrecognised value nodes are silently skipped.
922    /// Use [`iter`](Self::iter) to get both the key and value simultaneously, or
923    /// `pairs()` for the raw `SyntaxNode` pairs.
924    pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
925        self.pairs().filter_map(|(_, value_node)| {
926            // VALUE node contains the actual content as children
927            value_node
928                .children()
929                .next()
930                .and_then(crate::as_yaml::YamlNode::from_syntax)
931        })
932    }
933
934    /// Iterate over `(key, value)` pairs, both as [`YamlNode`](crate::as_yaml::YamlNode)s.
935    ///
936    /// Entries that cannot be fully wrapped (malformed key or value nodes) are
937    /// silently skipped. For raw CST nodes, use `pairs()`; for
938    /// typed entry handles, prefer [`entries`](Self::entries).
939    pub fn iter(
940        &self,
941    ) -> impl Iterator<Item = (crate::as_yaml::YamlNode, crate::as_yaml::YamlNode)> + '_ {
942        self.pairs().filter_map(|(key_node, value_node)| {
943            // KEY and VALUE nodes wrap the actual content - extract children
944            let key = key_node
945                .children()
946                .next()
947                .and_then(crate::as_yaml::YamlNode::from_syntax)?;
948            let value = value_node
949                .children()
950                .next()
951                .and_then(crate::as_yaml::YamlNode::from_syntax)?;
952            Some((key, value))
953        })
954    }
955
956    /// Create a new empty mapping
957    pub fn new() -> Self {
958        let mut builder = GreenNodeBuilder::new();
959        builder.start_node(SyntaxKind::MAPPING.into());
960        builder.finish_node();
961        Mapping(SyntaxNode::new_root_mut(builder.finish()))
962    }
963
964    /// Reorder fields according to the specified order.
965    ///
966    /// Fields not in the order list will appear after the ordered fields,
967    /// in their original relative order.
968    pub fn reorder_fields<I, K>(&self, order: I)
969    where
970        I: IntoIterator<Item = K>,
971        K: crate::AsYaml,
972    {
973        let order_keys: Vec<K> = order.into_iter().collect();
974
975        // Collect all MAPPING_ENTRY nodes
976        let entry_nodes: Vec<SyntaxNode> = self
977            .0
978            .children()
979            .filter(|child| child.kind() == SyntaxKind::MAPPING_ENTRY)
980            .collect();
981
982        // Build ordered list: entries in specified order first, then unordered entries.
983        let mut ordered_entries: Vec<SyntaxNode> = Vec::new();
984        let mut remaining_entries: Vec<SyntaxNode> = entry_nodes;
985
986        for order_key in &order_keys {
987            if let Some(pos) = remaining_entries.iter().position(|entry| {
988                entry
989                    .children()
990                    .find(|n| n.kind() == SyntaxKind::KEY)
991                    .map(|k| key_content_matches(&k, order_key))
992                    .unwrap_or(false)
993            }) {
994                ordered_entries.push(remaining_entries.remove(pos));
995            }
996        }
997
998        let new_children: Vec<_> = ordered_entries
999            .into_iter()
1000            .chain(remaining_entries)
1001            .map(|node| node.into())
1002            .collect();
1003
1004        // Replace all children
1005        let children_count = self.0.children_with_tokens().count();
1006        self.0.splice_children(0..children_count, new_children);
1007    }
1008}
1009
1010/// Check whether the CST key node matches `key` using semantic equality.
1011///
1012/// `key_node` may be a raw KEY wrapper; `from_syntax_peeled` unwraps it.
1013fn key_content_matches(key_node: &SyntaxNode, key: impl crate::AsYaml) -> bool {
1014    match crate::as_yaml::YamlNode::from_syntax_peeled(key_node.clone()) {
1015        Some(node) => crate::as_yaml::yaml_eq(&node, &key),
1016        None => false,
1017    }
1018}
1019
1020impl Mapping {
1021    /// Check if this mapping is in flow style (JSON/inline format with `{}`).
1022    ///
1023    /// Returns `true` if the mapping uses flow style (e.g., `{key: value}`),
1024    /// `false` if it uses block style (e.g., `key: value`).
1025    pub fn is_flow_style(&self) -> bool {
1026        // Flow-style mappings start with LEFT_BRACE token
1027        self.0.children_with_tokens().any(|child| {
1028            child
1029                .as_token()
1030                .map(|token| token.kind() == SyntaxKind::LEFT_BRACE)
1031                .unwrap_or(false)
1032        })
1033    }
1034
1035    /// Find the [`MappingEntry`] whose key matches `key`, or `None` if not found.
1036    ///
1037    /// Matching is semantic (quoting style is ignored), so `"foo"`, `'foo'`,
1038    /// and `foo` all match the scalar `"foo"`.
1039    pub fn find_entry_by_key(&self, key: impl crate::AsYaml) -> Option<MappingEntry> {
1040        self.0
1041            .children()
1042            .filter_map(MappingEntry::cast)
1043            .find(|entry| entry.key().is_some_and(|k| key_content_matches(&k, &key)))
1044    }
1045
1046    /// Find all entries with a given key.
1047    ///
1048    /// Returns an iterator over all [`MappingEntry`] instances that match the
1049    /// given key. This is useful for handling duplicate keys in YAML (which
1050    /// are allowed by the spec but semantically ambiguous).
1051    ///
1052    /// Matching is semantic (quoting style is ignored), so `"foo"`, `'foo'`,
1053    /// and `foo` all match the scalar `"foo"`.
1054    ///
1055    /// # Example
1056    ///
1057    /// ```rust
1058    /// # use std::str::FromStr;
1059    /// # use yaml_edit::Document;
1060    /// let yaml = r#"
1061    /// Reference: First
1062    /// Reference: Second
1063    /// Reference: Third
1064    /// "#;
1065    ///
1066    /// let doc = Document::from_str(yaml).unwrap();
1067    /// let mapping = doc.as_mapping().unwrap();
1068    ///
1069    /// // Collect all Reference entries
1070    /// let refs: Vec<_> = mapping.find_all_entries_by_key("Reference").collect();
1071    /// assert_eq!(refs.len(), 3);
1072    ///
1073    /// // Remove all but the first occurrence
1074    /// let _: Vec<()> = refs.into_iter().skip(1).map(|entry| entry.remove()).collect();
1075    /// ```
1076    pub fn find_all_entries_by_key<'a>(
1077        &'a self,
1078        key: impl crate::AsYaml + 'a,
1079    ) -> impl Iterator<Item = MappingEntry> + 'a {
1080        self.0
1081            .children()
1082            .filter_map(MappingEntry::cast)
1083            .filter(move |entry| entry.key().is_some_and(|k| key_content_matches(&k, &key)))
1084    }
1085
1086    /// Iterate over all entries in this mapping as typed [`MappingEntry`] handles.
1087    ///
1088    /// Each [`MappingEntry`] gives access to the key, value, and mutation
1089    /// methods for that entry. For decoded `(key, value)` pairs, prefer
1090    /// [`iter`](Self::iter); for raw CST nodes, use `pairs()`.
1091    pub fn entries(&self) -> impl Iterator<Item = MappingEntry> {
1092        self.0.children().filter_map(MappingEntry::cast)
1093    }
1094
1095    /// Find the child index of a mapping entry by its key.
1096    ///
1097    /// Returns the index within the mapping's children (including non-entry
1098    /// tokens like whitespace), or `None` if no entry with the given key exists.
1099    /// This index is suitable for use with `splice_children`.
1100    pub fn find_entry_index_by_key(&self, key: impl crate::AsYaml) -> Option<usize> {
1101        // Look through all children (not just entries, to get accurate index)
1102        self.0
1103            .children_with_tokens()
1104            .enumerate()
1105            .find_map(|(i, child)| {
1106                let node = child.as_node()?;
1107                if node.kind() != SyntaxKind::MAPPING_ENTRY {
1108                    return None;
1109                }
1110                let entry = MappingEntry::cast(node.clone())?;
1111                if entry.key().is_some_and(|k| key_content_matches(&k, &key)) {
1112                    Some(i)
1113                } else {
1114                    None
1115                }
1116            })
1117    }
1118
1119    /// Set a key-value pair, replacing the existing value if the key exists or
1120    /// appending a new entry if it does not. Accepts any value that implements
1121    /// [`AsYaml`](crate::AsYaml) — scalars, mappings, sequences, etc.
1122    ///
1123    /// This method always succeeds; it never silently ignores input. See also
1124    /// [`insert_after`](Self::insert_after) and [`insert_before`](Self::insert_before)
1125    /// which return `bool` to indicate whether the anchor key was found.
1126    ///
1127    /// Mutates in place despite `&self` (see crate docs on interior mutability).
1128    pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
1129        self.set_as_yaml(key, value);
1130    }
1131
1132    /// Detect if this mapping uses explicit key indicators (?)
1133    fn uses_explicit_keys(&self) -> bool {
1134        // Check if any existing entries use explicit key format
1135        // The QUESTION token is a child of MAPPING_ENTRY (sibling to KEY), not inside KEY
1136        for child in self.0.children() {
1137            if child.kind() == SyntaxKind::MAPPING_ENTRY {
1138                // Check if this entry has a QUESTION token as a child
1139                if child.children_with_tokens().any(|t| {
1140                    t.as_token()
1141                        .is_some_and(|tok| tok.kind() == SyntaxKind::QUESTION)
1142                }) {
1143                    return true;
1144                }
1145            }
1146        }
1147        false
1148    }
1149
1150    /// Internal unified method to set any YAML value type
1151    fn set_as_yaml<K: crate::AsYaml, V: crate::AsYaml>(&self, key: K, value: V) {
1152        // Detect if this mapping is in flow style (JSON format)
1153        let flow_context = self.is_flow_style();
1154
1155        // Detect if existing entries use explicit keys
1156        let use_explicit_keys = self.uses_explicit_keys();
1157
1158        // First, look for an existing entry with this key
1159        for (i, child) in self.0.children_with_tokens().enumerate() {
1160            if let Some(node) = child.as_node() {
1161                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1162                    if let Some(entry) = MappingEntry::cast(node.clone()) {
1163                        // Check if this entry matches our key by comparing using yaml_eq
1164                        if let Some(entry_key_node) = entry.key() {
1165                            if key_content_matches(&entry_key_node, &key) {
1166                                // Found it! Update the value in place
1167                                entry.set_value(value, flow_context);
1168
1169                                self.0.splice_children(i..i + 1, vec![entry.0.into()]);
1170                                return;
1171                            }
1172                        }
1173                    }
1174                }
1175            }
1176        }
1177
1178        // Entry doesn't exist, create a new one. Tell the constructor at
1179        // what column its key will sit so a node-backed block value has its
1180        // interior indentation shifted to line up under the new entry.
1181        let key_indent = self.detect_indentation_level();
1182        let new_entry =
1183            MappingEntry::new_at_indent(key, value, flow_context, use_explicit_keys, key_indent);
1184        self.insert_entry_cst(&new_entry.0);
1185    }
1186
1187    /// Internal method to insert a new entry at the end (does not check for duplicates)
1188    fn insert_entry_cst(&self, new_entry: &SyntaxNode) {
1189        // Count children and check if last entry has trailing newline
1190        let mut count = 0;
1191        let mut last_mapping_entry: Option<SyntaxNode> = None;
1192
1193        for child in self.0.children_with_tokens() {
1194            count += 1;
1195            if let Some(node) = child.as_node() {
1196                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1197                    last_mapping_entry = Some(node.clone());
1198                }
1199            }
1200        }
1201
1202        // Check if the last entry ends with a newline, OR if the mapping itself has a trailing newline
1203        // Note: The newline is inside the entry, not a direct child of the mapping
1204        let has_trailing_newline = if let Some(entry) = &last_mapping_entry {
1205            entry
1206                .last_token()
1207                .map(|t| t.kind() == SyntaxKind::NEWLINE)
1208                .unwrap_or(false)
1209        } else {
1210            // No mapping entries yet - check if mapping itself has a trailing newline token
1211            self.0
1212                .last_token()
1213                .map(|t| t.kind() == SyntaxKind::NEWLINE)
1214                .unwrap_or(false)
1215        };
1216
1217        let mut new_elements = Vec::new();
1218
1219        // Always insert at the end - the newline is inside the last entry, not a separate child
1220        let insert_pos = count;
1221
1222        // Add indentation if needed
1223        let indent_level = self.detect_indentation_level();
1224        if indent_level > 0 && count > 0 {
1225            let mut builder = rowan::GreenNodeBuilder::new();
1226            builder.start_node(SyntaxKind::ROOT.into());
1227            // Only add NEWLINE if we're NOT inserting before an existing trailing newline
1228            if !has_trailing_newline {
1229                builder.token(SyntaxKind::NEWLINE.into(), "\n");
1230            }
1231            builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent_level));
1232            builder.finish_node();
1233            let node = SyntaxNode::new_root_mut(builder.finish());
1234            // Get ALL tokens, not just the first one
1235            for child in node.children_with_tokens() {
1236                if let rowan::NodeOrToken::Token(token) = child {
1237                    new_elements.push(token.into());
1238                }
1239            }
1240        } else if count > 0 && !has_trailing_newline {
1241            // Only add newline if there isn't already one
1242            let mut builder = rowan::GreenNodeBuilder::new();
1243            builder.start_node(SyntaxKind::ROOT.into());
1244            builder.token(SyntaxKind::NEWLINE.into(), "\n");
1245            builder.finish_node();
1246            let node = SyntaxNode::new_root_mut(builder.finish());
1247            if let Some(token) = node.first_token() {
1248                new_elements.push(token.into());
1249            }
1250        }
1251
1252        new_elements.push(new_entry.clone().into());
1253
1254        // Note: We don't add a trailing newline here because MappingEntry::new()
1255        // already adds one as part of the newline ownership model (entries own their trailing newlines)
1256
1257        self.0.splice_children(insert_pos..insert_pos, new_elements);
1258
1259        // If the mapping had been empty and sits inside a VALUE whose parent
1260        // MAPPING_ENTRY already carries a trailing NEWLINE, that NEWLINE was
1261        // a placeholder for the still-empty value ("key:\n    \n") and is
1262        // now redundant: the freshly inserted entry brings its own trailing
1263        // NEWLINE. Leaving it in place would produce a blank line at the end
1264        // of the drilled-in mapping (see issue #18).
1265        if count == 0 {
1266            let entry_ends_with_nl = new_entry
1267                .last_token()
1268                .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE);
1269            if entry_ends_with_nl {
1270                if let Some(parent_value) = self.0.parent() {
1271                    if parent_value.kind() == SyntaxKind::VALUE {
1272                        if let Some(parent_entry) = parent_value.parent() {
1273                            if parent_entry.kind() == SyntaxKind::MAPPING_ENTRY {
1274                                if let Some(last) = parent_entry.last_child_or_token() {
1275                                    if last
1276                                        .as_token()
1277                                        .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
1278                                    {
1279                                        last.detach();
1280                                    }
1281                                }
1282                            }
1283                        }
1284                    }
1285                }
1286            }
1287        }
1288    }
1289
1290    /// Compare two key nodes structurally
1291    pub(crate) fn compare_key_nodes(&self, actual: &SyntaxNode, expected: &SyntaxNode) -> bool {
1292        // Both must be KEY nodes
1293        if actual.kind() != SyntaxKind::KEY || expected.kind() != SyntaxKind::KEY {
1294            return actual.kind() == expected.kind()
1295                && self.compare_nodes_structurally(actual, expected);
1296        }
1297
1298        // Get the actual content nodes (skipping whitespace)
1299        let actual_content = self.get_key_content_nodes(actual);
1300        let expected_content = self.get_key_content_nodes(expected);
1301
1302        if actual_content.len() != expected_content.len() {
1303            return false;
1304        }
1305
1306        for (a, e) in actual_content.iter().zip(expected_content.iter()) {
1307            if !self.compare_nodes_structurally(a, e) {
1308                return false;
1309            }
1310        }
1311
1312        true
1313    }
1314
1315    /// Get the content nodes of a KEY, skipping whitespace and formatting
1316    fn get_key_content_nodes(&self, key_node: &SyntaxNode) -> Vec<SyntaxNode> {
1317        let mut nodes = Vec::new();
1318        for child in key_node.children_with_tokens() {
1319            match child {
1320                rowan::NodeOrToken::Node(n) => {
1321                    // Include all child nodes (sequences, mappings, etc.)
1322                    nodes.push(n);
1323                }
1324                rowan::NodeOrToken::Token(t) => {
1325                    // Include significant tokens as synthetic nodes
1326                    if t.kind() != SyntaxKind::WHITESPACE
1327                        && t.kind() != SyntaxKind::INDENT
1328                        && t.kind() != SyntaxKind::QUESTION
1329                    {
1330                        // Create a synthetic node for the token to enable comparison
1331                        let mut token_builder = GreenNodeBuilder::new();
1332                        token_builder.start_node(t.kind().into());
1333                        token_builder.token(t.kind().into(), t.text());
1334                        token_builder.finish_node();
1335                        nodes.push(SyntaxNode::new_root_mut(token_builder.finish()));
1336                    }
1337                }
1338            }
1339        }
1340        nodes
1341    }
1342
1343    /// Compare nodes structurally (for complex keys like sequences and mappings)
1344    fn compare_nodes_structurally(&self, node1: &SyntaxNode, node2: &SyntaxNode) -> bool {
1345        if node1.kind() != node2.kind() {
1346            return false;
1347        }
1348
1349        match node1.kind() {
1350            SyntaxKind::SCALAR => {
1351                // For SCALAR nodes, compare the semantic content (unquoted strings).
1352                // Kind is already confirmed so cast should not fail; use map_or(false, …)
1353                // as a safe fallback rather than unwrap.
1354                let s1 = Scalar::cast(node1.clone()).map(|s| s.as_string());
1355                let s2 = Scalar::cast(node2.clone()).map(|s| s.as_string());
1356                s1 == s2 && s1.is_some()
1357            }
1358            SyntaxKind::STRING => {
1359                // For string tokens, compare the actual content
1360                let mut iter1 = node1
1361                    .children_with_tokens()
1362                    .filter_map(|c| c.into_token())
1363                    .filter(|t| t.kind() == SyntaxKind::STRING);
1364                let mut iter2 = node2
1365                    .children_with_tokens()
1366                    .filter_map(|c| c.into_token())
1367                    .filter(|t| t.kind() == SyntaxKind::STRING);
1368                loop {
1369                    match (iter1.next(), iter2.next()) {
1370                        (Some(a), Some(b)) if a.text() == b.text() => continue,
1371                        (None, None) => return true,
1372                        _ => return false,
1373                    }
1374                }
1375            }
1376            SyntaxKind::SEQUENCE => {
1377                // Compare sequence entries
1378                let mut entries1 = node1
1379                    .children()
1380                    .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY);
1381                let mut entries2 = node2
1382                    .children()
1383                    .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY);
1384                loop {
1385                    match (entries1.next(), entries2.next()) {
1386                        (Some(e1), Some(e2)) => {
1387                            if !self.compare_sequence_entries(&e1, &e2) {
1388                                return false;
1389                            }
1390                        }
1391                        (None, None) => return true,
1392                        _ => return false,
1393                    }
1394                }
1395            }
1396            SyntaxKind::MAPPING => {
1397                // Compare mapping entries (order matters for keys)
1398                let mut entries1 = node1
1399                    .children()
1400                    .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
1401                let mut entries2 = node2
1402                    .children()
1403                    .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
1404                loop {
1405                    match (entries1.next(), entries2.next()) {
1406                        (Some(e1), Some(e2)) => {
1407                            if !self.compare_mapping_entries(&e1, &e2) {
1408                                return false;
1409                            }
1410                        }
1411                        (None, None) => return true,
1412                        _ => return false,
1413                    }
1414                }
1415            }
1416            _ => {
1417                // For other node types, compare token content
1418                let filter_tokens = |node: &SyntaxNode| {
1419                    node.children_with_tokens()
1420                        .filter_map(|c| c.into_token())
1421                        .filter(|t| {
1422                            t.kind() != SyntaxKind::WHITESPACE && t.kind() != SyntaxKind::INDENT
1423                        })
1424                };
1425                let mut iter1 = filter_tokens(node1);
1426                let mut iter2 = filter_tokens(node2);
1427                loop {
1428                    match (iter1.next(), iter2.next()) {
1429                        (Some(a), Some(b)) if a.kind() == b.kind() && a.text() == b.text() => {
1430                            continue
1431                        }
1432                        (None, None) => return true,
1433                        _ => return false,
1434                    }
1435                }
1436            }
1437        }
1438    }
1439
1440    /// Compare sequence entries
1441    fn compare_sequence_entries(&self, entry1: &SyntaxNode, entry2: &SyntaxNode) -> bool {
1442        let value1 = entry1.children().find(|n| n.kind() == SyntaxKind::VALUE);
1443        let value2 = entry2.children().find(|n| n.kind() == SyntaxKind::VALUE);
1444
1445        match (value1, value2) {
1446            (Some(v1), Some(v2)) => self.compare_nodes_structurally(&v1, &v2),
1447            (None, None) => true,
1448            _ => false,
1449        }
1450    }
1451
1452    /// Compare mapping entries
1453    fn compare_mapping_entries(&self, entry1: &SyntaxNode, entry2: &SyntaxNode) -> bool {
1454        let key1 = entry1.children().find(|n| n.kind() == SyntaxKind::KEY);
1455        let key2 = entry2.children().find(|n| n.kind() == SyntaxKind::KEY);
1456        let value1 = entry1.children().find(|n| n.kind() == SyntaxKind::VALUE);
1457        let value2 = entry2.children().find(|n| n.kind() == SyntaxKind::VALUE);
1458
1459        match ((key1, value1), (key2, value2)) {
1460            ((Some(k1), Some(v1)), (Some(k2), Some(v2))) => {
1461                self.compare_key_nodes(&k1, &k2) && self.compare_nodes_structurally(&v1, &v2)
1462            }
1463            ((Some(k1), None), (Some(k2), None)) => self.compare_key_nodes(&k1, &k2),
1464            ((None, Some(v1)), (None, Some(v2))) => self.compare_nodes_structurally(&v1, &v2),
1465            ((None, None), (None, None)) => true,
1466            _ => false,
1467        }
1468    }
1469
1470    /// Set a key-value pair with field ordering support
1471    /// If the key exists, updates its value. If the key doesn't exist, inserts it
1472    /// at the correct position based on the provided field order.
1473    /// Fields not in the order list are placed at the end.
1474    pub fn set_with_field_order<I, K>(
1475        &self,
1476        key: impl crate::AsYaml,
1477        value: impl crate::AsYaml,
1478        field_order: I,
1479    ) where
1480        I: IntoIterator<Item = K>,
1481        K: crate::AsYaml,
1482    {
1483        // Collect field_order so we can iterate it multiple times.
1484        let field_order: Vec<K> = field_order.into_iter().collect();
1485
1486        // First check if the key already exists - if so, just update it
1487        let children: Vec<_> = self.0.children_with_tokens().collect();
1488        for child in children.iter() {
1489            if let Some(node) = child.as_node() {
1490                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1491                    if let Some(key_node) = node.children().find(|n| n.kind() == SyntaxKind::KEY) {
1492                        if key_content_matches(&key_node, &key) {
1493                            // Key exists, update its value using unified method
1494                            self.set_as_yaml(&key, &value);
1495                            return;
1496                        }
1497                    }
1498                }
1499            }
1500        }
1501
1502        // Key doesn't exist, need to find the correct insertion position based on field order.
1503        // Find position of this key in the field order (if it matches any)
1504        let key_position_in_order = field_order
1505            .iter()
1506            .position(|field| crate::as_yaml::yaml_eq(&key, field));
1507
1508        if let Some(key_index) = key_position_in_order {
1509            // Key is in the field order, find the right position to insert
1510            let mut insert_after_node: Option<SyntaxNode> = None;
1511            let mut insert_before_node: Option<SyntaxNode> = None;
1512
1513            // Look backwards in field_order to find the last existing key before this one
1514            for field in field_order.iter().take(key_index).rev() {
1515                for child in children.iter() {
1516                    if let Some(node) = child.as_node() {
1517                        if node.kind() == SyntaxKind::MAPPING_ENTRY {
1518                            if let Some(key_node) =
1519                                node.children().find(|n| n.kind() == SyntaxKind::KEY)
1520                            {
1521                                if key_content_matches(&key_node, field) {
1522                                    insert_after_node = Some(node.clone());
1523                                    break;
1524                                }
1525                            }
1526                        }
1527                    }
1528                }
1529                if insert_after_node.is_some() {
1530                    break;
1531                }
1532            }
1533
1534            // If no predecessor found, look for the first existing key in document order
1535            // that comes after this one in field_order
1536            if insert_after_node.is_none() {
1537                for child in children.iter() {
1538                    if let Some(node) = child.as_node() {
1539                        if node.kind() == SyntaxKind::MAPPING_ENTRY {
1540                            if let Some(existing_key_node) =
1541                                node.children().find(|n| n.kind() == SyntaxKind::KEY)
1542                            {
1543                                // Find this existing key's position in field_order
1544                                let existing_key_position = field_order.iter().position(|field| {
1545                                    key_content_matches(&existing_key_node, field)
1546                                });
1547
1548                                // If this existing key comes after our new key in field_order, insert before it
1549                                if let Some(existing_pos) = existing_key_position {
1550                                    if existing_pos > key_index {
1551                                        insert_before_node = Some(node.clone());
1552                                        break;
1553                                    }
1554                                }
1555                            }
1556                        }
1557                    }
1558                }
1559            }
1560
1561            // Build the new entry with proper newline ownership
1562            let flow_context = self.is_flow_style();
1563            let use_explicit_keys = self.uses_explicit_keys();
1564            let key_indent = self.detect_indentation_level();
1565            let new_entry = MappingEntry::new_at_indent(
1566                &key,
1567                &value,
1568                flow_context,
1569                use_explicit_keys,
1570                key_indent,
1571            )
1572            .0;
1573
1574            // Insert after the target entry, ensuring it has a trailing newline
1575            if let Some(after_node) = insert_after_node {
1576                // Insert after after_node
1577                let idx = children
1578                    .iter()
1579                    .position(|c| c.as_node() == Some(&after_node))
1580                    .expect("after_node was found in children earlier");
1581
1582                // Ensure after_node has a trailing newline
1583                let has_trailing_newline = after_node
1584                    .last_token()
1585                    .map(|t| t.kind() == SyntaxKind::NEWLINE)
1586                    .unwrap_or(false);
1587
1588                if !has_trailing_newline {
1589                    // Add trailing newline to after_node
1590                    let entry_children_count = after_node.children_with_tokens().count();
1591                    let mut nl_builder = GreenNodeBuilder::new();
1592                    nl_builder.start_node(SyntaxKind::ROOT.into());
1593                    nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
1594                    nl_builder.finish_node();
1595                    let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
1596                    if let Some(token) = nl_node.first_token() {
1597                        after_node.splice_children(
1598                            entry_children_count..entry_children_count,
1599                            vec![token.into()],
1600                        );
1601                    }
1602                }
1603
1604                // Insert new entry after after_node
1605                self.0
1606                    .splice_children(idx + 1..idx + 1, vec![new_entry.into()]);
1607            } else if let Some(before_node) = insert_before_node {
1608                // Insert before before_node
1609                let idx = children
1610                    .iter()
1611                    .position(|c| c.as_node() == Some(&before_node))
1612                    .expect("before_node was found in children earlier");
1613
1614                // If there's a previous entry, ensure it has a trailing newline
1615                if idx > 0 {
1616                    if let Some(prev_entry) = children[..idx].iter().rev().find_map(|c| {
1617                        c.as_node()
1618                            .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
1619                    }) {
1620                        let has_trailing_newline = prev_entry
1621                            .last_token()
1622                            .map(|t| t.kind() == SyntaxKind::NEWLINE)
1623                            .unwrap_or(false);
1624
1625                        if !has_trailing_newline {
1626                            let entry_children_count = prev_entry.children_with_tokens().count();
1627                            let mut nl_builder = GreenNodeBuilder::new();
1628                            nl_builder.start_node(SyntaxKind::ROOT.into());
1629                            nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
1630                            nl_builder.finish_node();
1631                            let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
1632                            if let Some(token) = nl_node.first_token() {
1633                                prev_entry.splice_children(
1634                                    entry_children_count..entry_children_count,
1635                                    vec![token.into()],
1636                                );
1637                            }
1638                        }
1639                    }
1640                }
1641
1642                // Insert new entry before before_node
1643                self.0.splice_children(idx..idx, vec![new_entry.into()]);
1644            } else {
1645                // No existing ordered keys, just append using CST
1646                self.set_as_yaml(&key, &value);
1647            }
1648        } else {
1649            // Key is not in field order, append at the end using CST
1650            self.set_as_yaml(&key, &value);
1651        }
1652    }
1653
1654    /// Detect the indentation level (in spaces) used by entries in this mapping.
1655    ///
1656    /// Returns `0` for root-level mappings where entries have no leading indentation.
1657    pub fn detect_indentation_level(&self) -> usize {
1658        // Look for an INDENT token that precedes a KEY inside this mapping.
1659        // For a multi-entry mapping the parser stores that column here.
1660        for child in self.0.children_with_tokens() {
1661            if let Some(token) = child.as_token() {
1662                if token.kind() == SyntaxKind::INDENT {
1663                    return token.text().len();
1664                }
1665            }
1666        }
1667        // Single-entry mapping (or empty): the column of the first entry
1668        // isn't stored here, it's the INDENT that precedes the MAPPING node
1669        // in its parent's VALUE. Fall back to that.
1670        crate::as_yaml::source_base_indent(&self.0)
1671    }
1672
1673    /// Move a key-value pair to immediately after an existing key.
1674    ///
1675    /// If `new_key` already exists in the mapping, it is first **removed** from its
1676    /// current position and then re-inserted after `after_key` with the new value —
1677    /// so the key ends up at the requested position regardless of where it was before.
1678    ///
1679    /// If `after_key` is not found, returns `false` and leaves the mapping unchanged.
1680    /// Returns `true` on success.
1681    ///
1682    /// Use [`insert_after`](Self::insert_after) if you want existing entries to be
1683    /// updated in-place rather than moved.
1684    pub fn move_after(
1685        &self,
1686        after_key: impl crate::AsYaml,
1687        new_key: impl crate::AsYaml,
1688        new_value: impl crate::AsYaml,
1689    ) -> bool {
1690        self.move_after_impl(after_key, new_key, new_value)
1691    }
1692
1693    /// Internal implementation for move_after
1694    fn move_after_impl(
1695        &self,
1696        after_key: impl crate::AsYaml,
1697        new_key: impl crate::AsYaml,
1698        new_value: impl crate::AsYaml,
1699    ) -> bool {
1700        let children: Vec<_> = self.0.children_with_tokens().collect();
1701        let mut insert_position = None;
1702        let mut found_key = false;
1703        let mut last_value_end = 0;
1704
1705        // First, check if the new key already exists and remove it
1706        let mut i = 0;
1707        let mut removed_existing = false;
1708        while i < children.len() {
1709            if let Some(node) = children[i].as_node() {
1710                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1711                    // Look inside the MAPPING_ENTRY for the KEY
1712                    for key_child in node.children() {
1713                        if key_child.kind() == SyntaxKind::KEY
1714                            && key_content_matches(&key_child, &new_key)
1715                        {
1716                            // Found existing key, remove this entire MAPPING_ENTRY
1717                            let mut remove_range = i..i + 1;
1718
1719                            // Also remove any trailing newline
1720                            if i + 1 < children.len() {
1721                                if let Some(token) = children[i + 1].as_token() {
1722                                    if token.kind() == SyntaxKind::NEWLINE {
1723                                        remove_range = i..i + 2;
1724                                    }
1725                                }
1726                            }
1727
1728                            self.0.splice_children(remove_range, vec![]);
1729                            removed_existing = true;
1730                            break;
1731                        }
1732                    }
1733                    if removed_existing {
1734                        // Need to refresh children list after removal
1735                        break;
1736                    }
1737                }
1738            }
1739            if !removed_existing {
1740                i += 1;
1741            }
1742        }
1743
1744        // If we removed an existing key, refresh the children list
1745        let children = if removed_existing {
1746            self.0.children_with_tokens().collect()
1747        } else {
1748            children
1749        };
1750
1751        // Find the position after the specified key's value
1752        for (i, child) in children.iter().enumerate() {
1753            if let Some(node) = child.as_node() {
1754                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1755                    if found_key {
1756                        // Check if this MAPPING_ENTRY is at the root level
1757                        // Root level means it's not preceded by INDENT
1758                        let is_root_level = if i > 0 {
1759                            children
1760                                .get(i - 1)
1761                                .and_then(|c| c.as_token())
1762                                .map(|t| t.kind() != SyntaxKind::INDENT)
1763                                .unwrap_or(true)
1764                        } else {
1765                            true
1766                        };
1767
1768                        if is_root_level {
1769                            insert_position = Some(i);
1770                            break;
1771                        }
1772                    }
1773                    // Look inside the MAPPING_ENTRY for the KEY
1774                    for key_child in node.children() {
1775                        if key_child.kind() == SyntaxKind::KEY
1776                            && key_content_matches(&key_child, &after_key)
1777                        {
1778                            found_key = true;
1779                            last_value_end = i + 1; // After this entire MAPPING_ENTRY
1780                            break;
1781                        }
1782                    }
1783                } else if node.kind() == SyntaxKind::KEY {
1784                    if key_content_matches(node, &after_key) {
1785                        found_key = true;
1786                    }
1787                } else if node.kind() == SyntaxKind::SCALAR {
1788                    // For SCALAR nodes that might be keys
1789                    if key_content_matches(node, &after_key) && !found_key {
1790                        // This is likely the key we're looking for
1791                        found_key = true;
1792                        // Look ahead for the value
1793                        for (j, child_j) in children[(i + 1)..].iter().enumerate() {
1794                            if let Some(n) = child_j.as_node() {
1795                                if n.kind() == SyntaxKind::VALUE || n.kind() == SyntaxKind::SCALAR {
1796                                    last_value_end = i + 1 + j + 1;
1797                                    break;
1798                                }
1799                            }
1800                        }
1801                    }
1802                } else if node.kind() == SyntaxKind::VALUE && found_key {
1803                    // We're at the value of the found key
1804                    last_value_end = i + 1;
1805                }
1806            } else if let Some(token) = child.as_token() {
1807                if found_key && token.kind() == SyntaxKind::COMMENT {
1808                    // Check if this comment is at the top level (not indented)
1809                    // Top-level comments can be preceded by:
1810                    // 1. NEWLINE token (traditional case)
1811                    // 2. MAPPING_ENTRY node (when all newlines are inside the entry)
1812                    if i > 0 {
1813                        if let Some(prev) = children.get(i - 1) {
1814                            let is_top_level = if let Some(prev_token) = prev.as_token() {
1815                                // Preceded by token - check if it's NEWLINE (not INDENT)
1816                                prev_token.kind() == SyntaxKind::NEWLINE
1817                            } else if let Some(prev_node) = prev.as_node() {
1818                                // Preceded by node - check if it's a MAPPING_ENTRY
1819                                // (means all newlines were inside the entry)
1820                                prev_node.kind() == SyntaxKind::MAPPING_ENTRY
1821                            } else {
1822                                false
1823                            };
1824
1825                            if is_top_level {
1826                                // Top-level comment - insert before it
1827                                insert_position = Some(i);
1828                                break;
1829                            }
1830                        }
1831                    }
1832                } else if found_key && token.kind() == SyntaxKind::NEWLINE {
1833                    // Check if this is a root-level newline (not inside nested content)
1834                    // Root-level means not preceded by INDENT
1835                    let is_root_level = if i > 0 {
1836                        children
1837                            .get(i - 1)
1838                            .and_then(|c| c.as_token())
1839                            .map(|t| t.kind() != SyntaxKind::INDENT)
1840                            .unwrap_or(true)
1841                    } else {
1842                        true
1843                    };
1844
1845                    if is_root_level && i + 1 < children.len() {
1846                        if let Some(next) = children.get(i + 1) {
1847                            if let Some(next_token) = next.as_token() {
1848                                if next_token.kind() == SyntaxKind::NEWLINE
1849                                    || next_token.kind() == SyntaxKind::COMMENT
1850                                {
1851                                    // Blank line or comment follows - insert before
1852                                    // them so the new entry is right after the target
1853                                    // key and blank lines are preserved before the
1854                                    // next key
1855                                    insert_position = Some(i);
1856                                    break;
1857                                }
1858                            } else if next.as_node().is_some() {
1859                                // Node follows (likely MAPPING_ENTRY) - insert before
1860                                // this separator newline so the new entry is right
1861                                // after the target key
1862                                insert_position = Some(i);
1863                                break;
1864                            }
1865                        }
1866                    }
1867                }
1868            }
1869        }
1870
1871        // If we didn't find a newline but found the key, insert after the value
1872        if insert_position.is_none() && found_key && last_value_end > 0 {
1873            insert_position = Some(last_value_end);
1874        }
1875
1876        if let Some(pos) = insert_position {
1877            // Create new elements for the key-value pair
1878            let mut new_elements = Vec::new();
1879
1880            // Check if the previous entry has a trailing newline and add one if needed
1881            if pos > 0 {
1882                // Look backwards for the last MAPPING_ENTRY
1883                if let Some(prev_entry) = children[..pos].iter().rev().find_map(|child| {
1884                    child
1885                        .as_node()
1886                        .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
1887                }) {
1888                    // Check if it ends with NEWLINE
1889                    let has_newline = prev_entry
1890                        .last_token()
1891                        .map(|t| t.kind() == SyntaxKind::NEWLINE)
1892                        .unwrap_or(false);
1893
1894                    // If not, add one to the previous entry (not to the mapping)
1895                    if !has_newline {
1896                        let entry_children_count = prev_entry.children_with_tokens().count();
1897                        let mut nl_builder = GreenNodeBuilder::new();
1898                        nl_builder.start_node(SyntaxKind::ROOT.into());
1899                        nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
1900                        nl_builder.finish_node();
1901                        let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
1902                        if let Some(token) = nl_node.first_token() {
1903                            prev_entry.splice_children(
1904                                entry_children_count..entry_children_count,
1905                                vec![token.into()],
1906                            );
1907                        }
1908                    }
1909                }
1910            }
1911
1912            // Add indentation if needed
1913            // Check if we're inserting at root level by looking at the previous element
1914            let needs_indent = if pos > 0 {
1915                children
1916                    .get(pos - 1)
1917                    .and_then(|c| c.as_token())
1918                    .map(|t| t.kind() == SyntaxKind::INDENT)
1919                    .unwrap_or(false)
1920            } else {
1921                false
1922            };
1923
1924            if needs_indent {
1925                let indent_level = self.detect_indentation_level();
1926                if indent_level > 0 {
1927                    let mut indent_builder = GreenNodeBuilder::new();
1928                    indent_builder.start_node(SyntaxKind::ROOT.into());
1929                    indent_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent_level));
1930                    indent_builder.finish_node();
1931                    let indent_node = SyntaxNode::new_root_mut(indent_builder.finish());
1932                    if let Some(token) = indent_node.first_token() {
1933                        new_elements.push(token.into());
1934                    }
1935                }
1936            }
1937
1938            // Create the MAPPING_ENTRY node
1939            let (entry, _has_trailing_newline) = self.create_mapping_entry(&new_key, &new_value);
1940
1941            // Add the new entry (which already has its own trailing newline)
1942            new_elements.push(entry.into());
1943
1944            // Splice in the new elements
1945            self.0.splice_children(pos..pos, new_elements);
1946            true
1947        } else {
1948            false
1949        }
1950    }
1951
1952    /// Move a key-value pair to immediately before an existing key.
1953    ///
1954    /// If `new_key` already exists in the mapping, it is first **removed** from its
1955    /// current position and then re-inserted before `before_key` with the new value.
1956    ///
1957    /// If `before_key` is not found, returns `false` and leaves the mapping unchanged.
1958    /// Returns `true` on success.
1959    ///
1960    /// Use [`insert_before`](Self::insert_before) if you want existing entries to be
1961    /// updated in-place rather than moved.
1962    pub fn move_before(
1963        &self,
1964        before_key: impl crate::AsYaml,
1965        new_key: impl crate::AsYaml,
1966        new_value: impl crate::AsYaml,
1967    ) -> bool {
1968        self.move_before_impl(before_key, new_key, new_value)
1969    }
1970
1971    /// Internal implementation for move_before
1972    fn move_before_impl(
1973        &self,
1974        before_key: impl crate::AsYaml,
1975        new_key: impl crate::AsYaml,
1976        new_value: impl crate::AsYaml,
1977    ) -> bool {
1978        let children: Vec<_> = self.0.children_with_tokens().collect();
1979        let mut insert_position = None;
1980
1981        // First, check if the new key already exists and remove it
1982        let mut i = 0;
1983        let mut removed_existing = false;
1984        while i < children.len() {
1985            if let Some(node) = children[i].as_node() {
1986                if node.kind() == SyntaxKind::MAPPING_ENTRY {
1987                    // Look inside the MAPPING_ENTRY for the KEY
1988                    for key_child in node.children() {
1989                        if key_child.kind() == SyntaxKind::KEY
1990                            && key_content_matches(&key_child, &new_key)
1991                        {
1992                            // Found existing key, remove this entire MAPPING_ENTRY
1993                            let mut remove_range = i..i + 1;
1994
1995                            // Also remove any trailing newline
1996                            if i + 1 < children.len() {
1997                                if let Some(token) = children[i + 1].as_token() {
1998                                    if token.kind() == SyntaxKind::NEWLINE {
1999                                        remove_range = i..i + 2;
2000                                    }
2001                                }
2002                            }
2003
2004                            self.0.splice_children(remove_range, vec![]);
2005                            removed_existing = true;
2006                            break;
2007                        }
2008                    }
2009                    if removed_existing {
2010                        // Need to refresh children list after removal
2011                        break;
2012                    }
2013                } else if (node.kind() == SyntaxKind::KEY || node.kind() == SyntaxKind::SCALAR)
2014                    && key_content_matches(node, &new_key)
2015                {
2016                    // Found existing key, find its VALUE node and replace just that
2017                    // Look for colon, then VALUE node
2018                    for (offset, child_j) in children[(i + 1)..].iter().enumerate() {
2019                        if let Some(node) = child_j.as_node() {
2020                            if node.kind() == SyntaxKind::VALUE {
2021                                // Found the VALUE node to replace
2022                                // Build new VALUE node using the helper
2023                                let mut value_builder = GreenNodeBuilder::new();
2024                                Document::build_value_content(&mut value_builder, &new_value, 2);
2025                                let new_value_node =
2026                                    SyntaxNode::new_root_mut(value_builder.finish());
2027
2028                                // Replace just the VALUE node
2029                                let j = i + 1 + offset;
2030                                self.0
2031                                    .splice_children(j..j + 1, vec![new_value_node.into()]);
2032                                return true;
2033                            }
2034                        }
2035                    }
2036                    // If no VALUE node found, something's wrong with the structure
2037                    return false;
2038                }
2039            }
2040            if !removed_existing {
2041                i += 1;
2042            }
2043        }
2044
2045        // If we removed an existing key, refresh the children list
2046        let children = if removed_existing {
2047            self.0.children_with_tokens().collect()
2048        } else {
2049            children
2050        };
2051
2052        // Find the position before the specified key
2053        for (i, child) in children.iter().enumerate() {
2054            if let Some(node) = child.as_node() {
2055                if node.kind() == SyntaxKind::MAPPING_ENTRY {
2056                    // Look inside the MAPPING_ENTRY for the KEY
2057                    for key_child in node.children() {
2058                        if key_child.kind() == SyntaxKind::KEY
2059                            && key_content_matches(&key_child, &before_key)
2060                        {
2061                            // Found the key, insert before this MAPPING_ENTRY
2062                            let mut line_start = i;
2063                            for j in (0..i).rev() {
2064                                if let Some(token) = children[j].as_token() {
2065                                    if token.kind() == SyntaxKind::NEWLINE {
2066                                        line_start = j + 1;
2067                                        break;
2068                                    }
2069                                }
2070                            }
2071                            insert_position = Some(line_start);
2072                            break;
2073                        }
2074                    }
2075                } else if (node.kind() == SyntaxKind::KEY || node.kind() == SyntaxKind::SCALAR)
2076                    && key_content_matches(node, &before_key)
2077                {
2078                    // Found the key, insert before it
2079                    // Look back to find the start of this line
2080                    let mut line_start = i;
2081                    for j in (0..i).rev() {
2082                        if let Some(token) = children[j].as_token() {
2083                            if token.kind() == SyntaxKind::NEWLINE {
2084                                line_start = j + 1;
2085                                break;
2086                            }
2087                        }
2088                    }
2089                    insert_position = Some(line_start);
2090                    break;
2091                }
2092            }
2093        }
2094
2095        if let Some(pos) = insert_position {
2096            // Create new AST elements for the key-value pair
2097            // Build the complete key-value entry as separate nodes/tokens
2098
2099            // Build each element as a SyntaxNode/Token
2100            let mut new_elements = Vec::new();
2101
2102            // Create the MAPPING_ENTRY node
2103            let (entry, _has_trailing_newline) = self.create_mapping_entry(&new_key, &new_value);
2104            new_elements.push(entry.into());
2105
2106            // Note: create_mapping_entry already adds a trailing newline to the MAPPING_ENTRY
2107            // (newline ownership model), so we don't add an extra one here
2108
2109            // Splice in the new elements
2110            self.0.splice_children(pos..pos, new_elements);
2111            true
2112        } else {
2113            false
2114        }
2115    }
2116
2117    /// Insert a key-value pair at a specific index (0-based), preserving formatting.
2118    ///
2119    /// If `new_key` already exists in the mapping, the existing entry is replaced
2120    /// with a newly built entry at the **same position** (the `index` argument is
2121    /// ignored). Surrounding whitespace in the file is preserved, but the entry
2122    /// node itself is rebuilt (comments attached to the old entry may be lost).
2123    /// If `index` is out of bounds, the entry is appended at the end.
2124    pub fn insert_at_index_preserving(
2125        &self,
2126        index: usize,
2127        new_key: impl crate::AsYaml,
2128        new_value: impl crate::AsYaml,
2129    ) {
2130        // Create the new entry using create_mapping_entry
2131        let (new_entry, _has_trailing_newline) = self.create_mapping_entry(new_key, new_value);
2132
2133        // Check if key already exists in newly created entry for update detection
2134        if let Some(created_entry) = MappingEntry::cast(new_entry.clone()) {
2135            if let Some(created_key_node) = created_entry.key() {
2136                // Find existing entry with matching key by comparing nodes directly
2137                let existing_entry_opt = self.entries().find(|entry| {
2138                    if let Some(entry_key) = entry.key() {
2139                        self.compare_key_nodes(&entry_key, &created_key_node)
2140                    } else {
2141                        false
2142                    }
2143                });
2144
2145                if let Some(existing_entry) = existing_entry_opt {
2146                    // Replace the existing entry + its trailing newline if present
2147                    let children: Vec<_> = self.0.children_with_tokens().collect();
2148                    for (i, child) in children.iter().enumerate() {
2149                        let Some(node) = child.as_node() else {
2150                            continue;
2151                        };
2152                        if node != existing_entry.syntax() {
2153                            continue;
2154                        };
2155
2156                        // Simple: new entries always end with newline (DESIGN.md rule)
2157                        // Just replace the old entry node with the new one
2158                        self.0.splice_children(i..i + 1, vec![new_entry.into()]);
2159
2160                        return;
2161                    }
2162                }
2163            }
2164        }
2165
2166        // Key doesn't exist, insert at the specified index
2167        let children: Vec<_> = self.0.children_with_tokens().collect();
2168        // Count existing MAPPING_ENTRY nodes to find insertion point
2169        let mut entry_indices = Vec::new();
2170        for (i, child) in children.iter().enumerate() {
2171            if let Some(node) = child.as_node() {
2172                if node.kind() == SyntaxKind::MAPPING_ENTRY {
2173                    entry_indices.push(i);
2174                }
2175            }
2176        }
2177
2178        let mut new_elements = Vec::new();
2179
2180        // Determine insertion position in children_with_tokens space
2181        let insert_pos = if entry_indices.is_empty() {
2182            // Empty mapping - insert at beginning
2183            0
2184        } else if index >= entry_indices.len() {
2185            // Index beyond end - insert after last entry
2186            let last_entry_idx = entry_indices[entry_indices.len() - 1];
2187            // Find the end of the last entry (including its newline if present)
2188            let mut pos = last_entry_idx + 1;
2189            while pos < children.len() {
2190                if let Some(token) = children[pos].as_token() {
2191                    if token.kind() == SyntaxKind::NEWLINE {
2192                        pos += 1;
2193                        break;
2194                    }
2195                }
2196                pos += 1;
2197            }
2198            pos
2199        } else {
2200            // Insert before the entry at the specified index
2201            entry_indices[index]
2202        };
2203
2204        // Add newline before entry if inserting after existing content
2205        // Check if previous element ends with newline (either as token or inside node)
2206        if insert_pos > 0 && !entry_indices.is_empty() {
2207            let has_newline_before = if let Some(child) = children.get(insert_pos - 1) {
2208                match child {
2209                    rowan::NodeOrToken::Token(t) => t.kind() == SyntaxKind::NEWLINE,
2210                    rowan::NodeOrToken::Node(n) => ends_with_newline(n),
2211                }
2212            } else {
2213                false
2214            };
2215
2216            if !has_newline_before {
2217                add_newline_token(&mut new_elements);
2218            }
2219        }
2220
2221        // Use the already-created MAPPING_ENTRY node (already ends with newline)
2222        new_elements.push(new_entry.into());
2223
2224        // Insert at the calculated position
2225        self.0.splice_children(insert_pos..insert_pos, new_elements);
2226    }
2227
2228    /// Remove a key-value pair, returning the removed entry.
2229    ///
2230    /// Returns `Some(entry)` if the key existed and was removed, or `None` if
2231    /// the key was not found. The returned [`MappingEntry`] is detached from
2232    /// the tree; callers can inspect its key and value or re-insert it
2233    /// elsewhere.
2234    ///
2235    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2236    pub fn remove(&self, key: impl crate::AsYaml) -> Option<MappingEntry> {
2237        let children: Vec<_> = self.0.children_with_tokens().collect();
2238
2239        // Find the entry to remove
2240        for (i, child) in children.iter().enumerate() {
2241            if let Some(node) = child.as_node() {
2242                if node.kind() == SyntaxKind::MAPPING_ENTRY {
2243                    if let Some(entry) = MappingEntry::cast(node.clone()) {
2244                        if entry.key_matches(&key) {
2245                            // Check if this is the last MAPPING_ENTRY
2246                            let is_last = !children.iter().skip(i + 1).any(|c| {
2247                                c.as_node()
2248                                    .is_some_and(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
2249                            });
2250
2251                            // Remove the entry (detaches its SyntaxNode from the tree)
2252                            self.0.splice_children(i..(i + 1), vec![]);
2253
2254                            if is_last && i > 0 {
2255                                // Removed the last entry - remove trailing newline from new last entry
2256                                // Find the previous MAPPING_ENTRY
2257                                if let Some(prev_entry_node) =
2258                                    children[..i].iter().rev().find_map(|c| {
2259                                        c.as_node()
2260                                            .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
2261                                    })
2262                                {
2263                                    // Check if it ends with NEWLINE and remove it
2264                                    if let Some(last_token) = prev_entry_node.last_token() {
2265                                        if last_token.kind() == SyntaxKind::NEWLINE {
2266                                            let entry_children_count =
2267                                                prev_entry_node.children_with_tokens().count();
2268                                            prev_entry_node.splice_children(
2269                                                (entry_children_count - 1)..entry_children_count,
2270                                                vec![],
2271                                            );
2272                                        }
2273                                    }
2274                                }
2275                            }
2276                            return Some(entry);
2277                        }
2278                    }
2279                }
2280            }
2281        }
2282        None
2283    }
2284
2285    /// Remove the nth occurrence of a key, returning the removed entry.
2286    ///
2287    /// Returns `Some(entry)` if the nth occurrence exists and was removed,
2288    /// or `None` if there are fewer than `n+1` occurrences of the key.
2289    /// The index `n` is 0-based (n=0 removes the first occurrence, n=1 removes
2290    /// the second, etc.).
2291    ///
2292    /// This is useful for handling duplicate keys in YAML. While duplicate keys
2293    /// are semantically ambiguous, they are allowed by the YAML spec, and this
2294    /// method provides fine-grained control over which occurrence to remove.
2295    ///
2296    /// # Example
2297    ///
2298    /// ```rust
2299    /// # use std::str::FromStr;
2300    /// # use yaml_edit::Document;
2301    /// let yaml = r#"
2302    /// Reference: First
2303    /// Reference: Second
2304    /// Reference: Third
2305    /// "#;
2306    ///
2307    /// let doc = Document::from_str(yaml).unwrap();
2308    /// let mapping = doc.as_mapping().unwrap();
2309    ///
2310    /// // Remove the second occurrence (index 1)
2311    /// let removed = mapping.remove_nth_occurrence("Reference", 1);
2312    /// assert!(removed.is_some());
2313    ///
2314    /// // Now only two Reference entries remain
2315    /// let count = mapping.find_all_entries_by_key("Reference").count();
2316    /// assert_eq!(count, 2);
2317    /// ```
2318    ///
2319    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2320    pub fn remove_nth_occurrence(&self, key: impl crate::AsYaml, n: usize) -> Option<MappingEntry> {
2321        let children: Vec<_> = self.0.children_with_tokens().collect();
2322
2323        // Find the nth entry matching the key
2324        let mut occurrence_count = 0;
2325        for (i, child) in children.iter().enumerate() {
2326            let node = child.as_node()?;
2327            if node.kind() != SyntaxKind::MAPPING_ENTRY {
2328                continue;
2329            }
2330
2331            let entry = MappingEntry::cast(node.clone())?;
2332            if !entry.key_matches(&key) {
2333                continue;
2334            }
2335
2336            if occurrence_count != n {
2337                occurrence_count += 1;
2338                continue;
2339            }
2340
2341            // Found the nth occurrence - remove it
2342            let is_last = !children.iter().skip(i + 1).any(|c| {
2343                c.as_node()
2344                    .is_some_and(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
2345            });
2346
2347            self.0.splice_children(i..(i + 1), vec![]);
2348
2349            if is_last && i > 0 {
2350                // Removed the last entry - remove trailing newline from new last entry
2351                let prev_entry_node = children[..i].iter().rev().find_map(|c| {
2352                    c.as_node()
2353                        .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
2354                })?;
2355
2356                if let Some(last_token) = prev_entry_node.last_token() {
2357                    if last_token.kind() == SyntaxKind::NEWLINE {
2358                        let entry_children_count = prev_entry_node.children_with_tokens().count();
2359                        prev_entry_node.splice_children(
2360                            (entry_children_count - 1)..entry_children_count,
2361                            vec![],
2362                        );
2363                    }
2364                }
2365            }
2366            return Some(entry);
2367        }
2368        None
2369    }
2370
2371    /// Remove all key-value pairs from this mapping.
2372    ///
2373    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2374    pub fn clear(&self) {
2375        let keys: Vec<crate::as_yaml::YamlNode> = self.keys().collect();
2376        for key in keys {
2377            self.remove(key);
2378        }
2379    }
2380
2381    /// Rename a key while preserving its value and formatting.
2382    ///
2383    /// The new key is built using the same `AsYaml` infrastructure as other
2384    /// write methods, so quoting and escaping are handled automatically.
2385    /// Returns `true` if the key was found and renamed, `false` if `old_key`
2386    /// does not exist.
2387    ///
2388    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2389    pub fn rename_key(&self, old_key: impl crate::AsYaml, new_key: impl crate::AsYaml) -> bool {
2390        let children: Vec<_> = self.0.children_with_tokens().collect();
2391
2392        for (i, child) in children.iter().enumerate() {
2393            if let Some(node) = child.as_node() {
2394                if node.kind() == SyntaxKind::MAPPING_ENTRY {
2395                    if let Some(key_node) = node.children().find(|n| n.kind() == SyntaxKind::KEY) {
2396                        if key_content_matches(&key_node, &old_key) {
2397                            let entry_children: Vec<_> = node.children_with_tokens().collect();
2398
2399                            let mut builder = GreenNodeBuilder::new();
2400                            builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2401
2402                            for entry_child in entry_children {
2403                                match entry_child {
2404                                    rowan::NodeOrToken::Node(n) if n.kind() == SyntaxKind::KEY => {
2405                                        // Replace the KEY node using AsYaml::build_content
2406                                        builder.start_node(SyntaxKind::KEY.into());
2407                                        new_key.build_content(&mut builder, 0, false);
2408                                        builder.finish_node(); // KEY
2409                                    }
2410                                    rowan::NodeOrToken::Node(n) => {
2411                                        crate::yaml::copy_node_to_builder(&mut builder, &n);
2412                                    }
2413                                    rowan::NodeOrToken::Token(t) => {
2414                                        builder.token(t.kind().into(), t.text());
2415                                    }
2416                                }
2417                            }
2418
2419                            builder.finish_node();
2420                            let new_entry = SyntaxNode::new_root_mut(builder.finish());
2421                            self.0.splice_children(i..i + 1, vec![new_entry.into()]);
2422                            return true;
2423                        }
2424                    }
2425                }
2426            }
2427        }
2428        false
2429    }
2430
2431    /// Helper to create a MAPPING_ENTRY node from key and value strings
2432    fn create_mapping_entry(
2433        &self,
2434        key: impl crate::AsYaml,
2435        value: impl crate::AsYaml,
2436    ) -> (SyntaxNode, bool) {
2437        let mut builder = GreenNodeBuilder::new();
2438        builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2439
2440        // Add KEY node
2441        builder.start_node(SyntaxKind::KEY.into());
2442        key.build_content(&mut builder, 0, false);
2443        builder.finish_node(); // KEY
2444
2445        // Add colon
2446        builder.token(SyntaxKind::COLON.into(), ":");
2447
2448        // Check if value is inline - if not, add newline + indent before VALUE
2449        if !value.is_inline() {
2450            builder.token(SyntaxKind::NEWLINE.into(), "\n");
2451            builder.token(SyntaxKind::INDENT.into(), "  "); // 2-space indent
2452        } else {
2453            builder.token(SyntaxKind::WHITESPACE.into(), " ");
2454        }
2455
2456        // Add VALUE node
2457        builder.start_node(SyntaxKind::VALUE.into());
2458        let value_ends_with_newline = value.build_content(&mut builder, 2, false);
2459        builder.finish_node(); // VALUE
2460
2461        // Every block-style MAPPING_ENTRY ends with NEWLINE
2462        // Only add if the value content didn't already end with one
2463        let added_newline = if !value_ends_with_newline {
2464            builder.token(SyntaxKind::NEWLINE.into(), "\n");
2465            true
2466        } else {
2467            false
2468        };
2469
2470        builder.finish_node(); // MAPPING_ENTRY
2471        (
2472            SyntaxNode::new_root_mut(builder.finish()),
2473            added_newline || value_ends_with_newline,
2474        )
2475    }
2476
2477    /// Insert a key-value pair immediately after an existing key.
2478    ///
2479    /// If `key` already exists in the mapping, its value is updated in-place and
2480    /// it remains at its current position (it is **not** moved to after `after_key`).
2481    /// Returns `true` in both the update and the insert cases.
2482    /// Returns `false` only if `after_key` is not found.
2483    ///
2484    /// Use [`move_after`](Self::move_after) if you want
2485    /// an existing entry to be moved to the new position.
2486    ///
2487    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2488    pub fn insert_after(
2489        &self,
2490        after_key: impl crate::AsYaml,
2491        key: impl crate::AsYaml,
2492        value: impl crate::AsYaml,
2493    ) -> bool {
2494        // Check if the new key already exists - if so, just update it
2495        if self.find_entry_by_key(&key).is_some() {
2496            self.set_as_yaml(&key, &value);
2497            return true;
2498        }
2499
2500        // Key doesn't exist yet — delegate to move_after, which already contains
2501        // the correct insertion logic (including newline handling for entries
2502        // that lack a trailing newline). The two methods differ only in what
2503        // they do when the key *already* exists.
2504        self.move_after(after_key, key, value)
2505    }
2506
2507    /// Insert a key-value pair immediately before an existing key.
2508    ///
2509    /// If `key` already exists in the mapping, its value is updated in-place and
2510    /// it remains at its current position (it is **not** moved to before `before_key`).
2511    /// Returns `true` in both the update and the insert cases.
2512    /// Returns `false` only if `before_key` is not found.
2513    ///
2514    /// Use [`move_before`](Self::move_before) if you want
2515    /// an existing entry to be moved to the new position.
2516    ///
2517    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2518    pub fn insert_before(
2519        &self,
2520        before_key: impl crate::AsYaml,
2521        key: impl crate::AsYaml,
2522        value: impl crate::AsYaml,
2523    ) -> bool {
2524        // Key exists → update in-place (don't move).
2525        if self.find_entry_by_key(&key).is_some() {
2526            self.set_as_yaml(&key, &value);
2527            // Only return true if before_key also exists (contract: false when
2528            // reference key is not found).
2529            return self.find_entry_by_key(&before_key).is_some();
2530        }
2531
2532        // Key doesn't exist yet — delegate to move_before, which already contains
2533        // the correct insertion logic. The two methods differ only in what they do
2534        // when the key *already* exists.
2535        self.move_before(before_key, key, value)
2536    }
2537
2538    /// Insert a key-value pair at a specific index (0-based).
2539    ///
2540    /// If `key` already exists in the mapping, its value is updated in-place and
2541    /// it remains at its current position (the `index` argument is ignored).
2542    /// If `index` is out of bounds, the entry is appended at the end.
2543    /// This method always succeeds; it never returns an error.
2544    ///
2545    /// Mutates in place despite `&self` (see crate docs on interior mutability).
2546    pub fn insert_at_index(
2547        &self,
2548        index: usize,
2549        key: impl crate::AsYaml,
2550        value: impl crate::AsYaml,
2551    ) {
2552        // Check if the key already exists - if so, just update it
2553        if self.find_entry_by_key(&key).is_some() {
2554            self.set_as_yaml(&key, &value);
2555            return;
2556        }
2557
2558        // Create the new mapping entry
2559        let flow_context = self.is_flow_style();
2560        let use_explicit_keys = self.uses_explicit_keys();
2561        let key_indent = self.detect_indentation_level();
2562        let new_entry =
2563            MappingEntry::new_at_indent(&key, &value, flow_context, use_explicit_keys, key_indent);
2564
2565        // Count existing entries to determine actual insertion position
2566        let entry_count = self.entries().count();
2567        let actual_index = index.min(entry_count);
2568
2569        // Find the position in children_with_tokens corresponding to the nth entry
2570        let mut entry_positions = Vec::new();
2571        for (i, child) in self.0.children_with_tokens().enumerate() {
2572            if child
2573                .as_node()
2574                .is_some_and(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
2575            {
2576                entry_positions.push(i);
2577            }
2578        }
2579
2580        // Determine where to insert
2581        let insert_pos = if actual_index < entry_positions.len() {
2582            entry_positions[actual_index]
2583        } else {
2584            self.0.children_with_tokens().count()
2585        };
2586
2587        // Build the elements to insert
2588        let mut new_elements = Vec::new();
2589
2590        // Add spacing before the new entry if not at position 0
2591        if insert_pos > 0 {
2592            // Check if previous element ends with newline
2593            // (normally entries own their newlines, but the last entry might not have one
2594            // if the file didn't end with a newline)
2595            let needs_newline =
2596                if let Some(prev) = self.0.children_with_tokens().nth(insert_pos - 1) {
2597                    match prev {
2598                        rowan::NodeOrToken::Token(t) => t.kind() != SyntaxKind::NEWLINE,
2599                        rowan::NodeOrToken::Node(n) => !ends_with_newline(&n),
2600                    }
2601                } else {
2602                    false
2603                };
2604
2605            if needs_newline {
2606                add_newline_token(&mut new_elements);
2607            }
2608
2609            // Add indentation
2610            let indent_level = self.detect_indentation_level();
2611            if indent_level > 0 {
2612                let mut indent_builder = GreenNodeBuilder::new();
2613                indent_builder.start_node(SyntaxKind::ROOT.into());
2614                indent_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent_level));
2615                indent_builder.finish_node();
2616                let indent_node = SyntaxNode::new_root_mut(indent_builder.finish());
2617                if let Some(token) = indent_node.first_token() {
2618                    new_elements.push(token.into());
2619                }
2620            }
2621        }
2622
2623        // Add the new entry
2624        new_elements.push(new_entry.0.into());
2625
2626        // Insert at the calculated position
2627        self.0.splice_children(insert_pos..insert_pos, new_elements);
2628    }
2629
2630    /// Get the byte offset range of this mapping in the source text.
2631    ///
2632    /// Returns the start and end byte offsets as a `TextPosition`.
2633    pub fn byte_range(&self) -> crate::TextPosition {
2634        self.0.text_range().into()
2635    }
2636
2637    /// Get the line and column where this mapping starts.
2638    ///
2639    /// Requires the original source text to calculate line/column from byte offsets.
2640    /// Line and column numbers are 1-indexed.
2641    ///
2642    /// # Arguments
2643    ///
2644    /// * `source_text` - The original YAML source text
2645    pub fn start_position(&self, source_text: &str) -> crate::LineColumn {
2646        let range = self.byte_range();
2647        crate::byte_offset_to_line_column(source_text, range.start as usize)
2648    }
2649
2650    /// Get the line and column where this mapping ends.
2651    ///
2652    /// Requires the original source text to calculate line/column from byte offsets.
2653    /// Line and column numbers are 1-indexed.
2654    ///
2655    /// # Arguments
2656    ///
2657    /// * `source_text` - The original YAML source text
2658    pub fn end_position(&self, source_text: &str) -> crate::LineColumn {
2659        let range = self.byte_range();
2660        crate::byte_offset_to_line_column(source_text, range.end as usize)
2661    }
2662}
2663
2664impl Default for Mapping {
2665    fn default() -> Self {
2666        Self::new()
2667    }
2668}
2669
2670// Iterator trait implementations for Mapping
2671
2672impl<'a> IntoIterator for &'a Mapping {
2673    type Item = (crate::as_yaml::YamlNode, crate::as_yaml::YamlNode);
2674    type IntoIter =
2675        Box<dyn Iterator<Item = (crate::as_yaml::YamlNode, crate::as_yaml::YamlNode)> + 'a>;
2676
2677    fn into_iter(self) -> Self::IntoIter {
2678        Box::new(self.iter())
2679    }
2680}
2681
2682impl AsYaml for Mapping {
2683    fn as_node(&self) -> Option<&SyntaxNode> {
2684        Some(&self.0)
2685    }
2686
2687    fn kind(&self) -> YamlKind {
2688        YamlKind::Mapping
2689    }
2690
2691    fn build_content(
2692        &self,
2693        builder: &mut rowan::GreenNodeBuilder,
2694        indent: usize,
2695        _flow_context: bool,
2696    ) -> bool {
2697        builder.start_node(SyntaxKind::MAPPING.into());
2698        crate::as_yaml::copy_node_content_with_indent(builder, &self.0, indent);
2699        builder.finish_node();
2700        self.0
2701            .last_token()
2702            .map(|t| t.kind() == SyntaxKind::NEWLINE)
2703            .unwrap_or(false)
2704    }
2705
2706    fn is_inline(&self) -> bool {
2707        ValueNode::is_inline(self)
2708    }
2709}
2710#[cfg(test)]
2711mod tests {
2712    use crate::scalar::ScalarValue;
2713    use crate::yaml::YamlFile;
2714    use std::str::FromStr;
2715
2716    #[test]
2717    fn test_mapping_set_new_key() {
2718        let yaml = "existing: value";
2719        let parsed = YamlFile::from_str(yaml).unwrap();
2720
2721        // Get the document and set on it
2722        let doc = parsed.document().expect("Should have a document");
2723        doc.set("new_key", "new_value");
2724
2725        let output = doc.to_string();
2726
2727        let expected = r#"existing: value
2728new_key: new_value"#;
2729        assert_eq!(output.trim(), expected);
2730    }
2731    #[test]
2732    fn test_mapping_rename_key() {
2733        let yaml = "old_name: value";
2734        let parsed = YamlFile::from_str(yaml).unwrap();
2735
2736        let doc = parsed.document().expect("expected a document");
2737        let mapping = doc.as_mapping().expect("expected a mapping");
2738        let renamed = mapping.rename_key("old_name", "new_name");
2739        assert!(renamed);
2740        assert!(doc.contains_key("new_name"));
2741        assert!(!doc.contains_key("old_name"));
2742    }
2743
2744    #[test]
2745    fn test_mapping_remove_key() {
2746        let yaml = "key1: value1\nkey2: value2";
2747        let parsed = YamlFile::from_str(yaml).unwrap();
2748
2749        let doc = parsed.document().expect("expected a document");
2750        let mapping = doc.as_mapping().expect("expected a mapping");
2751        let removed = mapping.remove("key1");
2752        assert!(removed.is_some());
2753        assert!(!doc.contains_key("key1"));
2754        assert!(doc.contains_key("key2"));
2755    }
2756    #[test]
2757    fn test_mapping_simple_set() {
2758        let yaml = "key1: value1";
2759        let parsed = YamlFile::from_str(yaml).unwrap();
2760
2761        // Get document and add a new key
2762        let doc = parsed.document().expect("Should have a document");
2763        doc.set("key2", "value2");
2764
2765        let output = doc.to_string();
2766
2767        let expected = r#"key1: value1
2768key2: value2"#;
2769        assert_eq!(output.trim(), expected);
2770    }
2771    #[test]
2772    fn test_mapping_set_preserves_position() {
2773        // Test that set() preserves the position of existing fields when updating
2774        let yaml = r#"Name: original_name
2775Contact: original_contact
2776Repository: https://github.com/example/repo.git
2777"#;
2778        let parsed = YamlFile::from_str(yaml).unwrap();
2779        let doc = parsed.document().expect("Should have a document");
2780
2781        // Update Contact - it should stay in position 2, not move to the end
2782        doc.set("Contact", "updated_contact");
2783
2784        let output = doc.to_string();
2785        let expected = r#"Name: original_name
2786Contact: updated_contact
2787Repository: https://github.com/example/repo.git
2788"#;
2789        assert_eq!(output, expected);
2790    }
2791    #[test]
2792    fn test_mapping_set_preserves_multiple_fields() {
2793        // Test updating multiple existing fields preserves all positions
2794        let yaml = r#"Name: tsne
2795Contact: Justin Donaldson <jdonaldson@gmail.com>
2796Archive: CRAN
2797Repository: https://github.com/jdonaldson/rtsne.git
2798"#;
2799        let parsed = YamlFile::from_str(yaml).unwrap();
2800        let doc = parsed.document().expect("Should have a document");
2801
2802        if let Some(mapping) = doc.as_mapping() {
2803            // Update Contact - should stay in position 2
2804            mapping.set("Contact", "New Contact <new@example.com>");
2805            // Update Archive - should stay in position 3
2806            mapping.set("Archive", "PyPI");
2807        }
2808
2809        let output = doc.to_string();
2810        let expected = r#"Name: tsne
2811Contact: New Contact <new@example.com>
2812Archive: PyPI
2813Repository: https://github.com/jdonaldson/rtsne.git
2814"#;
2815        assert_eq!(output, expected);
2816    }
2817    #[test]
2818    fn test_mapping_insert_after() {
2819        let yaml = r#"first: 1
2820second: 2
2821fourth: 4"#;
2822
2823        let parsed = YamlFile::from_str(yaml).unwrap();
2824
2825        let doc = parsed.document().expect("Should have a document");
2826
2827        // Insert after "second"
2828        let success = doc.insert_after("second", "third", 3);
2829        assert!(
2830            success,
2831            "insert_after should succeed when reference key exists"
2832        );
2833
2834        let output = doc.to_string();
2835
2836        // Check exact output - should preserve original structure and insert correctly
2837        let expected = r#"first: 1
2838second: 2
2839third: 3
2840fourth: 4"#;
2841        assert_eq!(output.trim(), expected);
2842
2843        // Test inserting after non-existent key
2844        let failed = doc.insert_after("nonexistent", "new_key", "new_value");
2845        assert!(
2846            !failed,
2847            "insert_after should fail when reference key doesn't exist"
2848        );
2849
2850        // Test updating existing key through insert_after
2851        let updated = doc.insert_after("first", "second", "2_updated");
2852        assert!(updated, "insert_after should update existing key");
2853        let updated_output = doc.to_string();
2854        let expected_updated = r#"first: 1
2855second: 2_updated
2856third: 3
2857fourth: 4"#;
2858        assert_eq!(updated_output.trim(), expected_updated);
2859    }
2860    #[test]
2861    fn test_mapping_insert_before() {
2862        let yaml = r#"first: 1
2863third: 3
2864fourth: 4"#;
2865
2866        let parsed = YamlFile::from_str(yaml).unwrap();
2867        let doc = parsed.document().expect("Should have a document");
2868
2869        // Insert before "third"
2870        let success = doc.insert_before("third", "second", 2);
2871        assert!(
2872            success,
2873            "insert_before should succeed when reference key exists"
2874        );
2875
2876        let output = doc.to_string();
2877
2878        // Check exact output - should preserve original structure and insert correctly
2879        let expected = r#"first: 1
2880second: 2
2881third: 3
2882fourth: 4"#;
2883        assert_eq!(output.trim(), expected);
2884
2885        // Test inserting before non-existent key
2886        let failed = doc.insert_before("nonexistent", "new_key", "new_value");
2887        assert!(
2888            !failed,
2889            "insert_before should fail when reference key doesn't exist"
2890        );
2891
2892        // Test updating existing key through insert_before
2893        let updated = doc.insert_before("fourth", "third", "3_updated");
2894        assert!(updated, "insert_before should update existing key");
2895        let output = doc.to_string();
2896        let expected_updated = r#"first: 1
2897second: 2
2898third: 3_updated
2899fourth: 4"#;
2900        assert_eq!(output.trim(), expected_updated);
2901    }
2902    #[test]
2903    fn test_mapping_insert_at_index() {
2904        let yaml = r#"first: 1
2905third: 3"#;
2906
2907        let parsed = YamlFile::from_str(yaml).unwrap();
2908        let doc = parsed.document().expect("Should have a document");
2909
2910        // Insert at index 1 (between first and third)
2911        doc.insert_at_index(1, "second", 2);
2912
2913        let output = doc.to_string();
2914
2915        // Check exact output - should preserve original structure and insert correctly
2916        let expected = r#"first: 1
2917second: 2
2918third: 3"#;
2919        assert_eq!(output.trim(), expected);
2920
2921        // Insert at index 0 (beginning)
2922        doc.insert_at_index(0, "zero", 0);
2923        let output2 = doc.to_string();
2924        let expected2 = r#"zero: 0
2925first: 1
2926second: 2
2927third: 3"#;
2928        assert_eq!(output2.trim(), expected2);
2929
2930        // Insert at out-of-bounds index (should append at end)
2931        doc.insert_at_index(100, "last", "999");
2932        let output3 = doc.to_string();
2933        let expected3 = r#"zero: 0
2934first: 1
2935second: 2
2936third: 3
2937last: '999'"#;
2938        assert_eq!(output3.trim(), expected3);
2939
2940        // Test updating existing key through insert_at_index
2941        doc.insert_at_index(2, "first", "1_updated");
2942        let final_output = doc.to_string();
2943        let expected_final = r#"zero: 0
2944first: 1_updated
2945second: 2
2946third: 3
2947last: '999'"#;
2948        assert_eq!(final_output.trim(), expected_final);
2949    }
2950    #[test]
2951    fn test_mapping_insert_special_characters() {
2952        let yaml = "key1: value1";
2953
2954        let parsed = YamlFile::from_str(yaml).unwrap();
2955        let doc = parsed.document().expect("Should have a document");
2956
2957        // Test with special characters that need escaping
2958        doc.insert_after("key1", "special:key", "value:with:colons");
2959        doc.insert_before("key1", "key with spaces", "value with spaces");
2960        doc.insert_at_index(1, "key@symbol", "value#hash");
2961
2962        // Verify all keys are present
2963        assert!(doc.contains_key("special:key"));
2964        assert!(doc.contains_key("key with spaces"));
2965        assert!(doc.contains_key("key@symbol"));
2966
2967        // Parse the output to verify it's valid YAML
2968        let output = doc.to_string();
2969        let reparsed = YamlFile::from_str(&output);
2970        assert!(reparsed.is_ok(), "Output should be valid YAML");
2971    }
2972    #[test]
2973    fn test_mapping_insert_empty_values() {
2974        let yaml = "key1: value1";
2975
2976        let parsed = YamlFile::from_str(yaml).unwrap();
2977        let doc = parsed.document().expect("Should have a document");
2978
2979        // Test with empty values
2980        doc.insert_after("key1", "empty", "");
2981        doc.insert_before("key1", "null_key", ScalarValue::null());
2982
2983        assert!(doc.contains_key("empty"));
2984        assert!(doc.contains_key("null_key"));
2985
2986        // Verify the output is valid YAML
2987        let output = parsed.to_string();
2988        let reparsed = YamlFile::from_str(&output);
2989        assert!(
2990            reparsed.is_ok(),
2991            "Output with empty values should be valid YAML"
2992        );
2993    }
2994
2995    // Iterator tests
2996
2997    #[test]
2998    fn test_mapping_into_iterator() {
2999        use crate::Document;
3000        let text = "name: Alice\nage: 30\ncity: Boston";
3001        let doc = Document::from_str(text).unwrap();
3002        let mapping = doc.as_mapping().unwrap();
3003
3004        // Test that we can use for loops directly
3005        let mut count = 0;
3006        for (key, value) in &mapping {
3007            count += 1;
3008
3009            // Check that we get scalar nodes
3010            assert!(key.is_scalar());
3011            assert!(value.is_scalar());
3012        }
3013
3014        assert_eq!(count, 3);
3015    }
3016
3017    #[test]
3018    fn test_mapping_into_iterator_collect() {
3019        use crate::Document;
3020        let text = "a: 1\nb: 2\nc: 3";
3021        let doc = Document::from_str(text).unwrap();
3022        let mapping = doc.as_mapping().unwrap();
3023
3024        // Collect into a Vec
3025        let pairs: Vec<_> = (&mapping).into_iter().collect();
3026        assert_eq!(pairs.len(), 3);
3027
3028        // Check we can get scalars
3029        for (key, value) in pairs {
3030            assert!(key.as_scalar().is_some());
3031            assert!(value.as_scalar().is_some());
3032        }
3033    }
3034
3035    #[test]
3036    fn test_mapping_iterator_filter() {
3037        use crate::Document;
3038        let text = "a: 1\nb: 2\nc: 3\nd: 4";
3039        let doc = Document::from_str(text).unwrap();
3040        let mapping = doc.as_mapping().unwrap();
3041
3042        // Filter for even values
3043        let even_count = (&mapping)
3044            .into_iter()
3045            .filter(|(_, value)| {
3046                value
3047                    .as_scalar()
3048                    .and_then(|s| s.to_string().parse::<i32>().ok())
3049                    .map(|n| n % 2 == 0)
3050                    .unwrap_or(false)
3051            })
3052            .count();
3053
3054        assert_eq!(even_count, 2); // b: 2 and d: 4
3055    }
3056
3057    #[test]
3058    fn test_empty_mapping_iterator() {
3059        let empty = crate::Mapping::new();
3060
3061        let count = (&empty).into_iter().count();
3062        assert_eq!(count, 0);
3063    }
3064
3065    #[test]
3066    fn test_nested_mapping_iteration() {
3067        use crate::Document;
3068        let text = "server:\n  host: localhost\n  port: 8080";
3069        let doc = Document::from_str(text).unwrap();
3070        let mapping = doc.as_mapping().unwrap();
3071
3072        // Iterate outer mapping
3073        for (key, _value) in &mapping {
3074            if let Some(key_scalar) = key.as_scalar() {
3075                if key_scalar.to_string() == "server" {
3076                    // Get nested mapping
3077                    if let Some(nested_mapping) = mapping.get_mapping("server") {
3078                        let nested_count = (&nested_mapping).into_iter().count();
3079                        assert_eq!(nested_count, 2); // host and port
3080                    }
3081                }
3082            }
3083        }
3084    }
3085
3086    // Tests from mapping_operations_test.rs
3087
3088    // ===== Basic accessor tests =====
3089
3090    #[test]
3091    fn test_mapping_keys() {
3092        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3093        let doc = yaml.document().unwrap();
3094        let mapping = doc.as_mapping().unwrap();
3095
3096        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3097        assert_eq!(keys, vec!["name", "age", "city"]);
3098    }
3099
3100    #[test]
3101    fn test_mapping_is_empty() {
3102        let yaml = YamlFile::from_str("{}").unwrap();
3103        let doc = yaml.document().unwrap();
3104        let mapping = doc.as_mapping().unwrap();
3105        assert!(mapping.is_empty());
3106
3107        let yaml2 = YamlFile::from_str("key: value").unwrap();
3108        let doc2 = yaml2.document().unwrap();
3109        let mapping2 = doc2.as_mapping().unwrap();
3110        assert!(!mapping2.is_empty());
3111    }
3112
3113    #[test]
3114    fn test_mapping_contains_key() {
3115        let yaml = YamlFile::from_str("name: Alice\nage: 30").unwrap();
3116        let doc = yaml.document().unwrap();
3117        let mapping = doc.as_mapping().unwrap();
3118
3119        assert!(mapping.contains_key("name"));
3120        assert!(mapping.contains_key("age"));
3121        assert!(!mapping.contains_key("city"));
3122    }
3123
3124    #[test]
3125    fn test_mapping_get() {
3126        let yaml = YamlFile::from_str("name: Alice\nage: 30").unwrap();
3127        let doc = yaml.document().unwrap();
3128        let mapping = doc.as_mapping().unwrap();
3129
3130        assert_eq!(
3131            mapping
3132                .get("name")
3133                .and_then(|v| v.as_scalar().map(|s| s.as_string())),
3134            Some("Alice".to_string())
3135        );
3136        assert_eq!(mapping.get("age").and_then(|v| v.to_i64()), Some(30));
3137        assert!(mapping.get("city").is_none());
3138    }
3139
3140    #[test]
3141    fn test_mapping_single_entry() {
3142        let yaml = YamlFile::from_str("key: value").unwrap();
3143        let doc = yaml.document().unwrap();
3144        let mapping = doc.as_mapping().unwrap();
3145
3146        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3147        assert_eq!(keys, vec!["key"]);
3148        assert!(!mapping.is_empty());
3149        assert!(mapping.contains_key("key"));
3150    }
3151
3152    // ===== Mutation tests =====
3153
3154    #[test]
3155    fn test_mapping_ops_set_new_key() {
3156        let yaml = YamlFile::from_str("name: Alice").unwrap();
3157        let doc = yaml.document().unwrap();
3158        let mapping = doc.as_mapping().unwrap();
3159
3160        mapping.set("age", 30);
3161
3162        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3163        assert_eq!(keys, vec!["name", "age"]);
3164        assert_eq!(mapping.get("age").and_then(|v| v.to_i64()), Some(30));
3165    }
3166
3167    #[test]
3168    fn test_mapping_set_existing_key() {
3169        let yaml = YamlFile::from_str("name: Alice\nage: 30").unwrap();
3170        let doc = yaml.document().unwrap();
3171        let mapping = doc.as_mapping().unwrap();
3172
3173        mapping.set("age", 31);
3174
3175        assert_eq!(mapping.get("age").and_then(|v| v.to_i64()), Some(31));
3176        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3177        assert_eq!(keys, vec!["name", "age"]);
3178    }
3179
3180    #[test]
3181    fn test_mapping_remove_existing_key() {
3182        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3183        let doc = yaml.document().unwrap();
3184        let mapping = doc.as_mapping().unwrap();
3185
3186        let removed = mapping.remove("age");
3187        assert!(removed.is_some());
3188
3189        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3190        assert_eq!(keys, vec!["name", "city"]);
3191        assert!(!mapping.contains_key("age"));
3192    }
3193
3194    #[test]
3195    fn test_mapping_remove_nonexistent_key() {
3196        let yaml = YamlFile::from_str("name: Alice").unwrap();
3197        let doc = yaml.document().unwrap();
3198        let mapping = doc.as_mapping().unwrap();
3199
3200        let removed = mapping.remove("age");
3201        assert!(removed.is_none());
3202
3203        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3204        assert_eq!(keys, vec!["name"]);
3205    }
3206
3207    #[test]
3208    fn test_mapping_remove_all_keys() {
3209        let yaml = YamlFile::from_str("a: 1\nb: 2").unwrap();
3210        let doc = yaml.document().unwrap();
3211        let mapping = doc.as_mapping().unwrap();
3212
3213        assert!(mapping.remove("a").is_some());
3214        assert!(mapping.remove("b").is_some());
3215        assert!(mapping.is_empty());
3216    }
3217
3218    #[test]
3219    fn test_rename_key_basic() {
3220        let original = r#"name: my-app
3221version: 1.0
3222author: Alice"#;
3223
3224        let yaml = YamlFile::from_str(original).unwrap();
3225
3226        if let Some(doc) = yaml.document() {
3227            if let Some(mapping) = doc.as_mapping() {
3228                let success = mapping.rename_key("version", "app_version");
3229                assert!(success);
3230            }
3231        }
3232
3233        let expected = r#"name: my-app
3234app_version: 1.0
3235author: Alice"#;
3236        assert_eq!(yaml.to_string(), expected);
3237    }
3238
3239    #[test]
3240    fn test_rename_key_preserves_value() {
3241        let original = r#"count: 42
3242enabled: true"#;
3243
3244        let yaml = YamlFile::from_str(original).unwrap();
3245
3246        if let Some(doc) = yaml.document() {
3247            if let Some(mapping) = doc.as_mapping() {
3248                mapping.rename_key("count", "total");
3249            }
3250        }
3251
3252        let expected = r#"total: 42
3253enabled: true"#;
3254        assert_eq!(yaml.to_string(), expected);
3255    }
3256
3257    #[test]
3258    fn test_remove_field() {
3259        let original = r#"name: my-app
3260version: 1.0
3261author: Alice"#;
3262
3263        let yaml = YamlFile::from_str(original).unwrap();
3264
3265        if let Some(doc) = yaml.document() {
3266            if let Some(mapping) = doc.as_mapping() {
3267                let removed = mapping.remove("author");
3268                assert!(removed.is_some());
3269            }
3270        }
3271
3272        let expected = r#"name: my-app
3273version: 1.0"#;
3274        assert_eq!(yaml.to_string(), expected);
3275    }
3276
3277    #[test]
3278    fn test_complex_operations_combined() {
3279        let original = r#"name: my-app
3280version: 1.0
3281author: Alice
3282year: 2023
3283
3284features:
3285  - logging
3286  - auth"#;
3287
3288        let yaml = YamlFile::from_str(original).unwrap();
3289
3290        if let Some(doc) = yaml.document() {
3291            if let Some(mapping) = doc.as_mapping() {
3292                // Add new fields
3293                mapping.set("license", "MIT");
3294                mapping.set("published", true);
3295                mapping.set("downloads", 1000);
3296
3297                // Remove a field
3298                mapping.remove("author");
3299
3300                // Rename a field
3301                mapping.rename_key("version", "app_version");
3302
3303                // Update existing field
3304                mapping.set("year", 2024);
3305            }
3306        }
3307
3308        let expected = r#"name: my-app
3309app_version: 1.0
3310year: 2024
3311
3312features:
3313  - logging
3314  - auth
3315license: MIT
3316published: true
3317downloads: 1000
3318"#;
3319        assert_eq!(yaml.to_string(), expected);
3320    }
3321
3322    // ===== Nested structure tests =====
3323
3324    #[test]
3325    fn test_mapping_get_nested_mapping() {
3326        let yaml = YamlFile::from_str("user:\n  name: Alice\n  age: 30").unwrap();
3327        let doc = yaml.document().unwrap();
3328        let mapping = doc.as_mapping().unwrap();
3329
3330        let nested = mapping.get_mapping("user");
3331        assert!(nested.is_some());
3332
3333        let nested = nested.unwrap();
3334        assert_eq!(
3335            nested
3336                .get("name")
3337                .and_then(|v| v.as_scalar().map(|s| s.as_string())),
3338            Some("Alice".to_string())
3339        );
3340        assert_eq!(nested.get("age").and_then(|v| v.to_i64()), Some(30));
3341    }
3342
3343    #[test]
3344    fn test_mapping_get_nested_sequence() {
3345        let yaml = YamlFile::from_str("items:\n  - a\n  - b\n  - c").unwrap();
3346        let doc = yaml.document().unwrap();
3347        let mapping = doc.as_mapping().unwrap();
3348
3349        let seq = mapping.get_sequence("items");
3350        assert!(seq.is_some());
3351
3352        let seq = seq.unwrap();
3353        assert_eq!(seq.len(), 3);
3354        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
3355        assert_eq!(values, vec!["a", "b", "c"]);
3356    }
3357
3358    #[test]
3359    fn test_mapping_get_nonexistent_nested() {
3360        let yaml = YamlFile::from_str("name: Alice").unwrap();
3361        let doc = yaml.document().unwrap();
3362        let mapping = doc.as_mapping().unwrap();
3363
3364        assert_eq!(mapping.get_mapping("user"), None);
3365        assert_eq!(mapping.get_sequence("items"), None);
3366    }
3367
3368    // ===== Rename key tests =====
3369
3370    #[test]
3371    fn test_rename_key_nonexistent() {
3372        let yaml = YamlFile::from_str("name: Alice").unwrap();
3373        let doc = yaml.document().unwrap();
3374        let mapping = doc.as_mapping().unwrap();
3375
3376        let success = mapping.rename_key("age", "years");
3377        assert!(!success);
3378
3379        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3380        assert_eq!(keys, vec!["name"]);
3381    }
3382
3383    #[test]
3384    fn test_rename_key_first_entry() {
3385        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3386        let doc = yaml.document().unwrap();
3387        let mapping = doc.as_mapping().unwrap();
3388
3389        let success = mapping.rename_key("name", "username");
3390        assert!(success);
3391
3392        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3393        assert_eq!(keys, vec!["username", "age", "city"]);
3394    }
3395
3396    #[test]
3397    fn test_rename_key_middle_entry() {
3398        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3399        let doc = yaml.document().unwrap();
3400        let mapping = doc.as_mapping().unwrap();
3401
3402        let success = mapping.rename_key("age", "years");
3403        assert!(success);
3404
3405        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3406        assert_eq!(keys, vec!["name", "years", "city"]);
3407    }
3408
3409    #[test]
3410    fn test_rename_key_last_entry() {
3411        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3412        let doc = yaml.document().unwrap();
3413        let mapping = doc.as_mapping().unwrap();
3414
3415        let success = mapping.rename_key("city", "location");
3416        assert!(success);
3417
3418        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3419        assert_eq!(keys, vec!["name", "age", "location"]);
3420    }
3421
3422    // ===== Multiple value type tests =====
3423
3424    #[test]
3425    fn test_mapping_with_different_value_types() {
3426        let yaml = YamlFile::from_str("string: hello\nnumber: 42\nbool: true").unwrap();
3427        let doc = yaml.document().unwrap();
3428        let mapping = doc.as_mapping().unwrap();
3429
3430        assert_eq!(
3431            mapping
3432                .get("string")
3433                .and_then(|v| v.as_scalar().map(|s| s.as_string())),
3434            Some("hello".to_string())
3435        );
3436        assert_eq!(mapping.get("number").and_then(|v| v.to_i64()), Some(42));
3437        assert_eq!(mapping.get("bool").and_then(|v| v.to_bool()), Some(true));
3438    }
3439
3440    #[test]
3441    fn test_mapping_set_different_value_types() {
3442        let yaml = YamlFile::from_str("key: value").unwrap();
3443        let doc = yaml.document().unwrap();
3444        let mapping = doc.as_mapping().unwrap();
3445
3446        mapping.set("number", 123);
3447        mapping.set("bool", false);
3448        mapping.set("text", "hello");
3449
3450        assert_eq!(mapping.get("number").and_then(|v| v.to_i64()), Some(123));
3451        assert_eq!(mapping.get("bool").and_then(|v| v.to_bool()), Some(false));
3452        assert_eq!(
3453            mapping
3454                .get("text")
3455                .and_then(|v| v.as_scalar().map(|s| s.as_string())),
3456            Some("hello".to_string())
3457        );
3458    }
3459
3460    // ===== Edge case tests =====
3461
3462    #[test]
3463    fn test_empty_mapping_operations() {
3464        let yaml = YamlFile::from_str("{}").unwrap();
3465        let doc = yaml.document().unwrap();
3466        let mapping = doc.as_mapping().unwrap();
3467
3468        assert!(mapping.is_empty());
3469        assert!(!mapping.contains_key("any"));
3470        assert_eq!(mapping.get("any"), None);
3471        assert!(mapping.remove("any").is_none());
3472        assert!(!mapping.rename_key("old", "new"));
3473
3474        // Can still add to empty mapping
3475        mapping.set("first", "value");
3476        assert!(!mapping.is_empty());
3477        // In flow-style (JSON) context, strings are quoted
3478        assert_eq!(
3479            mapping.get("first").map(|v| v.to_string()),
3480            Some("\"value\"".to_string())
3481        );
3482    }
3483
3484    #[test]
3485    fn test_mapping_remove_first_of_three() {
3486        let yaml = YamlFile::from_str("a: 1\nb: 2\nc: 3").unwrap();
3487        let doc = yaml.document().unwrap();
3488        let mapping = doc.as_mapping().unwrap();
3489
3490        assert!(mapping.remove("a").is_some());
3491
3492        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3493        assert_eq!(keys, vec!["b", "c"]);
3494    }
3495
3496    #[test]
3497    fn test_mapping_remove_middle_of_three() {
3498        let yaml = YamlFile::from_str("a: 1\nb: 2\nc: 3").unwrap();
3499        let doc = yaml.document().unwrap();
3500        let mapping = doc.as_mapping().unwrap();
3501
3502        assert!(mapping.remove("b").is_some());
3503
3504        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3505        assert_eq!(keys, vec!["a", "c"]);
3506    }
3507
3508    #[test]
3509    fn test_mapping_remove_last_of_three() {
3510        let yaml = YamlFile::from_str("a: 1\nb: 2\nc: 3").unwrap();
3511        let doc = yaml.document().unwrap();
3512        let mapping = doc.as_mapping().unwrap();
3513
3514        assert!(mapping.remove("c").is_some());
3515
3516        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3517        assert_eq!(keys, vec!["a", "b"]);
3518    }
3519
3520    // ===== Collection method tests =====
3521
3522    #[test]
3523    fn test_mapping_len_empty() {
3524        let yaml = YamlFile::from_str("{}").unwrap();
3525        let doc = yaml.document().unwrap();
3526        let mapping = doc.as_mapping().unwrap();
3527
3528        assert_eq!(mapping.len(), 0);
3529        assert!(mapping.is_empty());
3530    }
3531
3532    #[test]
3533    fn test_mapping_len_single() {
3534        let yaml = YamlFile::from_str("name: Alice").unwrap();
3535        let doc = yaml.document().unwrap();
3536        let mapping = doc.as_mapping().unwrap();
3537
3538        assert_eq!(mapping.len(), 1);
3539        assert!(!mapping.is_empty());
3540    }
3541
3542    #[test]
3543    fn test_mapping_len_multiple() {
3544        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3545        let doc = yaml.document().unwrap();
3546        let mapping = doc.as_mapping().unwrap();
3547
3548        assert_eq!(mapping.len(), 3);
3549        assert!(!mapping.is_empty());
3550    }
3551
3552    #[test]
3553    fn test_mapping_len_after_adding() {
3554        let yaml = YamlFile::from_str("name: Alice").unwrap();
3555        let doc = yaml.document().unwrap();
3556        let mapping = doc.as_mapping().unwrap();
3557
3558        assert_eq!(mapping.len(), 1);
3559
3560        mapping.set("age", 30);
3561        assert_eq!(mapping.len(), 2);
3562
3563        mapping.set("city", "NYC");
3564        assert_eq!(mapping.len(), 3);
3565    }
3566
3567    #[test]
3568    fn test_mapping_len_after_removing() {
3569        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3570        let doc = yaml.document().unwrap();
3571        let mapping = doc.as_mapping().unwrap();
3572
3573        assert_eq!(mapping.len(), 3);
3574
3575        mapping.remove("age");
3576        assert_eq!(mapping.len(), 2);
3577
3578        mapping.remove("city");
3579        assert_eq!(mapping.len(), 1);
3580
3581        mapping.remove("name");
3582        assert_eq!(mapping.len(), 0);
3583        assert!(mapping.is_empty());
3584    }
3585
3586    #[test]
3587    fn test_mapping_values_empty() {
3588        let yaml = YamlFile::from_str("{}").unwrap();
3589        let doc = yaml.document().unwrap();
3590        let mapping = doc.as_mapping().unwrap();
3591
3592        let values: Vec<_> = mapping.values().collect();
3593        assert_eq!(values.len(), 0);
3594    }
3595
3596    #[test]
3597    fn test_mapping_values_single() {
3598        let yaml = YamlFile::from_str("name: Alice").unwrap();
3599        let doc = yaml.document().unwrap();
3600        let mapping = doc.as_mapping().unwrap();
3601
3602        let values: Vec<_> = mapping.values().collect();
3603        assert_eq!(values.len(), 1);
3604        assert_eq!(
3605            values[0].as_scalar().map(|s| s.as_string()),
3606            Some("Alice".to_string())
3607        );
3608    }
3609
3610    #[test]
3611    fn test_mapping_values_multiple() {
3612        let yaml = YamlFile::from_str("name: Alice\nage: 30\nactive: true").unwrap();
3613        let doc = yaml.document().unwrap();
3614        let mapping = doc.as_mapping().unwrap();
3615
3616        let values: Vec<_> = mapping.values().collect();
3617        assert_eq!(values.len(), 3);
3618        assert_eq!(
3619            values[0].as_scalar().map(|s| s.as_string()),
3620            Some("Alice".to_string())
3621        );
3622        assert_eq!(values[1].to_i64(), Some(30));
3623        assert_eq!(values[2].to_bool(), Some(true));
3624    }
3625
3626    #[test]
3627    fn test_mapping_values_different_types() {
3628        let yaml = YamlFile::from_str("string: hello\nnumber: 42\nbool: false").unwrap();
3629        let doc = yaml.document().unwrap();
3630        let mapping = doc.as_mapping().unwrap();
3631
3632        // Collect values and check types
3633        let values: Vec<_> = mapping.values().collect();
3634        assert_eq!(values.len(), 3);
3635
3636        assert_eq!(
3637            values[0].as_scalar().map(|s| s.as_string()),
3638            Some("hello".to_string())
3639        );
3640        assert_eq!(values[1].to_i64(), Some(42));
3641        assert_eq!(values[2].to_bool(), Some(false));
3642    }
3643
3644    #[test]
3645    fn test_mapping_iter_empty() {
3646        let yaml = YamlFile::from_str("{}").unwrap();
3647        let doc = yaml.document().unwrap();
3648        let mapping = doc.as_mapping().unwrap();
3649
3650        let pairs: Vec<_> = mapping.iter().collect();
3651        assert_eq!(pairs.len(), 0);
3652    }
3653
3654    #[test]
3655    fn test_mapping_iter_single() {
3656        let yaml = YamlFile::from_str("name: Alice").unwrap();
3657        let doc = yaml.document().unwrap();
3658        let mapping = doc.as_mapping().unwrap();
3659
3660        let pairs: Vec<_> = mapping.iter().collect();
3661        assert_eq!(pairs.len(), 1);
3662        assert_eq!(
3663            pairs[0].0.as_scalar().map(|s| s.as_string()),
3664            Some("name".to_string())
3665        );
3666        assert_eq!(
3667            pairs[0].1.as_scalar().map(|s| s.as_string()),
3668            Some("Alice".to_string())
3669        );
3670    }
3671
3672    #[test]
3673    fn test_mapping_iter_multiple() {
3674        let yaml = YamlFile::from_str("name: Alice\nage: 30\nactive: true").unwrap();
3675        let doc = yaml.document().unwrap();
3676        let mapping = doc.as_mapping().unwrap();
3677
3678        let pairs: Vec<_> = mapping.iter().collect();
3679
3680        assert_eq!(pairs.len(), 3);
3681        assert_eq!(
3682            pairs[0].0.as_scalar().map(|s| s.as_string()),
3683            Some("name".to_string())
3684        );
3685        assert_eq!(
3686            pairs[0].1.as_scalar().map(|s| s.as_string()),
3687            Some("Alice".to_string())
3688        );
3689        assert_eq!(
3690            pairs[1].0.as_scalar().map(|s| s.as_string()),
3691            Some("age".to_string())
3692        );
3693        assert_eq!(pairs[1].1.to_i64(), Some(30));
3694        assert_eq!(
3695            pairs[2].0.as_scalar().map(|s| s.as_string()),
3696            Some("active".to_string())
3697        );
3698        assert_eq!(pairs[2].1.to_bool(), Some(true));
3699    }
3700
3701    #[test]
3702    fn test_mapping_iter_different_types() {
3703        let yaml = YamlFile::from_str("string: hello\nnumber: 42\nbool: false").unwrap();
3704        let doc = yaml.document().unwrap();
3705        let mapping = doc.as_mapping().unwrap();
3706
3707        let pairs: Vec<_> = mapping.iter().collect();
3708        assert_eq!(pairs.len(), 3);
3709
3710        // Check first pair (string: hello)
3711        assert_eq!(
3712            pairs[0].0.as_scalar().map(|s| s.as_string()),
3713            Some("string".to_string())
3714        );
3715        assert_eq!(
3716            pairs[0].1.as_scalar().map(|s| s.as_string()),
3717            Some("hello".to_string())
3718        );
3719
3720        // Check second pair (number: 42)
3721        assert_eq!(
3722            pairs[1].0.as_scalar().map(|s| s.as_string()),
3723            Some("number".to_string())
3724        );
3725        assert_eq!(pairs[1].1.to_i64(), Some(42));
3726
3727        // Check third pair (bool: false)
3728        assert_eq!(
3729            pairs[2].0.as_scalar().map(|s| s.as_string()),
3730            Some("bool".to_string())
3731        );
3732        assert_eq!(pairs[2].1.to_bool(), Some(false));
3733    }
3734
3735    #[test]
3736    fn test_mapping_iter_preserves_order() {
3737        let yaml = YamlFile::from_str("z: 1\na: 2\nm: 3").unwrap();
3738        let doc = yaml.document().unwrap();
3739        let mapping = doc.as_mapping().unwrap();
3740
3741        let pairs: Vec<_> = mapping.iter().collect();
3742        assert_eq!(pairs.len(), 3);
3743        assert_eq!(
3744            pairs[0].0.as_scalar().map(|s| s.as_string()),
3745            Some("z".to_string())
3746        );
3747        assert_eq!(
3748            pairs[1].0.as_scalar().map(|s| s.as_string()),
3749            Some("a".to_string())
3750        );
3751        assert_eq!(
3752            pairs[2].0.as_scalar().map(|s| s.as_string()),
3753            Some("m".to_string())
3754        );
3755    }
3756
3757    #[test]
3758    fn test_mapping_clear_empty() {
3759        let yaml = YamlFile::from_str("{}").unwrap();
3760        let doc = yaml.document().unwrap();
3761        let mapping = doc.as_mapping().unwrap();
3762
3763        assert_eq!(mapping.len(), 0);
3764        mapping.clear();
3765        assert_eq!(mapping.len(), 0);
3766    }
3767
3768    #[test]
3769    fn test_mapping_clear_single() {
3770        let yaml = YamlFile::from_str("name: Alice").unwrap();
3771        let doc = yaml.document().unwrap();
3772        let mapping = doc.as_mapping().unwrap();
3773
3774        assert_eq!(mapping.len(), 1);
3775        mapping.clear();
3776        assert_eq!(mapping.len(), 0);
3777        assert!(mapping.is_empty());
3778
3779        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3780        assert_eq!(keys, Vec::<String>::new());
3781    }
3782
3783    #[test]
3784    fn test_mapping_clear_multiple() {
3785        let yaml = YamlFile::from_str("name: Alice\nage: 30\ncity: NYC").unwrap();
3786        let doc = yaml.document().unwrap();
3787        let mapping = doc.as_mapping().unwrap();
3788
3789        assert_eq!(mapping.len(), 3);
3790        mapping.clear();
3791        assert_eq!(mapping.len(), 0);
3792        assert!(mapping.is_empty());
3793
3794        let keys: Vec<String> = mapping.keys().map(|k| k.to_string()).collect();
3795        assert_eq!(keys, Vec::<String>::new());
3796    }
3797
3798    #[test]
3799    fn test_mapping_clear_and_add() {
3800        let yaml = YamlFile::from_str("name: Alice\nage: 30").unwrap();
3801        let doc = yaml.document().unwrap();
3802        let mapping = doc.as_mapping().unwrap();
3803
3804        assert_eq!(mapping.len(), 2);
3805        mapping.clear();
3806        assert_eq!(mapping.len(), 0);
3807
3808        // Add new entries after clearing
3809        mapping.set("new_key", "new_value");
3810        assert_eq!(mapping.len(), 1);
3811        let value = mapping.get("new_key").unwrap();
3812        assert_eq!(
3813            value.as_scalar().map(|s| s.as_string()),
3814            Some("new_value".to_string())
3815        );
3816    }
3817
3818    #[test]
3819    fn test_mapping_clear_large() {
3820        // Build a large mapping
3821        let yaml_str = (0..100)
3822            .map(|i| format!("key{}: value{}", i, i))
3823            .collect::<Vec<_>>()
3824            .join("\n");
3825        let yaml = YamlFile::from_str(&yaml_str).unwrap();
3826        let doc = yaml.document().unwrap();
3827        let mapping = doc.as_mapping().unwrap();
3828
3829        assert_eq!(mapping.len(), 100);
3830        mapping.clear();
3831        assert_eq!(mapping.len(), 0);
3832        assert!(mapping.is_empty());
3833    }
3834
3835    #[test]
3836    fn test_mapping_newline_handling_block_style() {
3837        // Block-style mappings should end with newline
3838        let yaml_with_newline = "key1: value1\nkey2: value2\n";
3839        let yaml = YamlFile::from_str(yaml_with_newline).unwrap();
3840
3841        // Convert back to string - should preserve the newline
3842        let output = yaml.to_string();
3843        assert!(
3844            output.ends_with('\n'),
3845            "Block-style mapping should preserve trailing newline"
3846        );
3847        assert_eq!(output, yaml_with_newline);
3848    }
3849
3850    #[test]
3851    fn test_mapping_newline_handling_no_trailing() {
3852        // Mapping without trailing newline
3853        let yaml_no_newline = "key: value";
3854        let yaml = YamlFile::from_str(yaml_no_newline).unwrap();
3855
3856        // Convert back to string - should not add newline
3857        let output = yaml.to_string();
3858        assert!(
3859            !output.ends_with('\n'),
3860            "Mapping without trailing newline should not add one"
3861        );
3862        assert_eq!(output, yaml_no_newline);
3863    }
3864
3865    #[test]
3866    fn test_mapping_newline_handling_flow_style() {
3867        // Flow-style mappings typically don't have trailing newlines
3868        let yaml_flow = "data: {key1: value1, key2: value2}";
3869        let yaml = YamlFile::from_str(yaml_flow).unwrap();
3870
3871        // The flow mapping should serialize exactly as parsed
3872        let output = yaml.to_string();
3873        assert_eq!(output, yaml_flow);
3874    }
3875
3876    #[test]
3877    fn test_mapping_set_preserves_newline_context() {
3878        // When setting values in a mapping, newline context should be preserved
3879        let yaml_str = "key1: value1\nkey2: value2\n";
3880        let yaml = YamlFile::from_str(yaml_str).unwrap();
3881        let doc = yaml.document().unwrap();
3882        let mapping = doc.as_mapping().unwrap();
3883
3884        // Modify a value
3885        mapping.set("key1", "new_value");
3886
3887        // Should still end with newline
3888        let output = yaml.to_string();
3889        assert!(
3890            output.ends_with('\n'),
3891            "Newline should be preserved after modification"
3892        );
3893    }
3894}