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