Skip to main content

yaml_edit/nodes/
sequence.rs

1use super::replaced_value;
2use super::{
3    fresh_token, insert_children, nth_entry_index, replace_children, starts_a_flow_collection,
4    trivia, Lang, SyntaxNode,
5};
6use crate::as_yaml::{build_in, AsYaml, FlowQuoting, YamlKind};
7use crate::lex::SyntaxKind;
8use crate::nodes::empty_collection::{self, render_empty};
9use crate::nodes::flow_quoting;
10use crate::nodes::mapping::{drop_tagged_anchor, kept_anchor_token};
11use crate::nodes::trivia::Slot;
12use crate::yaml::ValueNode;
13use rowan::ast::AstNode;
14use rowan::GreenNodeBuilder;
15
16ast_node!(Sequence, SEQUENCE, "A YAML sequence (list)");
17
18/// True if `node` cannot be rendered as a block collection: any
19/// flow ancestor forbids it.
20pub(crate) fn must_render_flow(node: &SyntaxNode) -> bool {
21    // A pending-block placeholder is `{}` in the text but is headed for
22    // block style, so it does not force flow on what nests inside it (see
23    // `Mapping::new_pending_block`).
24    node.ancestors().skip(1).any(|p| {
25        matches!(p.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE)
26            && starts_a_flow_collection(&p)
27            && !empty_collection::is_block_placeholder(&p)
28    })
29}
30
31/// Rebuild `entry` with `value` in place of the node it currently holds,
32/// keeping everything else the entry carried.
33fn rebuild_entry_with_value(
34    entry: &SyntaxNode,
35    value: &impl crate::AsYaml,
36    flow: Option<FlowQuoting>,
37    new_anchor: Option<&str>,
38) -> SyntaxNode {
39    let entry_children: Vec<_> = entry.children_with_tokens().collect();
40    // Track the column while copying the entry, so the new value's later
41    // lines line up under its first.
42    let dash_column = crate::column::node_column(entry);
43    let mut column = dash_column;
44
45    // Build a new SEQUENCE_ENTRY with the new value using AsYaml
46    let mut builder = GreenNodeBuilder::new();
47    builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
48
49    // The old item's anchor goes on the new one, so aliases to it still
50    // resolve; failing that `new_anchor`, or the anchor the new item had
51    // where it was copied from. An alias cannot carry an anchor.
52    let old_anchor = kept_anchor_token(entry, value).map(|t| t.text().to_string());
53    let anchor = old_anchor
54        .clone()
55        .or_else(|| new_anchor.map(str::to_string))
56        .or_else(|| {
57            value
58                .value_node()
59                .filter(|_| value.kind() != YamlKind::Alias)
60                .and_then(|n| crate::nodes::mapping::source_anchor_text(&n))
61        });
62    // An anchor before the old item is rewritten along with it, unless a
63    // comment separates the two, in which case it stays where it is.
64    let item = entry_item(entry);
65    let item_at = entry_children
66        .iter()
67        .position(|c| c.as_node() == item.as_ref())
68        .unwrap_or(entry_children.len());
69    let outer_anchor_at = entry_children[..item_at]
70        .iter()
71        .position(|c| c.as_token().is_some_and(|t| t.kind() == SyntaxKind::ANCHOR));
72    let rewrite_anchor = outer_anchor_at.map_or(true, |at| {
73        entry_children[at + 1..item_at].iter().all(|c| {
74            c.as_token().is_some_and(|t| {
75                matches!(
76                    t.kind(),
77                    SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::INDENT
78                )
79            })
80        })
81    });
82    let anchor = anchor.filter(|_| rewrite_anchor);
83    // A block collection goes on the line after its anchor.
84    let block_collection = flow.is_none()
85        && !value.is_inline()
86        && matches!(value.kind(), YamlKind::Mapping | YamlKind::Sequence);
87
88    let mut value_inserted = false;
89    let mut value_ends_with_newline = false;
90    let mut old_ended_line = false;
91    let mut after_dash = false;
92    // A value ending in NEWLINE terminates the entry itself, so the old
93    // terminator after it, up to this index, is dropped.
94    let mut skip_until = 0;
95
96    for (i, entry_child) in entry_children.iter().enumerate() {
97        if i < skip_until {
98            continue;
99        }
100        if rewrite_anchor && outer_anchor_at.is_some_and(|at| (at..item_at).contains(&i)) {
101            continue;
102        }
103        match entry_child {
104            rowan::NodeOrToken::Node(n)
105                if matches!(
106                    n.kind(),
107                    SyntaxKind::SCALAR
108                        | SyntaxKind::MAPPING
109                        | SyntaxKind::SEQUENCE
110                        | SyntaxKind::ALIAS
111                        | SyntaxKind::TAGGED_NODE
112                ) =>
113            {
114                // A multi-line value (a nested mapping, say) ends the
115                // entry's line, which the new value has to as well.
116                old_ended_line = crate::nodes::trivia::ends_line(n);
117                if !value_inserted {
118                    // A bare `-` item is DASH then a zero-width NULL
119                    // scalar, so a written value needs the space that
120                    // was never there: without it `set` gives `-x`.
121                    if after_dash {
122                        builder.token(SyntaxKind::WHITESPACE.into(), " ");
123                        column += 1;
124                    }
125                    if let Some(anchor) = &anchor {
126                        builder.token(SyntaxKind::ANCHOR.into(), anchor);
127                        if block_collection {
128                            column = dash_column + 2;
129                            builder.token(SyntaxKind::NEWLINE.into(), "\n");
130                            builder.token(SyntaxKind::INDENT.into(), &" ".repeat(column));
131                        } else {
132                            // The value's later lines stay where they would
133                            // be without the anchor.
134                            builder.token(SyntaxKind::WHITESPACE.into(), " ");
135                        }
136                    }
137                    value_ends_with_newline =
138                        build_entry_value(&mut builder, value, dash_column, column, flow);
139                    value_inserted = true;
140                    if value_ends_with_newline {
141                        skip_until = terminator_end(&entry_children, i + 1);
142                    }
143                }
144                after_dash = false;
145            }
146            // A SEQUENCE_ENTRY holds no other node; see the implicit-null
147            // invariant in nodes/mod.rs.
148            rowan::NodeOrToken::Node(n) => {
149                panic!("unexpected {:?} in a SEQUENCE_ENTRY", n.kind())
150            }
151            rowan::NodeOrToken::Token(t) => {
152                // A block collection on the dash's line is indented by the
153                // white space after the dash, where a tab is not allowed.
154                let text = if after_dash
155                    && t.kind() == SyntaxKind::WHITESPACE
156                    && block_collection
157                    && anchor.is_none()
158                {
159                    t.text().replace('\t', " ")
160                } else {
161                    t.text().to_string()
162                };
163                builder.token(t.kind().into(), &text);
164                column = crate::column::column_after(column, &text);
165                after_dash = t.kind() == SyntaxKind::DASH;
166            }
167        }
168    }
169
170    if old_ended_line && !value_ends_with_newline {
171        builder.token(SyntaxKind::NEWLINE.into(), "\n");
172    }
173
174    builder.finish_node();
175    let new_entry = SyntaxNode::new_root_mut(builder.finish());
176    if old_anchor.is_some() {
177        drop_tagged_anchor(&new_entry);
178    }
179    new_entry
180}
181
182/// The item of a SEQUENCE_ENTRY.
183fn entry_item(entry: &SyntaxNode) -> Option<SyntaxNode> {
184    entry.children().find(|n| {
185        matches!(
186            n.kind(),
187            SyntaxKind::SCALAR
188                | SyntaxKind::MAPPING
189                | SyntaxKind::SEQUENCE
190                | SyntaxKind::ALIAS
191                | SyntaxKind::TAGGED_NODE
192        )
193    })
194}
195
196/// A block sequence entry holding `value`, its dash at `column`, ending
197/// in a line break.
198fn block_entry(value: &impl crate::AsYaml, column: usize) -> SyntaxNode {
199    let mut builder = GreenNodeBuilder::new();
200    builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
201    builder.token(SyntaxKind::DASH.into(), "-");
202    builder.token(SyntaxKind::WHITESPACE.into(), " ");
203    if !build_entry_value(&mut builder, value, column, column + 2, None) {
204        builder.token(SyntaxKind::NEWLINE.into(), "\n");
205    }
206    builder.finish_node();
207    SyntaxNode::new_root_mut(builder.finish())
208}
209
210/// Emit `value` as the item of an entry whose dash is at `dash_column`,
211/// the value itself starting at `column`. A flow sequence can't hold a
212/// block collection, so one is written in flow form there. Returns
213/// whether the content ends with a NEWLINE.
214fn build_entry_value(
215    builder: &mut GreenNodeBuilder,
216    value: &impl crate::AsYaml,
217    dash_column: usize,
218    column: usize,
219    flow: Option<FlowQuoting>,
220) -> bool {
221    // An inline value (a block scalar, say) takes the column of the entry
222    // and indents its body past it; a block collection takes the column its
223    // later lines align to, as in MappingEntry.
224    let indent = |v: &dyn crate::AsYaml| if v.is_inline() { dash_column } else { column };
225    // The flow form is inline, so it is shifted by dash_column when built;
226    // its continuation lines are given relative to that.
227    match flow.and_then(|q| crate::nodes::mapping::flow_form(value, 2, q)) {
228        Some(flow_value) => build_in(&flow_value, builder, indent(&flow_value), flow),
229        None => match value
230            .value_node()
231            .and_then(|n| Some((block_scalar_of(&n)?, n)))
232        {
233            Some((scalar, node)) => build_block_scalar(builder, &node, &scalar, dash_column + 2),
234            None => build_in(value, builder, indent(value), flow),
235        },
236    }
237}
238
239/// The block scalar `node` is, or holds under its tag.
240fn block_scalar_of(node: &SyntaxNode) -> Option<SyntaxNode> {
241    let scalar = match node.kind() {
242        SyntaxKind::TAGGED_NODE => node.first_child()?,
243        _ => node.clone(),
244    };
245    crate::nodes::is_block_scalar(&scalar).then_some(scalar)
246}
247
248/// Copy `node`, the block scalar `scalar` or a tagged node holding it,
249/// with the body shifted to `target`. Returns whether the copy ends with a
250/// line break.
251fn build_block_scalar(
252    builder: &mut GreenNodeBuilder,
253    node: &SyntaxNode,
254    scalar: &SyntaxNode,
255    target: usize,
256) -> bool {
257    let delta = crate::nodes::mapping::block_scalar_body_shift(scalar, target);
258    builder.start_node(node.kind().into());
259    let ends_with_newline = crate::as_yaml::copy_node_content_reindent(builder, node, delta);
260    builder.finish_node();
261    ends_with_newline
262}
263
264/// The index just past the NEWLINE that ends `children[start..]`, if only
265/// whitespace precedes it, else `start`.
266fn terminator_end(children: &[rowan::SyntaxElement<Lang>], start: usize) -> usize {
267    let blank_or_newline = |c: &rowan::SyntaxElement<Lang>| {
268        c.as_token()
269            .map(|t| t.kind())
270            .filter(|k| matches!(k, SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE))
271    };
272    children[start..]
273        .iter()
274        .map_while(blank_or_newline)
275        .position(|k| k == SyntaxKind::NEWLINE)
276        .map_or(start, |offset| start + offset + 1)
277}
278
279impl Sequence {
280    /// Iterate over items in this sequence as raw syntax nodes.
281    ///
282    /// For most use cases prefer [`values`](Self::values) which returns
283    /// [`YamlNode`](crate::as_yaml::YamlNode)s.
284    pub(crate) fn items(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
285        self.entries().filter_map(|e| entry_item(&e))
286    }
287
288    /// Iterate over items in this sequence as [`YamlNode`](crate::as_yaml::YamlNode)s.
289    ///
290    /// Items that cannot be wrapped as a `YamlNode` are silently skipped.
291    pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
292        self.items()
293            .filter_map(crate::as_yaml::YamlNode::from_syntax)
294    }
295
296    /// Returns the number of items in this sequence.
297    pub fn len(&self) -> usize {
298        self.items().count()
299    }
300
301    /// Returns `true` if this sequence contains no items.
302    pub fn is_empty(&self) -> bool {
303        self.items().next().is_none()
304    }
305
306    /// Get the item at `index` as a [`YamlNode`](crate::as_yaml::YamlNode).
307    ///
308    /// Returns `None` if `index` is out of bounds.
309    pub fn get(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
310        self.items()
311            .nth(index)
312            .and_then(crate::as_yaml::YamlNode::from_syntax)
313    }
314
315    /// Get the first item in this sequence, or `None` if empty.
316    pub fn first(&self) -> Option<crate::as_yaml::YamlNode> {
317        self.get(0)
318    }
319
320    /// Get the last item in this sequence, or `None` if empty.
321    pub fn last(&self) -> Option<crate::as_yaml::YamlNode> {
322        let len = self.len();
323        if len == 0 {
324            None
325        } else {
326            self.get(len - 1)
327        }
328    }
329}
330
331impl Sequence {
332    /// Create a new empty sequence.
333    pub fn new() -> Self {
334        Self::new_pending_block()
335    }
336
337    /// An empty sequence that renders as `[]` and stays flow when filled.
338    ///
339    /// Use this for a sequence that is meant to be flow-style in the output,
340    /// including one that is left empty.
341    pub fn new_flow() -> Self {
342        let mut builder = GreenNodeBuilder::new();
343        builder.start_node(SyntaxKind::SEQUENCE.into());
344        builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
345        builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
346        builder.finish_node();
347        Sequence(SyntaxNode::new_root_mut(builder.finish()))
348    }
349
350    /// An empty sequence that becomes a block sequence once an item is added.
351    ///
352    /// YAML has no block syntax for an empty sequence: block collections are
353    /// written as their entries, so with none left there is nothing to write,
354    /// and a bare `key:` reads back as null. An empty sequence is therefore
355    /// `[]` whatever its eventual style, and block-vs-flow only becomes a
356    /// real choice once there is an item.
357    ///
358    /// This renders as exactly `[]`, plus a zero-width token that marks it as
359    /// destined for block style. [`Sequence::push`] rewrites it into block
360    /// form when the first item arrives; a `[]` that came from the source has
361    /// no such marker and keeps its flow style.
362    ///
363    /// A block sequence that loses its last item to `remove` or `clear` is
364    /// written the same way, marker included.
365    ///
366    /// The marker lives only in the tree. Serializing and re-parsing yields
367    /// an ordinary `[]`, which stays flow, since YAML has nowhere to record
368    /// the intent.
369    pub fn new_pending_block() -> Self {
370        let mut builder = GreenNodeBuilder::new();
371        builder.start_node(SyntaxKind::SEQUENCE.into());
372        builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
373        // Zero-width sentinel: renders as nothing, so this is still exactly
374        // `[]` in the text, but marks the sequence as one that should become
375        // block when it gets its first item.
376        builder.token(SyntaxKind::WHITESPACE.into(), "");
377        builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
378        builder.finish_node();
379        Sequence(SyntaxNode::new_root_mut(builder.finish()))
380    }
381
382    /// Append `count` copies of `value` to the end of the sequence.
383    ///
384    /// Equivalent to calling [`push`](Self::push) `count` times, but splices
385    /// the new entries in one edit rather than one each. Since a single edit
386    /// is linear in the sequence's length (see `push`), doing them one at a
387    /// time is quadratic: padding a sequence out to 1023 entries took 89ms
388    /// that way and under 2ms this way.
389    pub(crate) fn extend_with(&self, value: impl crate::AsYaml + Clone, count: usize) {
390        if count == 0 {
391            return;
392        }
393        // The first entry settles the questions push answers about style and
394        // scaffolding -- flow vs block, the column, the placeholder newline.
395        // Let it, then add the rest after it in one splice.
396        self.push(value.clone());
397        let Some(remaining) = count.checked_sub(1).filter(|n| *n > 0) else {
398            return;
399        };
400        if self.is_flow_style() {
401            for _ in 0..remaining {
402                self.push(value.clone());
403            }
404            return;
405        }
406
407        let pushed = self
408            .0
409            .last_child()
410            .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
411            .expect("push added an entry");
412        let open = !trivia::ends_with_line_break(&pushed);
413        trivia::terminate(&pushed);
414        let column = trivia::entry_column(&self.0);
415        let mut inserts: Vec<rowan::NodeOrToken<SyntaxNode, _>> = Vec::new();
416        let mut last = None;
417        for _ in 0..remaining {
418            if column > 0 {
419                inserts.push(fresh_token(SyntaxKind::INDENT, &" ".repeat(column)).into());
420            }
421            let entry = block_entry(&value, column);
422            inserts.push(entry.clone().into());
423            last = Some(entry);
424        }
425        if open {
426            trivia::unterminate_last(&last.expect("at least one entry is added"));
427        }
428        let at = pushed.index() + 1;
429        insert_children(&self.0, at, inserts);
430    }
431
432    /// Add an item to the end of the sequence.
433    ///
434    /// Mutates in place despite `&self` (see crate docs on interior mutability).
435    ///
436    /// # Performance
437    ///
438    /// Runs in time linear in the sequence's current length, so building a
439    /// sequence one push at a time is quadratic overall. Almost all of the
440    /// cost is in rowan's `splice_children`, which walks every child of the
441    /// sequence and rebuilds its green node on each edit (see `rowan.md`);
442    /// this method reads only the tail of the child list and holds no child
443    /// handles across the edit.
444    ///
445    /// For a large sequence, build it with
446    /// [`SequenceBuilder`](crate::SequenceBuilder) or parse the YAML text in
447    /// one go, rather than pushing in a loop: the builder is linear, and
448    /// assembles those 8000 entries in under 2ms.
449    /// [`insert`](Self::insert) and [`remove`](Self::remove) are likewise
450    /// linear per call, and rather more costly than `push`.
451    pub fn push(&self, value: impl crate::AsYaml) {
452        self.attach(Slot::End, value);
453    }
454
455    /// Insert an item at a specific position.
456    ///
457    /// If `index` is out of bounds, the item is appended at the end.
458    /// This method always succeeds; it never returns an error.
459    ///
460    /// Mutates in place despite `&self` (see crate docs on interior mutability).
461    pub fn insert(&self, index: usize, value: impl crate::AsYaml) {
462        let slot = self.entries().nth(index).map_or(Slot::End, Slot::Before);
463        self.attach(slot, value);
464    }
465
466    /// Add an item holding `value` at `slot`.
467    fn attach(&self, slot: Slot, value: impl crate::AsYaml) {
468        // A placeholder from `Sequence::new_pending_block` turns into a real
469        // block sequence now that it has an item. A `[]` from the source is
470        // not marked and keeps its flow style; a placeholder nested inside a
471        // flow container stays flow to avoid mixed output.
472        empty_collection::convert_placeholder_to_block(&self.0);
473        if self.is_flow_style() {
474            trivia::attach_flow_entry(&self.0, slot, &self.flow_entry(value));
475            return;
476        }
477        let entry = block_entry(&value, trivia::entry_column(&self.0));
478        trivia::attach_entry(&self.0, slot, &entry);
479    }
480
481    /// A flow sequence entry holding `value`, for this sequence.
482    fn flow_entry(&self, value: impl crate::AsYaml) -> SyntaxNode {
483        let mut builder = GreenNodeBuilder::new();
484        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
485        // Should the value need several lines (to keep comments), they are
486        // indented past the entry holding this sequence, as for mappings.
487        let base = self
488            .0
489            .ancestors()
490            .find(|n| {
491                matches!(
492                    n.kind(),
493                    SyntaxKind::MAPPING_ENTRY | SyntaxKind::SEQUENCE_ENTRY
494                )
495            })
496            .map_or(0, |entry| crate::column::node_column(&entry));
497        let flow = Some(flow_quoting(&self.0));
498        build_entry_value(&mut builder, &value, base, base + 2, flow);
499        builder.finish_node();
500        SyntaxNode::new_root_mut(builder.finish())
501    }
502
503    /// Replace the item at `index` with a new value.
504    ///
505    /// Returns `true` if the index was in bounds and the item was replaced,
506    /// `false` if `index >= len()`.
507    ///
508    /// An anchor on the replaced item stays, on the new item, so aliases to
509    /// it still resolve. A node value that has an anchor of its own keeps
510    /// it only if the old item had none. The first alias to an anchor
511    /// deeper in the old item becomes a copy of the anchored node.
512    ///
513    /// Mutates in place despite `&self` (see crate docs on interior mutability).
514    pub fn set(&self, index: usize, value: impl crate::AsYaml) -> bool {
515        let Some(i) = nth_entry_index(&self.0, SyntaxKind::SEQUENCE_ENTRY, index) else {
516            return false;
517        };
518        let node = self
519            .0
520            .children_with_tokens()
521            .nth(i)
522            .and_then(|c| c.into_node())
523            .expect("entry index names a node");
524        // An alias cannot carry an anchor; the first alias to the old one
525        // gets a copy of the old item instead.
526        let kept = crate::nodes::mapping::kept_anchor_token(&node, &value);
527        crate::nodes::kept_aliases::keeping_aliases(&node, kept, || {
528            Some(self.replace_entry_at(i, &value, None))
529        });
530        true
531    }
532
533    /// Replace the item at `index` as [`set`](Self::set) does, putting
534    /// `anchor` on the new item, and return the new entry.
535    pub(crate) fn replace_item_under(
536        &self,
537        index: usize,
538        value: impl crate::AsYaml,
539        anchor: Option<&str>,
540    ) -> SyntaxNode {
541        let i =
542            nth_entry_index(&self.0, SyntaxKind::SEQUENCE_ENTRY, index).expect("index is in range");
543        self.replace_entry_at(i, &value, anchor)
544    }
545
546    /// Replace the entry at child index `i` with one holding `value`, and
547    /// return it. `anchor` goes on the new item if the old one had none.
548    fn replace_entry_at(
549        &self,
550        i: usize,
551        value: &impl crate::AsYaml,
552        anchor: Option<&str>,
553    ) -> SyntaxNode {
554        let children: Vec<_> = self.0.children_with_tokens().collect();
555        let node = children[i].as_node().expect("entry index names a node");
556        let flow = self.is_flow_style().then(|| flow_quoting(&self.0));
557        let dash_comment = match entry_item(node) {
558            Some(old) if flow.is_none() => {
559                replaced_value::remove_comment_after_item(&old);
560                replaced_value::take_dash_line_comment(node)
561            }
562            _ => None,
563        };
564        let new_entry = rebuild_entry_with_value(node, value, flow, anchor);
565
566        // Replace the old SEQUENCE_ENTRY with the new one
567        replace_children(&self.0, i..i + 1, vec![new_entry.into()]);
568        let new_entry = self
569            .0
570            .children_with_tokens()
571            .nth(i)
572            .and_then(|c| c.into_node());
573        let new_entry = new_entry.expect("the new entry was just spliced in");
574        replaced_value::settle_lines_after_block_scalar(&new_entry);
575        if let Some(comment) = dash_comment {
576            replaced_value::add_dash_line_comment(&new_entry, comment);
577        }
578        if let Some(item) = entry_item(&new_entry) {
579            crate::nodes::trivia::drop_space_before_empty(&item);
580        }
581        new_entry
582    }
583
584    /// Remove the item at `index`, returning its value.
585    ///
586    /// Returns `Some(value)` if the index was in bounds, `None` otherwise.
587    ///
588    /// The first alias to an anchor in the removed item becomes a copy of
589    /// the anchored node, so it and later aliases keep their value.
590    ///
591    /// Mutates in place despite `&self` (see crate docs on interior mutability).
592    pub fn remove(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
593        // Capture the value before removing so we can return it
594        let removed_value = self.get(index);
595        self.remove_entry(&self.entries().nth(index)?);
596        removed_value
597    }
598
599    /// The SEQUENCE_ENTRY nodes of this sequence.
600    fn entries(&self) -> impl Iterator<Item = SyntaxNode> {
601        self.0
602            .children()
603            .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
604    }
605
606    /// Remove `entry`, keeping aliases to anchors in it resolving.
607    fn remove_entry(&self, entry: &SyntaxNode) {
608        crate::nodes::kept_aliases::keeping_aliases(entry, None, || {
609            trivia::detach_entry(&self.0, entry);
610            None
611        });
612    }
613
614    /// Check if this sequence is in flow style [item1, item2]
615    pub fn is_flow_style(&self) -> bool {
616        starts_a_flow_collection(&self.0)
617    }
618
619    /// Remove and return the last item in this sequence.
620    ///
621    /// Returns `None` if the sequence is empty.
622    ///
623    /// Mutates in place despite `&self` (see crate docs on interior mutability).
624    pub fn pop(&self) -> Option<crate::as_yaml::YamlNode> {
625        self.remove(self.len().checked_sub(1)?)
626    }
627
628    /// Remove all items from this sequence.
629    ///
630    /// Mutates in place despite `&self` (see crate docs on interior mutability).
631    pub fn clear(&self) {
632        let line_break = self.entries().last().and_then(|e| trivia::line_break(&e));
633        // Entries are removed one by one, as keeping aliases resolving may
634        // rewrite a later item.
635        while let Some(entry) = self.entries().next() {
636            self.remove_entry(&entry);
637        }
638        render_empty(&self.0, line_break.as_deref());
639    }
640
641    crate::nodes::ast_node_spans!("sequence");
642}
643
644impl Default for Sequence {
645    fn default() -> Self {
646        Self::new()
647    }
648}
649
650// Iterator trait implementations for Sequence
651
652impl<'a> IntoIterator for &'a Sequence {
653    type Item = crate::as_yaml::YamlNode;
654    type IntoIter = Box<dyn Iterator<Item = crate::as_yaml::YamlNode> + 'a>;
655
656    fn into_iter(self) -> Self::IntoIter {
657        Box::new(self.values())
658    }
659}
660
661impl AsYaml for Sequence {
662    fn as_node(&self) -> Option<&SyntaxNode> {
663        Some(&self.0)
664    }
665
666    fn kind(&self) -> YamlKind {
667        YamlKind::Sequence
668    }
669
670    fn build_content(
671        &self,
672        builder: &mut rowan::GreenNodeBuilder,
673        indent: usize,
674        _flow_context: bool,
675    ) -> bool {
676        let is_flow = self.is_flow_style();
677        let mut delta =
678            indent as isize - crate::as_yaml::source_reindent_base(&self.0, is_flow) as isize;
679        if is_flow {
680            delta = crate::as_yaml::clear_parent_column(&self.0, indent, delta);
681        }
682        builder.start_node(SyntaxKind::SEQUENCE.into());
683        crate::as_yaml::copy_node_content_reindent(builder, &self.0, delta);
684        builder.finish_node();
685        self.0
686            .last_token()
687            .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
688    }
689
690    fn is_inline(&self) -> bool {
691        ValueNode::is_inline(self)
692    }
693}
694#[cfg(test)]
695mod tests {
696    use crate::debug::assert_cst_ok;
697    use crate::yaml::YamlFile;
698    use std::str::FromStr;
699
700    /// `extend_with` must produce exactly what the same number of `push`
701    /// calls would, for every sequence shape the padding path can meet.
702    #[test]
703    fn extend_with_matches_a_push_loop() {
704        use crate::yaml::Document;
705        use std::str::FromStr;
706
707        for src in [
708            "s:\n  - a\n",
709            "s: []\n",
710            "s: [1]\n",
711            "s:\n  - x\n  - y\n",
712            "s:\n    - deep\n",
713            "s:\n  - a\nafter: kept\n",
714            "s:\n  - a\n\nafter: kept\n",
715            "s:\n  - a",
716        ] {
717            for count in 0..6usize {
718                let pushed = {
719                    let doc = Document::from_str(src).unwrap();
720                    let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
721                    for _ in 0..count {
722                        seq.push(crate::scalar::ScalarValue::null());
723                        assert_cst_ok(&doc);
724                    }
725                    doc.to_string()
726                };
727                let extended = {
728                    let doc = Document::from_str(src).unwrap();
729                    let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
730                    seq.extend_with(crate::scalar::ScalarValue::null(), count);
731                    assert_cst_ok(&doc);
732                    doc.to_string()
733                };
734                assert_eq!(extended, pushed, "src={src:?} count={count}");
735            }
736        }
737    }
738
739    #[test]
740    fn test_sequence_items_tagged_node() {
741        // Tagged scalars inside sequences were previously skipped by items() because
742        // TAGGED_NODE was not listed in the kind filter.
743        let yaml = "- !custom foo\n- !custom bar\n- plain\n";
744        let parsed = YamlFile::from_str(yaml).unwrap();
745
746        let doc = parsed.document().unwrap();
747        let seq = doc.as_sequence().unwrap();
748        assert_eq!(
749            seq.items().count(),
750            3,
751            "Tagged scalars should be included in items()"
752        );
753        // values() should also return tagged scalars (cast as Scalar YamlValues)
754        assert_eq!(
755            seq.values().count(),
756            3,
757            "Tagged scalars should be included in values()"
758        );
759    }
760
761    // Iterator tests
762
763    // Tests from sequence_operations_test.rs
764
765    /// Sequence edits write the document's own line break, whichever it is.
766    #[test]
767    fn test_edits_write_document_line_breaks() {
768        type Edit = fn(&crate::Sequence);
769        let edits: [(&str, Edit, &str); 11] = [
770            ("- a\n", |s| s.push("b"), "- a\n- b\n"),
771            ("- a  # c\n", |s| s.push("b"), "- a  # c\n- b\n"),
772            ("- a\n", |s| s.insert(0, "b"), "- b\n- a\n"),
773            ("# h\n- a\n", |s| s.insert(0, "b"), "# h\n- b\n- a\n"),
774            (
775                "- a  # c\n- d\n",
776                |s| s.insert(1, "b"),
777                "- a  # c\n- b\n- d\n",
778            ),
779            ("- a\n", |s| s.extend_with("x", 3), "- a\n- x\n- x\n- x\n"),
780            (
781                "- a: 1\n  b: 2\n- c\n",
782                |s| assert!(s.set(0, 5)),
783                "- 5\n- c\n",
784            ),
785            (
786                "-\n  name: A\n  hr: 65\n-\n  name: B\n",
787                |s| assert!(s.set(0, "xval")),
788                "-\n  xval\n-\n  name: B\n",
789            ),
790            (
791                "- a\n- c\n",
792                |s| {
793                    assert!(s.set(
794                        0,
795                        crate::Document::from_str("x: 1\ny: 2\n")
796                            .unwrap()
797                            .as_mapping()
798                            .unwrap()
799                    ))
800                },
801                "- x: 1\n  y: 2\n- c\n",
802            ),
803            (
804                "- &a\n  - x\n- c\n",
805                |s| {
806                    assert!(s.set(
807                        0,
808                        crate::Document::from_str("x: 1\ny: 2\n")
809                            .unwrap()
810                            .as_mapping()
811                            .unwrap()
812                    ))
813                },
814                "- &a\n  x: 1\n  y: 2\n- c\n",
815            ),
816            (
817                "-\n  # n\n  a\n- c\n",
818                |s| {
819                    assert!(s.set(
820                        0,
821                        crate::Document::from_str("x: 1\ny: 2\n")
822                            .unwrap()
823                            .as_mapping()
824                            .unwrap()
825                    ))
826                },
827                "-\n  # n\n  x: 1\n  y: 2\n- c\n",
828            ),
829        ];
830        for line_break in ["\n", "\r\n", "\r"] {
831            for (source, edit, expected) in &edits {
832                let source = source.replace('\n', line_break);
833                let yaml = YamlFile::from_str(&source).unwrap();
834                edit(&yaml.document().unwrap().as_sequence().unwrap());
835                crate::debug::assert_edited(&yaml, &expected.replace('\n', line_break));
836            }
837        }
838    }
839}