Skip to main content

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}