Skip to main content

tui_breadcrumb/
strategy.rs

1// ==============================================================================
2// Truncation Strategies
3// ==============================================================================
4
5//! Smart overflow and truncation strategies for breadcrumb trails.
6//!
7//! Terminal displays frequently have constrained horizontal space. When a breadcrumb path
8//! is longer than the available render width, a [`TruncateStrategy`] dictates how segments
9//! are condensed, abbreviated, or replaced with ellipsis indicators.
10
11/// Strategy determining how breadcrumb segments are collapsed when overflowing horizontal bounds.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum TruncateStrategy {
14    /// Collapses intermediate ancestor segments into an ellipsis while preserving
15    /// the root ancestor(s) and deepest active segment(s).
16    ///
17    /// Example: `Home ❯ ... ❯ src ❯ sparkline.rs`
18    Middle {
19        /// Minimum number of root/head segments to preserve on the left.
20        min_head_items: usize,
21        /// Minimum number of leaf/tail segments to preserve on the right.
22        min_tail_items: usize,
23        /// The ellipsis indicator string (default `"..."`).
24        ellipsis: String,
25    },
26
27    /// Collapses leftmost ancestor segments into an ellipsis while preserving
28    /// the deepest active leaf segments.
29    ///
30    /// Example: `... ❯ src ❯ sparkline.rs`
31    Start {
32        /// Minimum number of leaf/tail segments to preserve on the right.
33        min_tail_items: usize,
34        /// The ellipsis indicator string (default `"..."`).
35        ellipsis: String,
36    },
37
38    /// Collapses deepest leaf segments into an ellipsis while preserving
39    /// root ancestor segments (left-to-right priority).
40    ///
41    /// Example: `Home ❯ Projects ❯ ...`
42    End {
43        /// Minimum number of root/head segments to preserve on the left.
44        min_head_items: usize,
45        /// The ellipsis indicator string (default `"..."`).
46        ellipsis: String,
47    },
48
49    /// Progressively abbreviates ancestor item labels to their leading characters
50    /// before collapsing them into an ellipsis if space is still constrained.
51    ///
52    /// Example: `H ❯ P ❯ ratatui ❯ src ❯ sparkline.rs`
53    ShortenNames {
54        /// Target abbreviation character length for shortened ancestor labels (default 1).
55        max_abbrev_len: usize,
56        /// Number of deepest tail segments to leave unshortened.
57        preserve_tail_items: usize,
58        /// Fallback ellipsis indicator if the trail still overflows after shortening.
59        ellipsis: String,
60    },
61
62    /// Performs no smart condensation or ellipsis substitution; segments are clipped
63    /// at the boundary.
64    None,
65}
66
67impl Default for TruncateStrategy {
68    /// Defaults to [`TruncateStrategy::Middle`] with 1 head item, 2 tail items, and `...` ellipsis.
69    fn default() -> Self {
70        Self::middle()
71    }
72}
73
74impl TruncateStrategy {
75    /// Creates a default `Middle` strategy preserving 1 root item, 2 tail items, and `"..."` ellipsis.
76    #[must_use]
77    pub fn middle() -> Self {
78        Self::Middle {
79            min_head_items: 1,
80            min_tail_items: 2,
81            ellipsis: String::from("..."),
82        }
83    }
84
85    /// Creates a customized `Middle` strategy.
86    #[must_use]
87    pub fn middle_with(
88        min_head_items: usize,
89        min_tail_items: usize,
90        ellipsis: impl Into<String>,
91    ) -> Self {
92        Self::Middle {
93            min_head_items,
94            min_tail_items,
95            ellipsis: ellipsis.into(),
96        }
97    }
98
99    /// Creates a default `Start` strategy preserving 2 tail items and `"..."` ellipsis.
100    #[must_use]
101    pub fn start() -> Self {
102        Self::Start {
103            min_tail_items: 2,
104            ellipsis: String::from("..."),
105        }
106    }
107
108    /// Creates a customized `Start` strategy.
109    #[must_use]
110    pub fn start_with(min_tail_items: usize, ellipsis: impl Into<String>) -> Self {
111        Self::Start {
112            min_tail_items,
113            ellipsis: ellipsis.into(),
114        }
115    }
116
117    /// Creates a default `End` strategy preserving 1 head item and `"..."` ellipsis.
118    #[must_use]
119    pub fn end() -> Self {
120        Self::End {
121            min_head_items: 1,
122            ellipsis: String::from("..."),
123        }
124    }
125
126    /// Creates a customized `End` strategy.
127    #[must_use]
128    pub fn end_with(min_head_items: usize, ellipsis: impl Into<String>) -> Self {
129        Self::End {
130            min_head_items,
131            ellipsis: ellipsis.into(),
132        }
133    }
134
135    /// Creates a default `ShortenNames` strategy abbreviating ancestors to 1 character,
136    /// preserving 2 tail items, and falling back to `"..."`.
137    #[must_use]
138    pub fn shorten_names() -> Self {
139        Self::ShortenNames {
140            max_abbrev_len: 1,
141            preserve_tail_items: 2,
142            ellipsis: String::from("..."),
143        }
144    }
145
146    /// Creates a customized `ShortenNames` strategy.
147    #[must_use]
148    pub fn shorten_names_with(
149        max_abbrev_len: usize,
150        preserve_tail_items: usize,
151        ellipsis: impl Into<String>,
152    ) -> Self {
153        Self::ShortenNames {
154            max_abbrev_len,
155            preserve_tail_items,
156            ellipsis: ellipsis.into(),
157        }
158    }
159
160    /// Creates a `None` strategy that performs no truncation or ellipsis insertion.
161    #[must_use]
162    pub fn none() -> Self {
163        Self::None
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_strategy_defaults() {
173        let strategy = TruncateStrategy::default();
174        assert_eq!(
175            strategy,
176            TruncateStrategy::Middle {
177                min_head_items: 1,
178                min_tail_items: 2,
179                ellipsis: String::from("..."),
180            }
181        );
182        assert_eq!(TruncateStrategy::none(), TruncateStrategy::None);
183    }
184}