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
18pub(crate) fn must_render_flow(node: &SyntaxNode) -> bool {
21 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
31fn 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 let dash_column = crate::column::node_column(entry);
43 let mut column = dash_column;
44
45 let mut builder = GreenNodeBuilder::new();
47 builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
48
49 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 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 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 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 old_ended_line = crate::nodes::trivia::ends_line(n);
117 if !value_inserted {
118 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 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 rowan::NodeOrToken::Node(n) => {
149 panic!("unexpected {:?} in a SEQUENCE_ENTRY", n.kind())
150 }
151 rowan::NodeOrToken::Token(t) => {
152 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
182fn 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
196fn 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
210fn 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 let indent = |v: &dyn crate::AsYaml| if v.is_inline() { dash_column } else { column };
225 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
239fn 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
248fn 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
264fn 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 pub(crate) fn items(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
285 self.entries().filter_map(|e| entry_item(&e))
286 }
287
288 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 pub fn len(&self) -> usize {
298 self.items().count()
299 }
300
301 pub fn is_empty(&self) -> bool {
303 self.items().next().is_none()
304 }
305
306 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 pub fn first(&self) -> Option<crate::as_yaml::YamlNode> {
317 self.get(0)
318 }
319
320 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 pub fn new() -> Self {
334 Self::new_pending_block()
335 }
336
337 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 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 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 pub(crate) fn extend_with(&self, value: impl crate::AsYaml + Clone, count: usize) {
390 if count == 0 {
391 return;
392 }
393 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 pub fn push(&self, value: impl crate::AsYaml) {
452 self.attach(Slot::End, value);
453 }
454
455 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 fn attach(&self, slot: Slot, value: impl crate::AsYaml) {
468 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 fn flow_entry(&self, value: impl crate::AsYaml) -> SyntaxNode {
483 let mut builder = GreenNodeBuilder::new();
484 builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
485 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 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 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 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 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_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 pub fn remove(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
593 let removed_value = self.get(index);
595 self.remove_entry(&self.entries().nth(index)?);
596 removed_value
597 }
598
599 fn entries(&self) -> impl Iterator<Item = SyntaxNode> {
601 self.0
602 .children()
603 .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
604 }
605
606 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 pub fn is_flow_style(&self) -> bool {
616 starts_a_flow_collection(&self.0)
617 }
618
619 pub fn pop(&self) -> Option<crate::as_yaml::YamlNode> {
625 self.remove(self.len().checked_sub(1)?)
626 }
627
628 pub fn clear(&self) {
632 let line_break = self.entries().last().and_then(|e| trivia::line_break(&e));
633 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
650impl<'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 #[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 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 assert_eq!(
755 seq.values().count(),
756 3,
757 "Tagged scalars should be included in values()"
758 );
759 }
760
761 #[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}