lipgloss_list/lib.rs
1//! Package list allows you to build lists, as simple or complicated as you need.
2//!
3//! Simply, define a list with some items and set its rendering properties, like
4//! enumerator and styling:
5//!
6//! ```rust
7//! use lipgloss_list::{List, arabic};
8//!
9//! let groceries = List::new()
10//! .items(vec!["Bananas", "Barley", "Cashews", "Milk"])
11//! .enumerator(arabic);
12//!
13//! println!("{}", groceries);
14//! ```
15
16pub mod enumerator;
17
18use lipgloss::Style;
19use lipgloss_tree::{Children, Leaf, Node, Tree};
20use std::fmt;
21
22// Re-export enumerators for convenience
23pub use enumerator::{alphabet, arabic, asterisk, bullet, dash, roman, Enumerator, Indenter};
24
25/// Items represents the list items.
26pub type Items = Box<dyn Children>;
27
28/// StyleFunc is the style function that determines the style of an item.
29///
30/// It takes the list items and index of the list and determines the lipgloss
31/// Style to use for that index.
32///
33/// Example:
34///
35/// ```rust
36/// use lipgloss::{color::{LIST_ITEM_PRIMARY, LIST_ITEM_SECONDARY, STATUS_SUCCESS}, Style};
37/// use lipgloss_list::List;
38/// use lipgloss_tree::Children;
39///
40/// let style_func = |items: &dyn Children, i: usize| {
41/// match i {
42/// 0 => Style::new().foreground(LIST_ITEM_PRIMARY),
43/// 1 => Style::new().foreground(STATUS_SUCCESS),
44/// 2 => Style::new().foreground(LIST_ITEM_SECONDARY),
45/// _ => Style::new(),
46/// }
47/// };
48/// ```
49pub type StyleFunc = fn(&dyn Children, usize) -> Style;
50
51/// List represents a list of items that can be displayed. Lists can contain
52/// lists as items, they will be rendered as nested (sub)lists.
53///
54/// In fact, lists can contain anything as items, like Table or Tree.
55pub struct List {
56 tree: Tree,
57}
58
59impl List {
60 /// Creates a new list with the given items.
61 ///
62 /// ```rust
63 /// use lipgloss_list::List;
64 ///
65 /// let alphabet = List::new()
66 /// .items(vec!["A", "B", "C", "D", "E", "F"]);
67 /// ```
68 ///
69 /// Items can be other lists, trees, tables, rendered markdown;
70 /// anything you want, really.
71 pub fn new() -> Self {
72 let mut tree = Tree::new();
73 tree = tree
74 .enumerator(bullet as lipgloss_tree::Enumerator)
75 .indenter(list_indenter);
76
77 Self { tree }
78 }
79
80 /// Creates a new list with initial items.
81 pub fn from_items(items: Vec<&str>) -> Self {
82 let mut list = Self::new();
83 for item in items {
84 list = list.item(item);
85 }
86 list
87 }
88
89 /// Returns whether this list is hidden.
90 pub fn hidden(&self) -> bool {
91 self.tree.hidden()
92 }
93
94 /// Hides this list.
95 /// If this list is hidden, it will not be shown when rendered.
96 pub fn hide(mut self, hide: bool) -> Self {
97 self.tree = self.tree.hide(hide);
98 self
99 }
100
101 /// Sets the start and end offset for the list.
102 ///
103 /// Example:
104 /// ```rust
105 /// use lipgloss_list::List;
106 ///
107 /// let l = List::new()
108 /// .items(vec!["A", "B", "C", "D"])
109 /// .offset(1, 1);
110 ///
111 /// println!("{}", l);
112 /// // • B
113 /// // • C
114 /// ```
115 pub fn offset(mut self, start: usize, end: usize) -> Self {
116 self.tree = self.tree.offset(start, end);
117 self
118 }
119
120 /// Returns the value of this node.
121 pub fn value(&self) -> String {
122 self.tree.value()
123 }
124
125 /// Sets the enumerator style for all enumerators.
126 ///
127 /// To set the enumerator style conditionally based on the item value or index,
128 /// use `enumerator_style_func`.
129 pub fn enumerator_style(mut self, style: Style) -> Self {
130 self.tree = self.tree.enumerator_style(style);
131 self
132 }
133
134 /// Sets the enumerator style function for the list items.
135 ///
136 /// Use this to conditionally set different styles based on the current items,
137 /// sibling items, or index values (i.e. even or odd).
138 ///
139 /// Example:
140 ///
141 /// ```rust
142 /// use lipgloss::{color::LIST_ENUMERATOR, Style};
143 /// use lipgloss_list::List;
144 ///
145 /// let l = List::new()
146 /// .enumerator_style_func(|_items, i| {
147 /// if i % 2 == 0 {
148 /// Style::new().foreground(LIST_ENUMERATOR)
149 /// } else {
150 /// Style::new()
151 /// }
152 /// });
153 /// ```
154 pub fn enumerator_style_func(mut self, f: StyleFunc) -> Self {
155 self.tree = self
156 .tree
157 .enumerator_style_func(f as lipgloss_tree::StyleFunc);
158 self
159 }
160
161 /// Sets the indenter implementation. This is used to change the way
162 /// the tree is indented. The default indenter places no indentation
163 /// for lists (unlike trees).
164 ///
165 /// You can define your own indenter.
166 ///
167 /// ```rust
168 /// use lipgloss_tree::Children;
169 /// use lipgloss_list::List;
170 ///
171 /// fn arrow_indenter(_children: &dyn Children, _index: usize) -> String {
172 /// "→ ".to_string()
173 /// }
174 ///
175 /// let l = List::new()
176 /// .items(vec!["Foo", "Bar", "Baz"])
177 /// .indenter(arrow_indenter);
178 /// ```
179 pub fn indenter(mut self, indenter: Indenter) -> Self {
180 self.tree = self.tree.indenter(indenter as lipgloss_tree::Indenter);
181 self
182 }
183
184 /// Sets the item style for all items.
185 ///
186 /// To set the item style conditionally based on the item value or index,
187 /// use `item_style_func`.
188 pub fn item_style(mut self, style: Style) -> Self {
189 self.tree = self.tree.item_style(style);
190 self
191 }
192
193 /// Sets the item style function for the list items.
194 ///
195 /// Use this to conditionally set different styles based on the current items,
196 /// sibling items, or index values.
197 ///
198 /// Example:
199 ///
200 /// ```rust
201 /// use lipgloss::{color::LIST_ITEM_PRIMARY, Style};
202 /// use lipgloss_list::List;
203 ///
204 /// let l = List::new()
205 /// .item_style_func(|_items, i| {
206 /// if i == 0 {
207 /// Style::new().foreground(LIST_ITEM_PRIMARY)
208 /// } else {
209 /// Style::new()
210 /// }
211 /// });
212 /// ```
213 pub fn item_style_func(mut self, f: StyleFunc) -> Self {
214 self.tree = self.tree.item_style_func(f as lipgloss_tree::StyleFunc);
215 self
216 }
217
218 /// Appends an item to the list.
219 ///
220 /// ```rust
221 /// use lipgloss_list::List;
222 ///
223 /// let l = List::new()
224 /// .item("Foo")
225 /// .item("Bar")
226 /// .item("Baz");
227 /// ```
228 pub fn item(mut self, item: &str) -> Self {
229 let leaf: Box<dyn Node> = Box::new(Leaf::new(item, false));
230 self.tree = self.tree.add_child(leaf);
231 self
232 }
233
234 /// Appends a generic node to the list.
235 ///
236 /// This allows adding any `lipgloss_tree::Node` implementation directly.
237 pub fn item_node(mut self, node: Box<dyn Node>) -> Self {
238 self.tree = self.tree.add_child(node);
239 self
240 }
241
242 /// Appends another list as a sublist (nested list).
243 pub fn item_list(mut self, list: List) -> Self {
244 // `Tree` implements `Node`, so we can add it directly as a child.
245 let node: Box<dyn Node> = Box::new(list.tree);
246 self.tree = self.tree.add_child(node);
247 self
248 }
249
250 /// Appends multiple items to the list.
251 ///
252 /// ```rust
253 /// use lipgloss_list::List;
254 ///
255 /// let l = List::new()
256 /// .items(vec!["Foo", "Bar", "Baz"]);
257 /// ```
258 pub fn items(mut self, items: Vec<&str>) -> Self {
259 for item in items {
260 self = self.item(item);
261 }
262 self
263 }
264
265 /// Sets the list enumerator.
266 ///
267 /// There are several predefined enumerators:
268 /// • alphabet
269 /// • arabic
270 /// • bullet
271 /// • dash
272 /// • roman
273 /// • asterisk
274 ///
275 /// Or, define your own.
276 ///
277 /// ```rust
278 /// use lipgloss_list::{List, arabic};
279 ///
280 /// let l = List::new()
281 /// .items(vec!["Foo", "Bar", "Baz"])
282 /// .enumerator(arabic);
283 /// ```
284 pub fn enumerator(mut self, enumerator: Enumerator) -> Self {
285 self.tree = self
286 .tree
287 .enumerator(enumerator as lipgloss_tree::Enumerator);
288 self
289 }
290}
291
292impl Default for List {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl fmt::Display for List {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "{}", self.tree)
301 }
302}
303
304/// List indenter for nested content within list items.
305///
306/// CRITICAL SPACING REQUIREMENTS:
307/// - Regular sublists MUST have 2-space indentation before bullets
308/// - Example: "• Parent\n • Child" (2 spaces before child bullet)
309/// - Golden files expect this 2-space pattern for proper visual hierarchy
310///
311/// GO IMPLEMENTATION DISCREPANCY:
312/// - Go's list.New() sets indenter to return single space: `return " "`
313/// - However, Go's output produces 2-space indentation for sublists
314/// - This suggests Go has additional logic that doubles indentation for nested lists
315/// - Our implementation achieves correct output by returning 2 spaces directly
316///
317/// TREE-IN-LIST ISSUE:
318/// - When trees are nested in lists (via item_node), this 2-space indenter
319/// can cause extra spacing after tree symbols
320/// - Tree symbols have padding_right(1) built-in, expecting 1-space list_indenter
321/// - With 2-space list_indenter: tree gets "├── content" instead of "├── content"
322/// - This affects golden_complex_sublist test specifically
323///
324/// USAGE:
325/// - Called by tree renderer for each child's indentation
326/// - Applied to continuation lines and nested content
327/// - NOT applied to the enumerator/bullet itself
328fn list_indenter(_children: &dyn Children, _index: usize) -> String {
329 " ".to_string() // 2 spaces required for sublist visual hierarchy
330}
331
332// Go API compatibility aliases
333pub use List as ListType;
334
335/// Creates a new list.
336pub fn new() -> List {
337 List::new()
338}
339
340/// Creates a new list with items.
341pub fn from_items(items: Vec<&str>) -> List {
342 List::from_items(items)
343}