Skip to main content

yaml_edit/nodes/
sequence.rs

1use super::{
2    append_children, ensure_trailing_newline, entry_indices, fresh_token, has_child_token,
3    nth_entry_index, Lang, SyntaxNode,
4};
5use crate::as_yaml::{AsYaml, YamlKind};
6use crate::lex::SyntaxKind;
7use crate::yaml::ValueNode;
8use rowan::ast::AstNode;
9use rowan::GreenNodeBuilder;
10
11ast_node!(Sequence, SEQUENCE, "A YAML sequence (list)");
12
13/// True if `node` cannot be rendered as a block collection: any
14/// flow ancestor forbids it, and so does sitting inline after a `- `
15/// (no NEWLINE + INDENT scaffold to hang a block entry off).
16pub(crate) fn must_render_flow(node: &SyntaxNode) -> bool {
17    if node
18        .parent()
19        .is_some_and(|p| p.kind() == SyntaxKind::SEQUENCE_ENTRY)
20    {
21        return true;
22    }
23    let mut cur = node.parent();
24    while let Some(p) = cur {
25        let opener = match p.kind() {
26            SyntaxKind::MAPPING => Some(SyntaxKind::LEFT_BRACE),
27            SyntaxKind::SEQUENCE => Some(SyntaxKind::LEFT_BRACKET),
28            _ => None,
29        };
30        if let Some(open) = opener {
31            // A pending-block placeholder is `{}` in the text but is headed
32            // for block style, so it does not force flow on what nests inside
33            // it (see `Mapping::new_pending_block`).
34            let pending =
35                crate::nodes::Mapping::cast(p.clone()).is_some_and(|m| m.is_block_placeholder());
36            if has_child_token(&p, |k| k == open) && !pending {
37                return true;
38            }
39        }
40        cur = p.parent();
41    }
42    false
43}
44
45/// Does the SEQUENCE's parent VALUE carry the leading INDENT for this
46/// sequence? True for the block-under-key shape (`key:\n  - a`): the
47/// first entry's indentation lives in VALUE, not inside SEQUENCE, so
48/// mutation helpers must not emit their own leading INDENT for it.
49fn parent_value_has_leading_indent(seq: &SyntaxNode) -> bool {
50    let Some(parent) = seq.parent() else {
51        return false;
52    };
53    if parent.kind() != SyntaxKind::VALUE {
54        return false;
55    }
56    let mut saw_indent = false;
57    for child in parent.children_with_tokens() {
58        match &child {
59            rowan::NodeOrToken::Node(n) if n == seq => return saw_indent,
60            rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::INDENT => saw_indent = true,
61            rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::NEWLINE => {}
62            _ => saw_indent = false,
63        }
64    }
65    false
66}
67
68/// If `node`'s tail token is a NEWLINE (optionally followed by an
69/// INDENT), return the concatenated text as `Some("\n" | "\n<indent>")`.
70///
71/// Used by [`Sequence::set`] to preserve the trailing separator of a
72/// multi-line value when swapping it out. Walking the CST directly is
73/// safer than `node.text().rfind('\n')` because a NEWLINE nested inside
74/// a block scalar's content would mislead a text-level search.
75fn trailing_newline_indent(node: &SyntaxNode) -> Option<String> {
76    let tokens: Vec<_> = node
77        .descendants_with_tokens()
78        .filter_map(|el| el.into_token())
79        .collect();
80    let mut result = String::new();
81    // Look at the last two tail tokens: [NEWLINE], [NEWLINE, INDENT], or
82    // [.., NEWLINE], [.., NEWLINE, INDENT].
83    let last = tokens.last()?;
84    match last.kind() {
85        SyntaxKind::NEWLINE => {
86            result.push_str(last.text());
87        }
88        SyntaxKind::INDENT => {
89            let prev = tokens.iter().rev().nth(1)?;
90            if prev.kind() != SyntaxKind::NEWLINE {
91                return None;
92            }
93            result.push_str(prev.text());
94            result.push_str(last.text());
95        }
96        _ => return None,
97    }
98    Some(result)
99}
100
101/// True if `sequence`'s enclosing MAPPING_ENTRY is the last entry of
102/// its MAPPING. Callers use this to know whether trimming trailing
103/// whitespace off the sequence would strand a following mapping entry
104/// that relied on the separator NEWLINE. Conservative `false` when
105/// the sequence isn't under a MAPPING_ENTRY at all.
106/// Rebuild `entry` with `value` in place of the node it currently holds,
107/// keeping everything else the entry carried.
108fn rebuild_entry_with_value(entry: &SyntaxNode, value: &impl crate::AsYaml) -> SyntaxNode {
109    let entry_children: Vec<_> = entry.children_with_tokens().collect();
110
111    // Build a new SEQUENCE_ENTRY with the new value using AsYaml
112    let mut builder = GreenNodeBuilder::new();
113    builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
114
115    let mut value_inserted = false;
116    let mut trailing_text: Option<String> = None;
117    let mut after_dash = false;
118
119    for entry_child in entry_children {
120        match &entry_child {
121            rowan::NodeOrToken::Node(n)
122                if matches!(
123                    n.kind(),
124                    SyntaxKind::SCALAR
125                        | SyntaxKind::MAPPING
126                        | SyntaxKind::SEQUENCE
127                        | SyntaxKind::ALIAS
128                        | SyntaxKind::TAGGED_NODE
129                ) =>
130            {
131                // A multi-line value (a nested mapping, say) ends with a
132                // NEWLINE and often an INDENT, which separate the entry
133                // from whatever follows and outlive the value itself.
134                trailing_text = trailing_newline_indent(n);
135                if !value_inserted {
136                    // A bare `-` item is DASH then a zero-width NULL
137                    // scalar, so a written value needs the space that
138                    // was never there: without it `set` gives `-x`.
139                    if after_dash {
140                        builder.token(SyntaxKind::WHITESPACE.into(), " ");
141                    }
142                    value.build_content(&mut builder, 0, false);
143                    value_inserted = true;
144                }
145                after_dash = false;
146            }
147            rowan::NodeOrToken::Node(n) => {
148                // Copy other nodes as-is (like VALUE wrappers, etc.)
149                crate::yaml::copy_node_to_builder(&mut builder, n);
150                after_dash = false;
151            }
152            rowan::NodeOrToken::Token(t) => {
153                // Copy tokens as-is
154                builder.token(t.kind().into(), t.text());
155                after_dash = t.kind() == SyntaxKind::DASH;
156            }
157        }
158    }
159
160    // Restore trailing whitespace extracted from the old value
161    if let Some(trailing) = trailing_text {
162        if let Some(indent_part) = trailing.strip_prefix('\n') {
163            builder.token(SyntaxKind::NEWLINE.into(), "\n");
164            if !indent_part.is_empty() {
165                builder.token(SyntaxKind::INDENT.into(), indent_part);
166            }
167        }
168    }
169
170    builder.finish_node();
171    SyntaxNode::new_root_mut(builder.finish())
172}
173
174/// Drop the run of blank tokens (newline, indent, whitespace) that ends
175/// `entry`, so removing the entry after it leaves no stray blank line.
176fn strip_trailing_blank_tokens(entry: &SyntaxNode) {
177    let children: Vec<_> = entry.children_with_tokens().collect();
178    let blanks = children
179        .iter()
180        .rev()
181        .take_while(|c| {
182            c.as_token().is_some_and(|t| {
183                matches!(
184                    t.kind(),
185                    SyntaxKind::NEWLINE | SyntaxKind::INDENT | SyntaxKind::WHITESPACE
186                )
187            })
188        })
189        .count();
190    if blanks > 0 {
191        let total = children.len();
192        entry.splice_children((total - blanks)..total, vec![]);
193    }
194}
195
196fn mapping_entry_is_last_in_mapping(sequence: &SyntaxNode) -> bool {
197    let Some(value) = sequence.parent() else {
198        return false;
199    };
200    if value.kind() != SyntaxKind::VALUE {
201        return false;
202    }
203    let Some(entry) = value.parent() else {
204        return false;
205    };
206    if entry.kind() != SyntaxKind::MAPPING_ENTRY {
207        return false;
208    }
209    let Some(parent_mapping) = entry.parent() else {
210        return false;
211    };
212    if parent_mapping.kind() != SyntaxKind::MAPPING {
213        return false;
214    }
215    // Any MAPPING_ENTRY sibling *after* this one means we must keep
216    // whatever separator is currently in place.
217    let mut seen_self = false;
218    for child in parent_mapping.children() {
219        if child == entry {
220            seen_self = true;
221            continue;
222        }
223        if seen_self && child.kind() == SyntaxKind::MAPPING_ENTRY {
224            return false;
225        }
226    }
227    true
228}
229
230// The collapse helper is shared with Mapping; it lives in yaml.rs
231// as `collapse_empty_child_collection_in_parent`.
232use crate::yaml::collapse_empty_child_collection_in_parent as collapse_empty_child_sequence_in_parent;
233
234impl Sequence {
235    /// Iterate over items in this sequence as raw syntax nodes.
236    ///
237    /// For most use cases prefer [`values`](Self::values) which returns
238    /// [`YamlNode`](crate::as_yaml::YamlNode)s.
239    pub(crate) fn items(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
240        self.0.children().filter_map(|child| {
241            if child.kind() == SyntaxKind::SEQUENCE_ENTRY {
242                // Look for the actual item within the SEQUENCE_ENTRY
243                // Skip DASH and WHITESPACE tokens, find the actual value node
244                child.children().find(|n| {
245                    matches!(
246                        n.kind(),
247                        SyntaxKind::SCALAR
248                            | SyntaxKind::MAPPING
249                            | SyntaxKind::SEQUENCE
250                            | SyntaxKind::ALIAS
251                            | SyntaxKind::TAGGED_NODE
252                    )
253                })
254            } else {
255                None
256            }
257        })
258    }
259
260    /// Iterate over items in this sequence as [`YamlNode`](crate::as_yaml::YamlNode)s.
261    ///
262    /// Items that cannot be wrapped as a `YamlNode` are silently skipped.
263    pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
264        self.items()
265            .filter_map(crate::as_yaml::YamlNode::from_syntax)
266    }
267
268    /// Returns the number of items in this sequence.
269    pub fn len(&self) -> usize {
270        self.items().count()
271    }
272
273    /// Returns `true` if this sequence contains no items.
274    pub fn is_empty(&self) -> bool {
275        self.items().next().is_none()
276    }
277
278    /// Get the item at `index` as a [`YamlNode`](crate::as_yaml::YamlNode).
279    ///
280    /// Returns `None` if `index` is out of bounds.
281    pub fn get(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
282        self.items()
283            .nth(index)
284            .and_then(crate::as_yaml::YamlNode::from_syntax)
285    }
286
287    /// Get the first item in this sequence, or `None` if empty.
288    pub fn first(&self) -> Option<crate::as_yaml::YamlNode> {
289        self.get(0)
290    }
291
292    /// Get the last item in this sequence, or `None` if empty.
293    pub fn last(&self) -> Option<crate::as_yaml::YamlNode> {
294        let len = self.len();
295        if len == 0 {
296            None
297        } else {
298            self.get(len - 1)
299        }
300    }
301}
302
303impl Sequence {
304    /// Create a new empty sequence.
305    pub fn new() -> Self {
306        Self::new_pending_block()
307    }
308
309    /// An empty sequence that renders as `[]` and stays flow when filled.
310    ///
311    /// Use this for a sequence that is meant to be flow-style in the output,
312    /// including one that is left empty.
313    pub fn new_flow() -> Self {
314        let mut builder = GreenNodeBuilder::new();
315        builder.start_node(SyntaxKind::SEQUENCE.into());
316        builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
317        builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
318        builder.finish_node();
319        Sequence(SyntaxNode::new_root_mut(builder.finish()))
320    }
321
322    /// An empty sequence that becomes a block sequence once an item is added.
323    ///
324    /// YAML has no block syntax for an empty sequence: block collections are
325    /// written as their entries, so with none left there is nothing to write,
326    /// and a bare `key:` reads back as null. An empty sequence is therefore
327    /// `[]` whatever its eventual style, and block-vs-flow only becomes a
328    /// real choice once there is an item.
329    ///
330    /// This renders as exactly `[]`, plus a zero-width token that marks it as
331    /// destined for block style. [`Sequence::push`] rewrites it into block
332    /// form when the first item arrives; a `[]` that came from the source has
333    /// no such marker and keeps its flow style.
334    ///
335    /// The marker lives only in the tree. Serializing and re-parsing yields
336    /// an ordinary `[]`, which stays flow, since YAML has nowhere to record
337    /// the intent.
338    pub fn new_pending_block() -> Self {
339        let mut builder = GreenNodeBuilder::new();
340        builder.start_node(SyntaxKind::SEQUENCE.into());
341        builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
342        // Zero-width sentinel: renders as nothing, so this is still exactly
343        // `[]` in the text, but marks the sequence as one that should become
344        // block when it gets its first item.
345        builder.token(SyntaxKind::WHITESPACE.into(), "");
346        builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
347        builder.finish_node();
348        Sequence(SyntaxNode::new_root_mut(builder.finish()))
349    }
350
351    /// Is this an empty sequence awaiting its first item? See
352    /// [`Sequence::new_pending_block`].
353    pub(crate) fn is_block_placeholder(&self) -> bool {
354        !self
355            .0
356            .children()
357            .any(|c| c.kind() == SyntaxKind::SEQUENCE_ENTRY)
358            && self
359                .0
360                .children_with_tokens()
361                .filter_map(|c| c.into_token())
362                .any(|t| t.kind() == SyntaxKind::WHITESPACE && t.text().is_empty())
363    }
364
365    /// Indentation string used by entries in this sequence: a
366    /// top-level INDENT if present, else WHITESPACE before DASH inside
367    /// an entry, else the parent VALUE's INDENT (the parser's storage
368    /// for single-entry block sequences under a key). Defaults to two
369    /// spaces.
370    fn detect_indentation(&self) -> String {
371        // First try top-level INDENT tokens
372        if let Some(ind) = self.0.children_with_tokens().find_map(|child| {
373            child
374                .into_token()
375                .filter(|t| t.kind() == SyntaxKind::INDENT)
376                .map(|t| t.text().to_string())
377        }) {
378            return ind;
379        }
380
381        // Fall back: look for WHITESPACE before DASH inside entry nodes
382        if let Some(indent) = self
383            .0
384            .children()
385            .filter(|c| c.kind() == SyntaxKind::SEQUENCE_ENTRY)
386            .find_map(|entry| {
387                let tokens: Vec<_> = entry.children_with_tokens().collect();
388                tokens.windows(2).find_map(|pair| {
389                    let ws = pair[0].as_token()?;
390                    let dash = pair[1].as_token()?;
391                    if ws.kind() == SyntaxKind::WHITESPACE && dash.kind() == SyntaxKind::DASH {
392                        Some(ws.text().to_string())
393                    } else {
394                        None
395                    }
396                })
397            })
398        {
399            return indent;
400        }
401
402        // For a block sequence under a key, the parser stores the entry
403        // column as an INDENT in the parent VALUE (right before the SEQUENCE).
404        if let Some(parent) = self.0.parent().filter(|p| p.kind() == SyntaxKind::VALUE) {
405            for child in parent.children_with_tokens() {
406                match &child {
407                    rowan::NodeOrToken::Node(n) if n == &self.0 => break,
408                    rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::INDENT => {
409                        return t.text().to_string();
410                    }
411                    _ => {}
412                }
413            }
414        }
415
416        // A sequence that is the document's own node starts at column 0, so
417        // there is nothing to indent by. Guessing two spaces here made
418        // `push` write the new entry as a nested sequence inside the last
419        // one, where as_sequence could no longer reach it.
420        match self.0.parent() {
421            None => return String::new(),
422            Some(parent) if parent.kind() == SyntaxKind::DOCUMENT => return String::new(),
423            Some(_) => {}
424        }
425
426        "  ".to_string()
427    }
428
429    /// Append `count` copies of `value` to the end of the sequence.
430    ///
431    /// Equivalent to calling [`push`](Self::push) `count` times, but splices
432    /// the new entries in one edit rather than one each. Since a single edit
433    /// is linear in the sequence's length (see `push`), doing them one at a
434    /// time is quadratic: padding a sequence out to 1023 entries took 89ms
435    /// that way and under 2ms this way.
436    pub(crate) fn extend_with(&self, value: impl crate::AsYaml + Clone, count: usize) {
437        if count == 0 {
438            return;
439        }
440        // The first entry settles the questions push answers about style and
441        // scaffolding -- flow vs block, the indent string, whether the parent
442        // supplies the indent, the placeholder newline. Let it, then append
443        // the rest alongside it in one splice.
444        self.push(value.clone());
445        let Some(remaining) = count.checked_sub(1).filter(|n| *n > 0) else {
446            return;
447        };
448        if self.is_flow_style() {
449            for _ in 0..remaining {
450                self.push(value.clone());
451            }
452            return;
453        }
454
455        // Mirror the entry push just made: same indent, and a trailing
456        // NEWLINE since it is no longer last.
457        let indentation = self.detect_indentation();
458        let mut inserts: Vec<rowan::NodeOrToken<SyntaxNode, _>> = Vec::new();
459        for _ in 0..remaining {
460            let mut builder = GreenNodeBuilder::new();
461            builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
462            builder.token(SyntaxKind::DASH.into(), "-");
463            builder.token(SyntaxKind::WHITESPACE.into(), " ");
464            let ends_with_newline = value.clone().build_content(&mut builder, 0, false);
465            if !ends_with_newline {
466                builder.token(SyntaxKind::NEWLINE.into(), "\n");
467            }
468            builder.finish_node();
469            inserts.push(fresh_token(SyntaxKind::INDENT, &indentation).into());
470            inserts.push(SyntaxNode::new_root_mut(builder.finish()).into());
471        }
472
473        let end = self.0.green().children().len();
474        self.0.splice_children(end..end, inserts);
475    }
476
477    /// Add an item to the end of the sequence.
478    ///
479    /// Mutates in place despite `&self` (see crate docs on interior mutability).
480    ///
481    /// # Performance
482    ///
483    /// Runs in time linear in the sequence's current length, so building a
484    /// sequence one push at a time is quadratic overall. The cost is rowan
485    /// rebuilding the green node for every edit, which an immutable tree has
486    /// to do; this method already avoids the avoidable part by reading only
487    /// the tail of the child list and holding no child handles across the
488    /// edit. As a rough guide, 1000 pushes take about 70ms and 8000 about
489    /// 4.5s (release build).
490    ///
491    /// For a large sequence, build it with
492    /// [`SequenceBuilder`](crate::SequenceBuilder) or parse the YAML text in
493    /// one go, rather than pushing in a loop: the builder is linear, and
494    /// assembles those 8000 entries in under 2ms.
495    /// [`insert`](Self::insert) and [`remove`](Self::remove) are likewise
496    /// linear per call, and rather more costly than `push`.
497    pub fn push(&self, value: impl crate::AsYaml) {
498        // A placeholder from `Sequence::new_pending_block` turns into a real
499        // block sequence now that it has an item. A `[]` from the source is
500        // not marked and keeps its flow style; a placeholder nested inside a
501        // flow container stays flow to avoid mixed output.
502        if self.is_block_placeholder() && !must_render_flow(&self.0) {
503            crate::yaml::convert_placeholder_to_block(&self.0);
504        }
505        if self.is_flow_style() {
506            self.insert_flow(usize::MAX, value);
507            return;
508        }
509
510        let indentation = self.detect_indentation();
511
512        // Build the INDENT token (separate from the SEQUENCE_ENTRY)
513        let indent_token = fresh_token(SyntaxKind::INDENT, &indentation);
514
515        // Locate the last SEQUENCE_ENTRY and the insert position by walking
516        // back from the end of the child list.
517        //
518        // Both this scan and `splice_children` cost O(len) if done naively,
519        // which makes building a sequence O(len^2). The green child list is
520        // a DoubleEndedIterator, so walking back from the end reads only the
521        // few trailing elements; and since `splice_children` has to fix up
522        // every *live* SyntaxNode handle, take the measurements and fix the
523        // previous entry before creating the handles the splice needs.
524        let green = self.0.green();
525        let mut tail = green.children();
526        let child_count = tail.len();
527
528        // Walk back over the trailing tokens the parser leaves after the
529        // last entry. A NEWLINE there is a blank line belonging to the
530        // enclosing mapping, so the new entry goes before it; an INDENT is a
531        // separator, so the new entry goes after it.
532        let mut last_entry_index = None;
533        let mut last_entry_has_newline = true; // Default for an empty sequence
534        let mut insert_pos = child_count;
535        let mut idx = child_count;
536        while idx > 0 {
537            idx -= 1;
538            match tail.next_back() {
539                Some(rowan::NodeOrToken::Node(n))
540                    if n.kind() == SyntaxKind::SEQUENCE_ENTRY.into() =>
541                {
542                    last_entry_has_newline = n
543                        .children()
544                        .next_back()
545                        .and_then(|c| c.into_token())
546                        .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE.into());
547                    last_entry_index = Some(idx);
548                    if insert_pos == child_count {
549                        insert_pos = idx + 1;
550                    }
551                    break;
552                }
553                Some(rowan::NodeOrToken::Token(tok)) if tok.kind() == SyntaxKind::INDENT.into() => {
554                    insert_pos = idx + 1;
555                }
556                Some(rowan::NodeOrToken::Token(tok))
557                    if tok.kind() == SyntaxKind::NEWLINE.into() =>
558                {
559                    insert_pos = idx;
560                }
561                _ => break,
562            }
563        }
564
565        // Build the SEQUENCE_ENTRY node using AsYaml trait
566        let mut builder = GreenNodeBuilder::new();
567        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
568        builder.token(SyntaxKind::DASH.into(), "-");
569        builder.token(SyntaxKind::WHITESPACE.into(), " ");
570
571        // Build the value content directly using AsYaml
572        let value_ends_with_newline = value.build_content(&mut builder, 0, false);
573
574        // Add trailing newline only if the value doesn't already end with one
575        // and if the last entry had one (preserves document style)
576        if last_entry_has_newline && !value_ends_with_newline {
577            builder.token(SyntaxKind::NEWLINE.into(), "\n");
578        }
579        builder.finish_node(); // SEQUENCE_ENTRY
580        let new_entry = SyntaxNode::new_root_mut(builder.finish());
581
582        // Ensure the previous last entry has a trailing newline (it won't be
583        // last anymore). `last_child` reaches it without walking the list.
584        if last_entry_index.is_some() && !last_entry_has_newline {
585            if let Some(node) = self
586                .0
587                .last_child()
588                .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
589            {
590                let entry_children_count = node.green().children().len();
591                let nl = fresh_token(SyntaxKind::NEWLINE, "\n");
592                node.splice_children(entry_children_count..entry_children_count, vec![nl.into()]);
593            }
594        }
595
596        // Parser convention: the first entry in a SEQUENCE has no leading
597        // INDENT inside the SEQUENCE; its indentation comes from the parent
598        // VALUE (`NEWLINE INDENT` right before the SEQUENCE). Only later
599        // entries carry an INDENT as a separator after the previous entry's
600        // NEWLINE. Match that when pushing.
601        let entry_is_first = last_entry_index.is_none();
602        let parent_supplies_indent = entry_is_first && parent_value_has_leading_indent(&self.0);
603
604        let mut inserts: Vec<rowan::NodeOrToken<SyntaxNode, _>> = Vec::new();
605        if !parent_supplies_indent {
606            inserts.push(indent_token.into());
607        }
608        inserts.push(new_entry.clone().into());
609
610        self.0.splice_children(insert_pos..insert_pos, inserts);
611
612        // Strip the outer MAPPING_ENTRY's placeholder NEWLINE if this was
613        // the first entry (see issue #18).
614        if entry_is_first {
615            crate::yaml::detach_empty_collection_placeholder_newline(&self.0, &new_entry);
616        }
617    }
618
619    /// Splice a new flow entry at `index` (or append when
620    /// `index >= len`), wiring up `, ` separators. The flow separator
621    /// convention is that every entry except the last carries a
622    /// trailing `, ` as its own tail (see the flow-separator note in
623    /// [`crate::nodes`]).
624    fn insert_flow(&self, index: usize, value: impl crate::AsYaml) {
625        let mut builder = GreenNodeBuilder::new();
626        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
627        // flow_context=false: YAML flow permits plain scalars; only
628        // JSON-flavored callers want the aggressive quoting.
629        value.build_content(&mut builder, 0, false);
630        builder.finish_node();
631        let new_entry = SyntaxNode::new_root_mut(builder.finish());
632
633        let children: Vec<_> = self.0.children_with_tokens().collect();
634        let entry_positions = entry_indices(&self.0, SyntaxKind::SEQUENCE_ENTRY);
635        let Some(right_bracket_pos) = children.iter().position(|c| {
636            c.as_token()
637                .is_some_and(|t| t.kind() == SyntaxKind::RIGHT_BRACKET)
638        }) else {
639            // Callers check `is_flow_style()` first, which requires
640            // a LEFT_BRACKET; the matching RIGHT_BRACKET is a parser
641            // invariant. Bail defensively on malformed CSTs rather
642            // than corrupting the tree further.
643            debug_assert!(false, "flow SEQUENCE missing RIGHT_BRACKET");
644            return;
645        };
646
647        let comma = fresh_token(SyntaxKind::COMMA, ",");
648        let sep_ws = fresh_token(SyntaxKind::WHITESPACE, " ");
649
650        if index >= entry_positions.len() {
651            // Append: add `, ` to the previous last entry, then
652            // splice before the `]`.
653            if let Some(&last_pos) = entry_positions.last() {
654                let last_entry = children[last_pos].as_node().expect("SEQUENCE_ENTRY");
655                let ends_with_comma = last_entry
656                    .last_token()
657                    .is_some_and(|t| t.kind() == SyntaxKind::COMMA);
658                if !ends_with_comma {
659                    append_children(last_entry, vec![comma.into(), sep_ws.into()]);
660                }
661            }
662            self.0
663                .splice_children(right_bracket_pos..right_bracket_pos, vec![new_entry.into()]);
664            return;
665        }
666
667        // Insert before the entry at `index`; the new entry carries
668        // its own `, ` tail to keep the displaced entry separated.
669        let target_pos = entry_positions[index];
670        append_children(&new_entry, vec![comma.into(), sep_ws.into()]);
671        self.0
672            .splice_children(target_pos..target_pos, vec![new_entry.into()]);
673    }
674
675    /// Insert an item at a specific position.
676    ///
677    /// If `index` is out of bounds, the item is appended at the end.
678    /// This method always succeeds; it never returns an error.
679    ///
680    /// Mutates in place despite `&self` (see crate docs on interior mutability).
681    pub fn insert(&self, index: usize, value: impl crate::AsYaml) {
682        // Same rule as `push`; see there for the "why".
683        if self.is_block_placeholder() && !must_render_flow(&self.0) {
684            crate::yaml::convert_placeholder_to_block(&self.0);
685        }
686        if self.is_flow_style() {
687            self.insert_flow(index, value);
688            return;
689        }
690
691        let indentation = self.detect_indentation();
692
693        // Build the new SEQUENCE_ENTRY, terminated with its own NEWLINE.
694        // Parser convention: the INDENT sits at SEQUENCE level as a
695        // separator before the entry, not inside it.
696        let mut builder = GreenNodeBuilder::new();
697        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
698        builder.token(SyntaxKind::DASH.into(), "-");
699        builder.token(SyntaxKind::WHITESPACE.into(), " ");
700        let value_ends_with_newline = value.build_content(&mut builder, 0, false);
701        if !value_ends_with_newline {
702            builder.token(SyntaxKind::NEWLINE.into(), "\n");
703        }
704        builder.finish_node();
705        let new_entry = SyntaxNode::new_root_mut(builder.finish());
706
707        // Build a standalone INDENT to sit before the new entry.
708        let indent_token = fresh_token(SyntaxKind::INDENT, &indentation);
709
710        // Locate the target position. If we're inserting before an
711        // existing entry, we want to land right at the INDENT that
712        // precedes it (so we don't split the existing NEWLINE-INDENT
713        // pairing that separates entries).
714        let children: Vec<_> = self.0.children_with_tokens().collect();
715        let mut item_count = 0;
716        let mut target_entry_pos = children.len();
717        for (i, child) in children.iter().enumerate() {
718            let Some(node) = child.as_node() else {
719                continue;
720            };
721            if node.kind() != SyntaxKind::SEQUENCE_ENTRY {
722                continue;
723            }
724            if item_count == index {
725                target_entry_pos = i;
726                break;
727            }
728            item_count += 1;
729        }
730
731        // The INDENT (if any) that separates the target entry from the
732        // previous one is the child immediately before target_entry_pos.
733        // Insert our new entry+INDENT-separator *before* that INDENT, so
734        // the layout stays `NEWLINE INDENT ENTRY INDENT NEW_ENTRY`.
735        let insert_at = if target_entry_pos > 0
736            && children
737                .get(target_entry_pos - 1)
738                .and_then(|c| c.as_token())
739                .is_some_and(|t| t.kind() == SyntaxKind::INDENT)
740        {
741            target_entry_pos - 1
742        } else {
743            target_entry_pos
744        };
745
746        // Appending past the last entry (source doc had no trailing
747        // newline)? Ensure the previous entry has one, otherwise our
748        // new INDENT+ENTRY gets glued onto its tail as `- b  - c`.
749        if target_entry_pos == children.len() {
750            if let Some(prev_entry) = children.iter().rev().find_map(|c| {
751                c.as_node()
752                    .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
753            }) {
754                ensure_trailing_newline(prev_entry);
755            }
756        }
757
758        // Inserting at the head of a SEQUENCE whose parent VALUE
759        // supplies the leading INDENT? Don't emit our own leading
760        // INDENT for the new entry (it would stack), but do give the
761        // displaced old-first entry an INDENT of its own so it stays
762        // at the right column.
763        let inserting_at_head = target_entry_pos < children.len()
764            && children[..target_entry_pos].iter().all(|c| {
765                c.as_node()
766                    .map_or(true, |n| n.kind() != SyntaxKind::SEQUENCE_ENTRY)
767            });
768        if inserting_at_head && parent_value_has_leading_indent(&self.0) {
769            let old_first_indent = fresh_token(SyntaxKind::INDENT, &indentation);
770            self.0.splice_children(
771                insert_at..insert_at,
772                vec![new_entry.into(), old_first_indent.into()],
773            );
774            return;
775        }
776
777        self.0.splice_children(
778            insert_at..insert_at,
779            vec![indent_token.into(), new_entry.into()],
780        );
781    }
782
783    /// Replace the item at `index` with a new value.
784    ///
785    /// Returns `true` if the index was in bounds and the item was replaced,
786    /// `false` if `index >= len()`.
787    ///
788    /// Mutates in place despite `&self` (see crate docs on interior mutability).
789    pub fn set(&self, index: usize, value: impl crate::AsYaml) -> bool {
790        let children: Vec<_> = self.0.children_with_tokens().collect();
791
792        let Some(i) = nth_entry_index(&self.0, SyntaxKind::SEQUENCE_ENTRY, index) else {
793            return false;
794        };
795        let node = children[i].as_node().expect("entry index names a node");
796        let new_entry = rebuild_entry_with_value(node, &value);
797
798        // Replace the old SEQUENCE_ENTRY with the new one
799        self.0.splice_children(i..i + 1, vec![new_entry.into()]);
800        true
801    }
802
803    /// Drop the INDENT that separated the entry just removed at child
804    /// position `i` from its sibling.
805    ///
806    /// For a non-first entry that INDENT sits before it, left over from the
807    /// previous entry's NEWLINE. For the first, the one *after* it would
808    /// become a leading INDENT inside the SEQUENCE and stack with the parent
809    /// VALUE's (`  ` + `  ` -> `    `), shifting the new first entry a level
810    /// in. A flow sequence has no such separators.
811    fn detach_orphaned_indent(
812        &self,
813        children: &[rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<Lang>>],
814        i: usize,
815    ) {
816        if self.is_flow_style() {
817            return;
818        }
819        let (neighbour, range) = if i > 0 {
820            (children.get(i - 1), (i - 1)..i)
821        } else {
822            (children.get(i + 1), i..(i + 1))
823        };
824        let is_indent =
825            neighbour.is_some_and(|c| c.as_token().is_some_and(|t| t.kind() == SyntaxKind::INDENT));
826        if is_indent {
827            self.0.splice_children(range, vec![]);
828        }
829    }
830
831    /// Remove the item at `index`, returning its value.
832    ///
833    /// Returns `Some(value)` if the index was in bounds, `None` otherwise.
834    ///
835    /// Mutates in place despite `&self` (see crate docs on interior mutability).
836    pub fn remove(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
837        // Capture the value before removing so we can return it
838        let removed_value = self.get(index);
839
840        // Use children_with_tokens() since splice_children() expects those indices
841        let children: Vec<_> = self.0.children_with_tokens().collect();
842
843        // The child position of the entry at `index`, which is not the index
844        // itself: INDENT tokens sit between entries.
845        let i = children
846            .iter()
847            .enumerate()
848            .filter(|(_, c)| {
849                c.as_node()
850                    .is_some_and(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
851            })
852            .map(|(i, _)| i)
853            .nth(index)?;
854        let is_last = !children.iter().skip(i + 1).any(|c| {
855            c.as_node()
856                .is_some_and(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
857        });
858
859        // Remove the entry first, then the INDENT that
860        // separated it from a sibling. Doing them as two
861        // separate single-child splices sidesteps a
862        // rowan iteration quirk where a multi-child
863        // splice can skip elements mid-iteration.
864        //
865        // For non-first entries the INDENT sits right
866        // before this entry (the separator after the
867        // previous entry's NEWLINE); for the first
868        // entry any INDENT is a top-level formatting
869        // one we leave alone.
870        self.0.splice_children(i..(i + 1), vec![]);
871        self.detach_orphaned_indent(&children, i);
872
873        if !self.is_flow_style() && is_last && i > 0 && mapping_entry_is_last_in_mapping(&self.0) {
874            // Removed the last entry of a block sequence
875            // that itself terminates its enclosing mapping.
876            // Strip trailing whitespace/newline off the new
877            // last entry so we don't emit a stray blank
878            // line at the end of the document.
879            //
880            // When the enclosing MAPPING_ENTRY has a
881            // following sibling, the new-last-entry's
882            // NEWLINE is still needed as the separator
883            // between mapping entries -- do not touch it.
884            if let Some(prev_entry) = children[..i].iter().rev().find_map(|c| {
885                c.as_node()
886                    .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
887            }) {
888                strip_trailing_blank_tokens(prev_entry);
889            }
890        }
891        // If we just drained the last entry from a block
892        // sequence under a key, collapse the placeholder
893        // scaffold to `key: []` so re-parse still finds
894        // the (now-empty) sequence at that key.
895        if self.is_empty() {
896            collapse_empty_child_sequence_in_parent(&self.0);
897        }
898        removed_value
899    }
900
901    /// Check if this sequence is in flow style [item1, item2]
902    pub fn is_flow_style(&self) -> bool {
903        has_child_token(&self.0, |k| k == SyntaxKind::LEFT_BRACKET)
904    }
905
906    /// Remove and return the last item in this sequence.
907    ///
908    /// Returns `None` if the sequence is empty.
909    ///
910    /// Mutates in place despite `&self` (see crate docs on interior mutability).
911    pub fn pop(&self) -> Option<crate::as_yaml::YamlNode> {
912        let len = self.len();
913        if len == 0 {
914            return None;
915        }
916        let removed = self.remove(len - 1);
917
918        debug_assert_eq!(
919            self.len(),
920            len - 1,
921            "pop() invariant: remove() did not reduce length"
922        );
923
924        removed
925    }
926
927    /// Remove all items from this sequence.
928    ///
929    /// Mutates in place despite `&self` (see crate docs on interior mutability).
930    pub fn clear(&self) {
931        // Remove items from the beginning to avoid recalculating indices
932        // Use a safety counter to prevent infinite loops
933        let initial_len = self.len();
934        for _ in 0..initial_len {
935            let current_len = self.len();
936            if current_len == 0 {
937                break;
938            }
939            // Always remove the first item
940            let removed = self.remove(0);
941            debug_assert!(
942                removed.is_some(),
943                "clear() invariant: remove(0) returned None"
944            );
945            debug_assert_eq!(
946                self.len(),
947                current_len - 1,
948                "clear() invariant: remove(0) did not reduce length"
949            );
950        }
951    }
952
953    crate::nodes::ast_node_spans!("sequence");
954}
955
956impl Default for Sequence {
957    fn default() -> Self {
958        Self::new()
959    }
960}
961
962// Iterator trait implementations for Sequence
963
964impl<'a> IntoIterator for &'a Sequence {
965    type Item = crate::as_yaml::YamlNode;
966    type IntoIter = Box<dyn Iterator<Item = crate::as_yaml::YamlNode> + 'a>;
967
968    fn into_iter(self) -> Self::IntoIter {
969        Box::new(self.values())
970    }
971}
972
973impl AsYaml for Sequence {
974    fn as_node(&self) -> Option<&SyntaxNode> {
975        Some(&self.0)
976    }
977
978    fn kind(&self) -> YamlKind {
979        YamlKind::Sequence
980    }
981
982    fn build_content(
983        &self,
984        builder: &mut rowan::GreenNodeBuilder,
985        indent: usize,
986        _flow_context: bool,
987    ) -> bool {
988        builder.start_node(SyntaxKind::SEQUENCE.into());
989        crate::as_yaml::copy_node_content_with_indent(builder, &self.0, indent);
990        builder.finish_node();
991        self.0
992            .last_token()
993            .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
994    }
995
996    fn is_inline(&self) -> bool {
997        ValueNode::is_inline(self)
998    }
999}
1000#[cfg(test)]
1001mod tests {
1002    use crate::yaml::YamlFile;
1003    use std::str::FromStr;
1004
1005    /// `Sequence::new_pending_block` serializes as `[]` so an empty sequence
1006    /// survives a round-trip, and turns into a block sequence once it has an
1007    /// item. Before this it rendered as `key:` followed by blank space, which
1008    /// reparses as null: the empty sequence was silently lost.
1009    #[test]
1010    fn pending_block_sequence_round_trips_and_becomes_block() {
1011        use crate::yaml::Sequence;
1012        use crate::Document;
1013        use std::str::FromStr;
1014
1015        let doc = Document::from_str("a: 1\n").unwrap();
1016        doc.as_mapping()
1017            .unwrap()
1018            .set("s", Sequence::new_pending_block());
1019        let text = doc.to_string();
1020        assert_eq!(text, "a: 1\ns: []\n");
1021
1022        let reparsed = Document::from_str(&text).unwrap();
1023        let value = reparsed.as_mapping().and_then(|m| m.get("s")).unwrap();
1024        assert!(
1025            value.as_sequence().is_some(),
1026            "empty sequence must survive as a sequence"
1027        );
1028
1029        let doc = Document::from_str("a: 1\n").unwrap();
1030        let root = doc.as_mapping().unwrap();
1031        root.set("s", Sequence::new_pending_block());
1032        let nested = root.get("s").unwrap();
1033        nested.as_sequence().unwrap().push("x");
1034        assert_eq!(doc.to_string(), "a: 1\ns:\n  - x\n");
1035    }
1036
1037    /// A `[]` that was in the source keeps its flow style when an item is
1038    /// added; only a pending-block placeholder is rewritten.
1039    #[test]
1040    fn parsed_flow_sequence_stays_flow_when_filled() {
1041        use crate::yaml::Sequence;
1042        use crate::Document;
1043        use std::str::FromStr;
1044
1045        let doc = Document::from_str("a: 1\n").unwrap();
1046        let root = doc.as_mapping().unwrap();
1047        root.set("s", Sequence::new_flow());
1048        let nested = root.get("s").unwrap();
1049        nested.as_sequence().unwrap().push("x");
1050        assert_eq!(doc.to_string(), "a: 1\ns: [x]\n");
1051    }
1052
1053    /// `set_path` builds sequence intermediates as pending-block sequences,
1054    /// so an indexed path produces readable block YAML.
1055    #[test]
1056    fn set_path_builds_block_sequence_intermediates() {
1057        use crate::path::YamlPath;
1058        use crate::Document;
1059        use std::str::FromStr;
1060
1061        let doc = Document::from_str("name: test\n").unwrap();
1062        doc.try_set_path("a.b[0]", "v").unwrap();
1063        assert_eq!(doc.to_string(), "name: test\na:\n  b:\n    - v\n");
1064    }
1065
1066    /// `extend_with` must produce exactly what the same number of `push`
1067    /// calls would, for every sequence shape the padding path can meet.
1068    #[test]
1069    fn extend_with_matches_a_push_loop() {
1070        use crate::yaml::Document;
1071        use std::str::FromStr;
1072
1073        for src in [
1074            "s:\n  - a\n",
1075            "s: []\n",
1076            "s: [1]\n",
1077            "s:\n  - x\n  - y\n",
1078            "s:\n    - deep\n",
1079            "s:\n  - a\nafter: kept\n",
1080        ] {
1081            for count in 0..6usize {
1082                let pushed = {
1083                    let doc = Document::from_str(src).unwrap();
1084                    let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
1085                    for _ in 0..count {
1086                        seq.push(crate::scalar::ScalarValue::null());
1087                    }
1088                    doc.to_string()
1089                };
1090                let extended = {
1091                    let doc = Document::from_str(src).unwrap();
1092                    let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
1093                    seq.extend_with(crate::scalar::ScalarValue::null(), count);
1094                    doc.to_string()
1095                };
1096                assert_eq!(extended, pushed, "src={src:?} count={count}");
1097            }
1098        }
1099    }
1100
1101    #[test]
1102    fn test_push_into_empty_flow_sequence_reshapes_to_block() {
1103        // Regression: `push` on an empty flow sequence `[]` used to append
1104        // the block entry after the `]`, producing `seq: []  - item1\n`.
1105        // A `[]` the source contained keeps its flow style, so the item goes
1106        // inside the brackets.
1107        use crate::path::YamlPath;
1108        use crate::Document;
1109        let doc = Document::from_str("seq: []").unwrap();
1110        let seq = doc
1111            .try_get_path("seq")
1112            .unwrap()
1113            .as_sequence()
1114            .unwrap()
1115            .clone();
1116        seq.push("item1");
1117        assert_eq!(doc.to_string(), "seq: [item1]");
1118    }
1119
1120    #[test]
1121    fn test_push_into_empty_flow_sequence_nested_indent() {
1122        use crate::path::YamlPath;
1123        use crate::Document;
1124        let doc = Document::from_str("a:\n  seq: []\n").unwrap();
1125        let seq = doc
1126            .try_get_path("a.seq")
1127            .unwrap()
1128            .as_sequence()
1129            .unwrap()
1130            .clone();
1131        seq.push("item1");
1132        assert_eq!(doc.to_string(), "a:\n  seq: [item1]\n");
1133    }
1134
1135    #[test]
1136    fn test_push_deeply_nested_block_sequence_inherits_indent() {
1137        // Regression: `push` fell back to a 2-space INDENT for the new entry
1138        // because the sequence has no top-level INDENT (single entry) and its
1139        // one entry starts with DASH, not WHITESPACE. The correct column lives
1140        // on the parent VALUE's INDENT (the one before the SEQUENCE node).
1141        use crate::path::YamlPath;
1142        use crate::Document;
1143        let doc = Document::from_str("a:\n  b:\n    c:\n      - existing\n").unwrap();
1144        let seq = doc
1145            .try_get_path("a.b.c")
1146            .unwrap()
1147            .as_sequence()
1148            .unwrap()
1149            .clone();
1150        seq.push("new_item");
1151        assert_eq!(
1152            doc.to_string(),
1153            "a:\n  b:\n    c:\n      - existing\n      - new_item\n"
1154        );
1155    }
1156
1157    #[test]
1158    fn test_push_into_empty_sequence_under_mapping_placeholder() {
1159        // Regression: `mapping.set(k, Sequence::new())` produces the
1160        // placeholder shape `k:\n  \n` with an INDENT hint in the parent
1161        // VALUE. A follow-up `push` used to double the indent (4 spaces)
1162        // and leave a stray trailing newline.
1163        use crate::{Document, Sequence};
1164        let doc = Document::from_str("existing: value\n").unwrap();
1165        let mapping = doc.as_mapping().unwrap();
1166        mapping.set("items", Sequence::new());
1167        let items = mapping.get_sequence("items").unwrap();
1168        items.push("apple");
1169        items.push("banana");
1170        assert_eq!(
1171            doc.to_string(),
1172            "existing: value\nitems:\n  - apple\n  - banana\n"
1173        );
1174    }
1175
1176    #[test]
1177    fn test_implicit_null_item_shares_indexes_with_set_and_remove() {
1178        use crate::Document;
1179        let doc = Document::from_str("- a\n- \n- c\n").unwrap();
1180        let seq = doc.as_sequence().unwrap();
1181        assert_eq!(seq.len(), 3);
1182        assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
1183        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "");
1184        assert_eq!(seq.get(2).unwrap().as_scalar().unwrap().as_string(), "c");
1185
1186        assert!(seq.set(1, "x"));
1187        assert_eq!(doc.to_string(), "- a\n- x\n- c\n");
1188
1189        let doc = Document::from_str("- a\n- \n- c\n").unwrap();
1190        let seq = doc.as_sequence().unwrap();
1191        let removed = seq.remove(1);
1192        assert_eq!(
1193            removed.and_then(|n| n.as_scalar().map(|s| s.as_string())),
1194            Some(String::new())
1195        );
1196        assert_eq!(seq.len(), 2);
1197        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "c");
1198
1199        let doc = Document::from_str("- a\n-\n- c\n").unwrap();
1200        let seq = doc.as_sequence().unwrap();
1201        assert_eq!(seq.len(), 3);
1202        assert!(seq.set(1, "x"));
1203        assert_eq!(doc.to_string(), "- a\n- x\n- c\n");
1204    }
1205
1206    #[test]
1207    fn test_flow_sequence_implicit_null_shares_indexes() {
1208        use crate::debug::{roundtrip_ok, validate_tree};
1209        use crate::{AsYaml, Document};
1210
1211        // `[a, , c]` is three entries per YAML spec; the middle one is an
1212        // implicit null. Accessors must agree with mutators on the index
1213        // set, matching block-sequence behavior.
1214        let doc = Document::from_str("[a, , c]").unwrap();
1215        let seq = doc.as_sequence().unwrap();
1216        assert_eq!(seq.len(), 3);
1217        assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
1218        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "");
1219        assert_eq!(seq.get(2).unwrap().as_scalar().unwrap().as_string(), "c");
1220        validate_tree(doc.as_node().unwrap()).unwrap();
1221        roundtrip_ok(doc.as_node().unwrap()).unwrap();
1222
1223        assert!(seq.set(1, "x"));
1224        assert_eq!(doc.to_string(), "[a, x, c]");
1225
1226        let doc = Document::from_str("[a, , c]").unwrap();
1227        let seq = doc.as_sequence().unwrap();
1228        let removed = seq.remove(1);
1229        assert_eq!(
1230            removed.and_then(|n| n.as_scalar().map(|s| s.as_string())),
1231            Some(String::new())
1232        );
1233        assert_eq!(seq.len(), 2);
1234        assert_eq!(doc.to_string(), "[a, c]");
1235    }
1236
1237    #[test]
1238    fn test_flow_sequence_trailing_comma_is_not_extra_entry() {
1239        use crate::Document;
1240
1241        // `[a, b,]` has two entries: the trailing comma is a terminator,
1242        // not a separator introducing a null entry.
1243        let doc = Document::from_str("[a, b,]").unwrap();
1244        let seq = doc.as_sequence().unwrap();
1245        assert_eq!(seq.len(), 2);
1246        assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
1247        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "b");
1248        assert_eq!(doc.to_string(), "[a, b,]");
1249    }
1250
1251    #[test]
1252    fn test_flow_sequence_only_nulls() {
1253        use crate::debug::{roundtrip_ok, validate_tree};
1254        use crate::{AsYaml, Document};
1255
1256        // `[,]` is one null entry. `[,,]` is two.
1257        let doc = Document::from_str("[,]").unwrap();
1258        assert_eq!(doc.as_sequence().unwrap().len(), 1);
1259        validate_tree(doc.as_node().unwrap()).unwrap();
1260        roundtrip_ok(doc.as_node().unwrap()).unwrap();
1261
1262        let doc = Document::from_str("[,,]").unwrap();
1263        assert_eq!(doc.as_sequence().unwrap().len(), 2);
1264        validate_tree(doc.as_node().unwrap()).unwrap();
1265        roundtrip_ok(doc.as_node().unwrap()).unwrap();
1266    }
1267
1268    #[test]
1269    fn test_sequence_items_tagged_node() {
1270        // Tagged scalars inside sequences were previously skipped by items() because
1271        // TAGGED_NODE was not listed in the kind filter.
1272        let yaml = "- !custom foo\n- !custom bar\n- plain\n";
1273        let parsed = YamlFile::from_str(yaml).unwrap();
1274
1275        let doc = parsed.document().unwrap();
1276        let seq = doc.as_sequence().unwrap();
1277        assert_eq!(
1278            seq.items().count(),
1279            3,
1280            "Tagged scalars should be included in items()"
1281        );
1282        // values() should also return tagged scalars (cast as Scalar YamlValues)
1283        assert_eq!(
1284            seq.values().count(),
1285            3,
1286            "Tagged scalars should be included in values()"
1287        );
1288    }
1289
1290    #[test]
1291    fn test_sequence_set_tagged_node() {
1292        // Sequence::set() was missing TAGGED_NODE from its kind filter, so
1293        // replacing a tagged-scalar item would leave the original tag+value in place
1294        // and insert the new value alongside it.
1295        let yaml = "- !custom foo\n- bar\n";
1296        let parsed = YamlFile::from_str(yaml).unwrap();
1297        let doc = parsed.document().unwrap();
1298        let seq = doc.as_sequence().unwrap();
1299
1300        seq.set(0, "replaced");
1301
1302        let values: Vec<_> = seq.values().collect();
1303        assert_eq!(values.len(), 2);
1304        assert_eq!(
1305            values[0].as_scalar().map(|s| s.as_string()),
1306            Some("replaced".to_string())
1307        );
1308        assert_eq!(
1309            values[1].as_scalar().map(|s| s.as_string()),
1310            Some("bar".to_string())
1311        );
1312    }
1313
1314    #[test]
1315    fn test_sequence_set_alias() {
1316        let yaml = "colors:\n  - *red\n  - keep\n";
1317        let parsed = YamlFile::from_str(yaml).unwrap();
1318        let doc = parsed.document().unwrap();
1319        let mapping = doc.as_mapping().unwrap();
1320        let colors_node = mapping.get("colors").unwrap();
1321        let seq = colors_node.as_sequence().unwrap();
1322
1323        assert!(seq.set(0, "blue"));
1324        assert_eq!(parsed.to_string(), "colors:\n  - blue\n  - keep\n");
1325
1326        let first = seq.get(0).unwrap();
1327        assert_eq!(
1328            first.as_scalar().map(|s| s.as_string()),
1329            Some("blue".to_string())
1330        );
1331    }
1332
1333    #[test]
1334    fn test_sequence_operations() {
1335        let yaml = "- item1\n- item2";
1336        let parsed = YamlFile::from_str(yaml).unwrap();
1337
1338        let doc = parsed.document().expect("expected a document");
1339        let seq = doc.as_sequence().expect("expected a sequence");
1340
1341        // Test push
1342        seq.push("item3");
1343        let values: Vec<_> = seq.values().collect();
1344        assert_eq!(values.len(), 3);
1345        assert_eq!(
1346            values[2].as_scalar().map(|s| s.as_string()),
1347            Some("item3".to_string())
1348        );
1349
1350        // Test insert
1351        seq.insert(0, "item0");
1352        let values: Vec<_> = seq.values().collect();
1353        assert_eq!(values.len(), 4);
1354        assert_eq!(
1355            values[0].as_scalar().map(|s| s.as_string()),
1356            Some("item0".to_string())
1357        );
1358    }
1359
1360    // Iterator tests
1361
1362    #[test]
1363    fn test_sequence_into_iterator() {
1364        use crate::Document;
1365        let text = "items:\n  - apple\n  - banana\n  - cherry";
1366        let doc = Document::from_str(text).unwrap();
1367        let mapping = doc.as_mapping().unwrap();
1368        let sequence = mapping.get_sequence("items").unwrap();
1369
1370        // Test that we can use for loops directly
1371        let mut items = Vec::new();
1372        for value in &sequence {
1373            if let Some(scalar) = value.as_scalar() {
1374                items.push(scalar.to_string());
1375            }
1376        }
1377
1378        assert_eq!(items.len(), 3);
1379        assert_eq!(items[0], "apple");
1380        assert_eq!(items[1], "banana");
1381        assert_eq!(items[2], "cherry");
1382    }
1383
1384    #[test]
1385    fn test_sequence_into_iterator_count() {
1386        use crate::Document;
1387        let text = "[1, 2, 3, 4, 5]";
1388        let doc = Document::from_str(text).unwrap();
1389        let sequence = doc.as_sequence().unwrap();
1390
1391        let count = (&sequence).into_iter().count();
1392        assert_eq!(count, 5);
1393    }
1394
1395    #[test]
1396    fn test_sequence_iterator_map() {
1397        use crate::Document;
1398        let text = "numbers: [1, 2, 3]";
1399        let doc = Document::from_str(text).unwrap();
1400        let mapping = doc.as_mapping().unwrap();
1401        let sequence = mapping.get_sequence("numbers").unwrap();
1402
1403        // Map to strings
1404        let strings: Vec<_> = (&sequence)
1405            .into_iter()
1406            .filter_map(|v| v.as_scalar().map(|s| s.to_string()))
1407            .collect();
1408
1409        assert_eq!(strings, vec!["1", "2", "3"]);
1410    }
1411
1412    #[test]
1413    fn test_empty_sequence_iterator() {
1414        use crate::Document;
1415        let text = "items: []";
1416        let doc = Document::from_str(text).unwrap();
1417        let mapping = doc.as_mapping().unwrap();
1418        let sequence = mapping.get_sequence("items").unwrap();
1419
1420        let count = (&sequence).into_iter().count();
1421        assert_eq!(count, 0);
1422    }
1423
1424    // Tests from sequence_operations_test.rs
1425
1426    #[test]
1427    fn test_sequence_push_single() {
1428        use crate::Document;
1429        let original = r#"team:
1430  - Alice
1431  - Bob"#;
1432
1433        let doc = Document::from_str(original).unwrap();
1434        let mapping = doc.as_mapping().unwrap();
1435        let team = mapping.get_sequence("team").unwrap();
1436        team.push("Charlie");
1437
1438        let expected = r#"team:
1439  - Alice
1440  - Bob
1441  - Charlie"#;
1442        assert_eq!(doc.to_string(), expected);
1443    }
1444
1445    #[test]
1446    fn test_sequence_push_multiple() {
1447        use crate::Document;
1448        let original = r#"team:
1449  - Alice
1450  - Bob"#;
1451
1452        let doc = Document::from_str(original).unwrap();
1453        let mapping = doc.as_mapping().unwrap();
1454        let team = mapping.get_sequence("team").unwrap();
1455        team.push("Charlie");
1456        team.push("Diana");
1457
1458        let expected = r#"team:
1459  - Alice
1460  - Bob
1461  - Charlie
1462  - Diana"#;
1463        assert_eq!(doc.to_string(), expected);
1464    }
1465
1466    #[test]
1467    fn test_sequence_set_item() {
1468        use crate::Document;
1469        let original = r#"team:
1470  - Alice
1471  - Bob
1472  - Charlie"#;
1473
1474        let doc = Document::from_str(original).unwrap();
1475        let mapping = doc.as_mapping().unwrap();
1476        let team = mapping.get_sequence("team").unwrap();
1477        team.set(1, "Robert");
1478
1479        let expected = r#"team:
1480  - Alice
1481  - Robert
1482  - Charlie"#;
1483        assert_eq!(doc.to_string(), expected);
1484    }
1485
1486    #[test]
1487    fn test_multiple_sequences() {
1488        use crate::Document;
1489        let original = r#"team:
1490  - Alice
1491  - Bob
1492
1493scores:
1494  - 95
1495  - 87"#;
1496
1497        let doc = Document::from_str(original).unwrap();
1498        let mapping = doc.as_mapping().unwrap();
1499        let team = mapping.get_sequence("team").unwrap();
1500        team.push("Charlie");
1501        let scores = mapping.get_sequence("scores").unwrap();
1502        scores.push(92);
1503        scores.set(0, 100);
1504
1505        let expected = r#"team:
1506  - Alice
1507  - Bob
1508  - Charlie
1509
1510scores:
1511  - 100
1512  - 87
1513  - 92"#;
1514        assert_eq!(doc.to_string(), expected);
1515    }
1516
1517    #[test]
1518    fn test_nested_structure_with_sequences() {
1519        use crate::Document;
1520        let original = r#"config:
1521  enabled: true
1522  retries: 3
1523  servers:
1524    - host1
1525    - host2"#;
1526
1527        let doc = Document::from_str(original).unwrap();
1528        let mapping = doc.as_mapping().unwrap();
1529        let config = mapping.get_mapping("config").unwrap();
1530        config.set("enabled", false);
1531        config.set("retries", 5);
1532
1533        let servers = config.get_sequence("servers").unwrap();
1534        servers.push("host3");
1535        servers.set(0, "primary-host");
1536
1537        let expected = r#"config:
1538  enabled: false
1539  retries: 5
1540  servers:
1541    - primary-host
1542    - host2
1543    - host3"#;
1544        assert_eq!(doc.to_string(), expected);
1545    }
1546
1547    #[test]
1548    fn test_sequence_len_and_is_empty() {
1549        use crate::Document;
1550        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1551        let mapping = doc.as_mapping().unwrap();
1552        let seq = mapping.get_sequence("items").unwrap();
1553
1554        assert_eq!(seq.len(), 3);
1555        assert!(!seq.is_empty());
1556
1557        let empty_doc = Document::from_str("items: []").unwrap();
1558        let empty_mapping = empty_doc.as_mapping().unwrap();
1559        let empty_seq = empty_mapping.get_sequence("items").unwrap();
1560
1561        assert_eq!(empty_seq.len(), 0);
1562        assert!(empty_seq.is_empty());
1563    }
1564
1565    #[test]
1566    fn test_sequence_get() {
1567        use crate::Document;
1568        let doc = Document::from_str("items:\n  - first\n  - second\n  - third").unwrap();
1569        let mapping = doc.as_mapping().unwrap();
1570        let seq = mapping.get_sequence("items").unwrap();
1571
1572        assert_eq!(seq.get(0).unwrap().to_string(), "first");
1573        assert_eq!(seq.get(1).unwrap().to_string(), "second");
1574        assert_eq!(seq.get(2).unwrap().to_string(), "third");
1575        assert!(seq.get(3).is_none());
1576    }
1577
1578    #[test]
1579    fn test_sequence_first_and_last() {
1580        use crate::Document;
1581        let doc = Document::from_str("items:\n  - first\n  - middle\n  - last").unwrap();
1582        let mapping = doc.as_mapping().unwrap();
1583        let seq = mapping.get_sequence("items").unwrap();
1584
1585        assert_eq!(seq.first().unwrap().to_string(), "first");
1586        assert_eq!(seq.last().unwrap().to_string(), "last");
1587
1588        let empty_doc = Document::from_str("items: []").unwrap();
1589        let empty_mapping = empty_doc.as_mapping().unwrap();
1590        let empty_seq = empty_mapping.get_sequence("items").unwrap();
1591
1592        assert!(empty_seq.first().is_none());
1593        assert!(empty_seq.last().is_none());
1594    }
1595
1596    #[test]
1597    fn test_sequence_values_iterator() {
1598        use crate::Document;
1599        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1600        let mapping = doc.as_mapping().unwrap();
1601        let seq = mapping.get_sequence("items").unwrap();
1602
1603        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1604        assert_eq!(values, vec!["a", "b", "c"]);
1605    }
1606
1607    #[test]
1608    fn test_sequence_pop() {
1609        use crate::Document;
1610        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1611        let mapping = doc.as_mapping().unwrap();
1612        let seq = mapping.get_sequence("items").unwrap();
1613
1614        assert_eq!(seq.len(), 3);
1615        let popped = seq.pop().unwrap();
1616        assert_eq!(popped.to_string(), "c");
1617        assert_eq!(seq.len(), 2);
1618
1619        let popped = seq.pop().unwrap();
1620        assert_eq!(popped.to_string(), "b");
1621        assert_eq!(seq.len(), 1);
1622
1623        let expected = "items:\n  - a";
1624        assert_eq!(doc.to_string().trim_end(), expected);
1625
1626        let popped = seq.pop().unwrap();
1627        assert_eq!(popped.to_string(), "a");
1628        assert_eq!(seq.len(), 0);
1629        assert!(seq.pop().is_none());
1630    }
1631
1632    #[test]
1633    fn test_sequence_clear() {
1634        use crate::Document;
1635        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1636        let mapping = doc.as_mapping().unwrap();
1637        let seq = mapping.get_sequence("items").unwrap();
1638
1639        assert_eq!(seq.len(), 3);
1640        seq.clear();
1641        assert_eq!(seq.len(), 0);
1642        assert!(seq.is_empty());
1643    }
1644
1645    #[test]
1646    fn test_sequence_get_with_nested_values() {
1647        use crate::Document;
1648        let doc = Document::from_str(
1649            r#"items:
1650  - simple
1651  - {key: value}
1652  - [nested, list]"#,
1653        )
1654        .unwrap();
1655        let mapping = doc.as_mapping().unwrap();
1656        let seq = mapping.get_sequence("items").unwrap();
1657
1658        assert_eq!(seq.len(), 3);
1659        assert!(seq.get(0).unwrap().is_scalar());
1660        assert!(seq.get(1).unwrap().is_mapping());
1661        assert!(seq.get(2).unwrap().is_sequence());
1662    }
1663
1664    #[test]
1665    fn test_flow_sequence_len_and_is_empty() {
1666        use crate::Document;
1667        let doc = Document::from_str("items: [a, b, c]").unwrap();
1668        let mapping = doc.as_mapping().unwrap();
1669        let seq = mapping.get_sequence("items").unwrap();
1670
1671        assert_eq!(seq.len(), 3);
1672        assert!(!seq.is_empty());
1673
1674        let empty_doc = Document::from_str("items: []").unwrap();
1675        let empty_mapping = empty_doc.as_mapping().unwrap();
1676        let empty_seq = empty_mapping.get_sequence("items").unwrap();
1677
1678        assert_eq!(empty_seq.len(), 0);
1679        assert!(empty_seq.is_empty());
1680    }
1681
1682    #[test]
1683    fn test_flow_sequence_get() {
1684        use crate::Document;
1685        let doc = Document::from_str("items: [first, second, third]").unwrap();
1686        let mapping = doc.as_mapping().unwrap();
1687        let seq = mapping.get_sequence("items").unwrap();
1688
1689        assert_eq!(seq.get(0).unwrap().to_string(), "first");
1690        assert_eq!(seq.get(1).unwrap().to_string(), "second");
1691        assert_eq!(seq.get(2).unwrap().to_string(), "third");
1692        assert!(seq.get(3).is_none());
1693    }
1694
1695    #[test]
1696    fn test_flow_sequence_first_and_last() {
1697        use crate::Document;
1698        let doc = Document::from_str("items: [first, middle, last]").unwrap();
1699        let mapping = doc.as_mapping().unwrap();
1700        let seq = mapping.get_sequence("items").unwrap();
1701
1702        assert_eq!(seq.first().unwrap().to_string(), "first");
1703        assert_eq!(seq.last().unwrap().to_string(), "last");
1704    }
1705
1706    #[test]
1707    fn test_flow_sequence_values_iterator() {
1708        use crate::Document;
1709        let doc = Document::from_str("items: [a, b, c]").unwrap();
1710        let mapping = doc.as_mapping().unwrap();
1711        let seq = mapping.get_sequence("items").unwrap();
1712
1713        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1714        assert_eq!(values, vec!["a", "b", "c"]);
1715    }
1716
1717    #[test]
1718    fn test_flow_sequence_remove_middle() {
1719        use crate::Document;
1720        let doc = Document::from_str("items: [a, b, c]").unwrap();
1721        let mapping = doc.as_mapping().unwrap();
1722        let seq = mapping.get_sequence("items").unwrap();
1723
1724        assert_eq!(seq.len(), 3);
1725        let removed = seq.remove(1);
1726        assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
1727        assert_eq!(seq.len(), 2);
1728
1729        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1730        assert_eq!(values, vec!["a", "c"]);
1731    }
1732
1733    #[test]
1734    fn test_flow_sequence_remove_first() {
1735        use crate::Document;
1736        let doc = Document::from_str("items: [a, b, c]").unwrap();
1737        let mapping = doc.as_mapping().unwrap();
1738        let seq = mapping.get_sequence("items").unwrap();
1739
1740        assert_eq!(seq.len(), 3);
1741        let removed = seq.remove(0);
1742        assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
1743        assert_eq!(seq.len(), 2);
1744
1745        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1746        assert_eq!(values, vec!["b", "c"]);
1747    }
1748
1749    #[test]
1750    fn test_flow_sequence_remove_last() {
1751        use crate::Document;
1752        let doc = Document::from_str("items: [a, b, c]").unwrap();
1753        let mapping = doc.as_mapping().unwrap();
1754        let seq = mapping.get_sequence("items").unwrap();
1755
1756        assert_eq!(seq.len(), 3);
1757        let removed = seq.remove(2);
1758        assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
1759        assert_eq!(seq.len(), 2);
1760
1761        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1762        assert_eq!(values, vec!["a", "b"]);
1763    }
1764
1765    #[test]
1766    fn test_flow_sequence_pop() {
1767        use crate::Document;
1768        let doc = Document::from_str("items: [a, b, c]").unwrap();
1769        let mapping = doc.as_mapping().unwrap();
1770        let seq = mapping.get_sequence("items").unwrap();
1771
1772        assert_eq!(seq.len(), 3);
1773        let popped = seq.pop().unwrap();
1774        assert_eq!(popped.to_string(), "c");
1775        assert_eq!(seq.len(), 2);
1776
1777        let popped = seq.pop().unwrap();
1778        assert_eq!(popped.to_string(), "b");
1779        assert_eq!(seq.len(), 1);
1780
1781        let popped = seq.pop().unwrap();
1782        assert_eq!(popped.to_string(), "a");
1783        assert_eq!(seq.len(), 0);
1784        assert!(seq.pop().is_none());
1785    }
1786
1787    #[test]
1788    fn test_flow_sequence_clear() {
1789        use crate::Document;
1790        let doc = Document::from_str("items: [a, b, c]").unwrap();
1791        let mapping = doc.as_mapping().unwrap();
1792        let seq = mapping.get_sequence("items").unwrap();
1793
1794        assert_eq!(seq.len(), 3);
1795        seq.clear();
1796        assert_eq!(seq.len(), 0);
1797        assert!(seq.is_empty());
1798    }
1799
1800    #[test]
1801    fn test_flow_sequence_with_whitespace() {
1802        use crate::Document;
1803        let doc = Document::from_str("items: [ a , b , c ]").unwrap();
1804        let mapping = doc.as_mapping().unwrap();
1805        let seq = mapping.get_sequence("items").unwrap();
1806
1807        assert_eq!(seq.len(), 3);
1808        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1809        assert_eq!(values, vec!["a", "b", "c"]);
1810    }
1811
1812    #[test]
1813    fn test_block_sequence_remove_middle() {
1814        use crate::Document;
1815        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1816        let mapping = doc.as_mapping().unwrap();
1817        let seq = mapping.get_sequence("items").unwrap();
1818
1819        assert_eq!(seq.len(), 3);
1820        let removed = seq.remove(1);
1821        assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
1822        assert_eq!(seq.len(), 2);
1823
1824        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1825        assert_eq!(values, vec!["a", "c"]);
1826    }
1827
1828    #[test]
1829    fn test_block_sequence_remove_first() {
1830        use crate::Document;
1831        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1832        let mapping = doc.as_mapping().unwrap();
1833        let seq = mapping.get_sequence("items").unwrap();
1834
1835        assert_eq!(seq.len(), 3);
1836        let removed = seq.remove(0);
1837        assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
1838        assert_eq!(seq.len(), 2);
1839
1840        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1841        assert_eq!(values, vec!["b", "c"]);
1842    }
1843
1844    #[test]
1845    fn test_block_sequence_remove_last() {
1846        use crate::Document;
1847        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1848        let mapping = doc.as_mapping().unwrap();
1849        let seq = mapping.get_sequence("items").unwrap();
1850
1851        assert_eq!(seq.len(), 3);
1852        let removed = seq.remove(2);
1853        assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
1854        assert_eq!(seq.len(), 2);
1855
1856        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1857        assert_eq!(values, vec!["a", "b"]);
1858    }
1859
1860    #[test]
1861    fn test_single_item_block_sequence_remove() {
1862        use crate::Document;
1863        let doc = Document::from_str("items:\n  - only").unwrap();
1864        let mapping = doc.as_mapping().unwrap();
1865        let seq = mapping.get_sequence("items").unwrap();
1866
1867        assert_eq!(seq.len(), 1);
1868        let removed = seq.remove(0);
1869        assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
1870        assert_eq!(seq.len(), 0);
1871    }
1872
1873    #[test]
1874    fn test_single_item_flow_sequence_remove() {
1875        use crate::Document;
1876        let doc = Document::from_str("items: [only]").unwrap();
1877        let mapping = doc.as_mapping().unwrap();
1878        let seq = mapping.get_sequence("items").unwrap();
1879
1880        assert_eq!(seq.len(), 1);
1881        let removed = seq.remove(0);
1882        assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
1883        assert_eq!(seq.len(), 0);
1884    }
1885
1886    #[test]
1887    fn test_flow_sequence_push() {
1888        use crate::Document;
1889        let doc = Document::from_str("items: [a, b]").unwrap();
1890        let mapping = doc.as_mapping().unwrap();
1891        let seq = mapping.get_sequence("items").unwrap();
1892
1893        assert_eq!(seq.len(), 2);
1894        seq.push("c");
1895        assert_eq!(seq.len(), 3);
1896
1897        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1898        assert_eq!(values, vec!["a", "b", "c"]);
1899    }
1900
1901    #[test]
1902    fn test_flow_sequence_push_multiple() {
1903        use crate::Document;
1904        let doc = Document::from_str("items: [a]").unwrap();
1905        let mapping = doc.as_mapping().unwrap();
1906        let seq = mapping.get_sequence("items").unwrap();
1907
1908        seq.push("b");
1909        seq.push("c");
1910        seq.push("d");
1911
1912        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1913        assert_eq!(values, vec!["a", "b", "c", "d"]);
1914    }
1915
1916    #[test]
1917    fn test_flow_sequence_set_item() {
1918        use crate::Document;
1919        let doc = Document::from_str("items: [a, b, c]").unwrap();
1920        let mapping = doc.as_mapping().unwrap();
1921        let seq = mapping.get_sequence("items").unwrap();
1922
1923        seq.set(1, "modified");
1924
1925        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1926        assert_eq!(values, vec!["a", "modified", "c"]);
1927    }
1928
1929    #[test]
1930    fn test_flow_sequence_insert_beginning() {
1931        use crate::Document;
1932        let doc = Document::from_str("items: [b, c]").unwrap();
1933        let mapping = doc.as_mapping().unwrap();
1934        let seq = mapping.get_sequence("items").unwrap();
1935
1936        seq.insert(0, "a");
1937
1938        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1939        assert_eq!(values, vec!["a", "b", "c"]);
1940    }
1941
1942    #[test]
1943    fn test_flow_sequence_insert_middle() {
1944        use crate::Document;
1945        let doc = Document::from_str("items: [a, c]").unwrap();
1946        let mapping = doc.as_mapping().unwrap();
1947        let seq = mapping.get_sequence("items").unwrap();
1948
1949        seq.insert(1, "b");
1950
1951        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1952        assert_eq!(values, vec!["a", "b", "c"]);
1953    }
1954
1955    #[test]
1956    fn test_flow_sequence_insert_end() {
1957        use crate::Document;
1958        let doc = Document::from_str("items: [a, b]").unwrap();
1959        let mapping = doc.as_mapping().unwrap();
1960        let seq = mapping.get_sequence("items").unwrap();
1961
1962        seq.insert(2, "c");
1963
1964        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1965        assert_eq!(values, vec!["a", "b", "c"]);
1966    }
1967
1968    #[test]
1969    fn test_block_sequence_push() {
1970        use crate::Document;
1971        let doc = Document::from_str("items:\n  - a\n  - b").unwrap();
1972        let mapping = doc.as_mapping().unwrap();
1973        let seq = mapping.get_sequence("items").unwrap();
1974
1975        assert_eq!(seq.len(), 2);
1976        seq.push("c");
1977        assert_eq!(seq.len(), 3);
1978
1979        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1980        assert_eq!(values, vec!["a", "b", "c"]);
1981    }
1982
1983    #[test]
1984    fn test_block_sequence_set_item() {
1985        use crate::Document;
1986        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1987        let mapping = doc.as_mapping().unwrap();
1988        let seq = mapping.get_sequence("items").unwrap();
1989
1990        seq.set(1, "modified");
1991
1992        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1993        assert_eq!(values, vec!["a", "modified", "c"]);
1994    }
1995
1996    #[test]
1997    fn test_block_sequence_insert_beginning() {
1998        use crate::Document;
1999        let doc = Document::from_str("items:\n  - b\n  - c").unwrap();
2000        let mapping = doc.as_mapping().unwrap();
2001        let seq = mapping.get_sequence("items").unwrap();
2002
2003        seq.insert(0, "a");
2004
2005        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
2006        assert_eq!(values, vec!["a", "b", "c"]);
2007    }
2008
2009    #[test]
2010    fn test_block_sequence_insert_middle() {
2011        use crate::Document;
2012        let doc = Document::from_str("items:\n  - a\n  - c").unwrap();
2013        let mapping = doc.as_mapping().unwrap();
2014        let seq = mapping.get_sequence("items").unwrap();
2015
2016        seq.insert(1, "b");
2017
2018        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
2019        assert_eq!(values, vec!["a", "b", "c"]);
2020    }
2021
2022    #[test]
2023    fn test_block_sequence_insert_end() {
2024        use crate::Document;
2025        let doc = Document::from_str("items:\n  - a\n  - b").unwrap();
2026        let mapping = doc.as_mapping().unwrap();
2027        let seq = mapping.get_sequence("items").unwrap();
2028
2029        seq.insert(2, "c");
2030
2031        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
2032        assert_eq!(values, vec!["a", "b", "c"]);
2033    }
2034
2035    #[test]
2036    fn test_sequence_get_node() {
2037        let doc = YamlFile::from_str("items:\n  - alpha\n  - beta\n  - gamma")
2038            .unwrap()
2039            .document()
2040            .unwrap();
2041        let seq = doc.as_mapping().unwrap().get_sequence("items").unwrap();
2042
2043        assert_eq!(seq.len(), 3);
2044        assert!(seq.get(0).is_some());
2045        assert!(seq.get(1).is_some());
2046        assert!(seq.get(2).is_some());
2047        assert!(seq.get(3).is_none());
2048
2049        assert_eq!(
2050            seq.get(0).unwrap().as_scalar().unwrap().as_string(),
2051            "alpha"
2052        );
2053        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "beta");
2054        assert_eq!(
2055            seq.get(2).unwrap().as_scalar().unwrap().as_string(),
2056            "gamma"
2057        );
2058    }
2059
2060    #[test]
2061    fn test_sequence_set_with_nested_mapping() {
2062        use crate::path::YamlPath;
2063        use crate::Document;
2064
2065        let yaml_str = "items:\n  - name: first\n    value: 1\n  - name: second\n    value: 2\n";
2066        let doc = Document::from_str(yaml_str).unwrap();
2067
2068        let items_node = doc.try_get_path("items").unwrap();
2069        let items = items_node.as_sequence().unwrap();
2070
2071        assert_eq!(items.len(), 2);
2072        let first = items.get(0).unwrap();
2073        assert!(first.is_mapping());
2074
2075        items.set(0, "replaced");
2076        assert_eq!(
2077            doc.to_string(),
2078            "items:\n  - replaced\n  - name: second\n    value: 2\n"
2079        );
2080    }
2081}