common/parser_tools/
list_grouper.rs1use crate::entities::ListStyle;
2use crate::types::EntityId;
3
4#[derive(Default)]
10pub struct ListGrouper {
11 active: Vec<Option<(EntityId, ListStyle, String, String)>>,
17}
18
19impl ListGrouper {
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn try_reuse(&mut self, style: &ListStyle, indent: u32) -> Option<EntityId> {
28 self.try_reuse_delim(style, indent, "", "")
29 }
30
31 pub fn try_reuse_delim(
35 &mut self,
36 style: &ListStyle,
37 indent: u32,
38 prefix: &str,
39 suffix: &str,
40 ) -> Option<EntityId> {
41 let idx = indent as usize;
42 self.active.truncate(idx + 1);
44 if let Some(Some((id, existing_style, existing_prefix, existing_suffix))) =
45 self.active.get(idx)
46 && existing_style == style
47 && existing_prefix == prefix
48 && existing_suffix == suffix
49 {
50 return Some(*id);
51 }
52 None
53 }
54
55 pub fn register(&mut self, id: EntityId, style: ListStyle, indent: u32) {
57 self.register_delim(id, style, indent, String::new(), String::new());
58 }
59
60 pub fn register_delim(
64 &mut self,
65 id: EntityId,
66 style: ListStyle,
67 indent: u32,
68 prefix: String,
69 suffix: String,
70 ) {
71 let idx = indent as usize;
72 while self.active.len() <= idx {
73 self.active.push(None);
74 }
75 self.active[idx] = Some((id, style, prefix, suffix));
76 }
77
78 pub fn reset(&mut self) {
80 self.active.clear();
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn first_item_returns_none() {
90 let mut g = ListGrouper::new();
91 assert!(g.try_reuse(&ListStyle::Decimal, 0).is_none());
92 }
93
94 #[test]
95 fn consecutive_same_style_reuses() {
96 let mut g = ListGrouper::new();
97 g.register(42, ListStyle::Decimal, 0);
98 assert_eq!(g.try_reuse(&ListStyle::Decimal, 0), Some(42));
99 }
100
101 #[test]
102 fn different_style_creates_new() {
103 let mut g = ListGrouper::new();
104 g.register(42, ListStyle::Decimal, 0);
105 assert!(g.try_reuse(&ListStyle::Disc, 0).is_none());
106 }
107
108 #[test]
109 fn different_indent_creates_new() {
110 let mut g = ListGrouper::new();
111 g.register(42, ListStyle::Decimal, 0);
112 assert!(g.try_reuse(&ListStyle::Decimal, 1).is_none());
113 }
114
115 #[test]
116 fn reset_clears_all() {
117 let mut g = ListGrouper::new();
118 g.register(42, ListStyle::Decimal, 0);
119 g.reset();
120 assert!(g.try_reuse(&ListStyle::Decimal, 0).is_none());
121 }
122
123 #[test]
124 fn nested_indent_resumes_outer() {
125 let mut g = ListGrouper::new();
126 g.register(10, ListStyle::Decimal, 0);
127 g.register(20, ListStyle::LowerAlpha, 1);
128 assert_eq!(g.try_reuse(&ListStyle::Decimal, 0), Some(10));
130 }
131
132 #[test]
133 fn nested_indent_different_style_creates_new() {
134 let mut g = ListGrouper::new();
135 g.register(10, ListStyle::Decimal, 0);
136 g.register(20, ListStyle::LowerAlpha, 1);
137 assert!(g.try_reuse(&ListStyle::Disc, 0).is_none());
139 }
140
141 #[test]
142 fn same_style_different_delimiter_creates_new() {
143 let mut g = ListGrouper::new();
145 g.register_delim(10, ListStyle::Decimal, 0, String::new(), ".".to_string());
146 assert!(g.try_reuse_delim(&ListStyle::Decimal, 0, "", ")").is_none());
147 }
148
149 #[test]
150 fn same_style_same_delimiter_reuses() {
151 let mut g = ListGrouper::new();
152 g.register_delim(10, ListStyle::Decimal, 0, "(".to_string(), ")".to_string());
153 assert_eq!(
154 g.try_reuse_delim(&ListStyle::Decimal, 0, "(", ")"),
155 Some(10)
156 );
157 }
158}