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}