1#![forbid(unsafe_code)]
2
3pub mod alloc_budget;
23pub mod ansi;
24pub mod arena;
25pub mod budget;
26pub mod buffer;
27pub mod cell;
28pub mod counting_writer;
29pub mod diff;
30pub mod diff_strategy;
31pub mod drawing;
32pub mod fit_metrics;
33pub mod frame;
34pub mod frame_guardrails;
35pub mod grapheme_pool;
36pub mod headless;
37pub mod link_registry;
38pub mod presenter;
39
40pub mod quotient_filter;
41pub mod roaring_bitmap;
42pub mod sanitize;
43pub mod spatial_hit_index;
44pub mod terminal_model;
45
46pub(crate) use ftui_core::text_width::{char_width, display_width, grapheme_width};
48
49#[cfg(test)]
50mod tests {
51 use super::{char_width, display_width, grapheme_width};
52
53 #[test]
56 fn display_width_matches_expected_samples() {
57 let samples = [
62 ("hello", 5usize),
63 ("😀", 2usize),
64 ("👩💻", 2usize),
65 ("🇺🇸", 2usize),
66 ("⭐", 2usize),
67 ("A😀B", 4usize),
68 ("ok ✅", 5usize),
69 ];
70 for (sample, expected) in samples {
71 assert_eq!(
72 display_width(sample),
73 expected,
74 "display width mismatch for {sample:?}"
75 );
76 }
77 }
78
79 #[test]
80 fn display_width_empty_string() {
81 assert_eq!(display_width(""), 0);
82 }
83
84 #[test]
85 fn display_width_single_ascii_char() {
86 assert_eq!(display_width("x"), 1);
87 assert_eq!(display_width(" "), 1);
88 }
89
90 #[test]
91 fn display_width_pure_ascii_fast_path() {
92 assert_eq!(display_width("Hello, World!"), 13);
93 assert_eq!(display_width("fn main() {}"), 12);
94 }
95
96 #[test]
97 fn display_width_ascii_with_tabs() {
98 assert_eq!(display_width("a\tb"), 3);
99 assert_eq!(display_width("\n"), 1);
100 }
101
102 #[test]
103 fn display_width_mixed_ascii_emoji() {
104 assert_eq!(display_width("hi 🎉"), 5);
105 assert_eq!(display_width("🚀start"), 7);
106 }
107
108 #[test]
109 fn display_width_zero_width_chars_in_string() {
110 let s = "a\u{00AD}b";
111 assert_eq!(display_width(s), 2);
112 }
113
114 #[test]
115 fn display_width_combining_characters() {
116 let s = "e\u{0301}";
117 assert_eq!(display_width(s), 1);
118 }
119
120 #[test]
121 fn display_width_multiple_emoji() {
122 assert_eq!(display_width("😀😀😀"), 6);
123 }
124
125 #[test]
128 fn grapheme_width_matches_expected_samples() {
129 let samples = [
133 ("a", 1usize),
134 ("😀", 2usize),
135 ("👩💻", 2usize),
136 ("🇺🇸", 2usize),
137 ("👍🏽", 2usize),
138 ("⭐", 2usize),
139 ];
140 for (grapheme, expected) in samples {
141 assert_eq!(
142 grapheme_width(grapheme),
143 expected,
144 "grapheme width mismatch for {grapheme:?}"
145 );
146 }
147 }
148
149 #[test]
150 fn grapheme_width_ascii_space() {
151 assert_eq!(grapheme_width(" "), 1);
152 }
153
154 #[test]
155 fn grapheme_width_ascii_tilde() {
156 assert_eq!(grapheme_width("~"), 1);
157 }
158
159 #[test]
160 fn grapheme_width_tab() {
161 assert_eq!(grapheme_width("\t"), 1);
162 }
163
164 #[test]
165 fn grapheme_width_newline() {
166 assert_eq!(grapheme_width("\n"), 1);
167 }
168
169 #[test]
170 fn grapheme_width_combining_accent() {
171 assert_eq!(grapheme_width("e\u{0301}"), 1);
172 }
173
174 #[test]
175 fn grapheme_width_zero_width_space() {
176 assert_eq!(grapheme_width("\u{200B}"), 0);
177 }
178
179 #[test]
180 fn grapheme_width_zero_width_joiner() {
181 assert_eq!(grapheme_width("\u{200D}"), 0);
182 }
183
184 #[test]
185 fn grapheme_width_skin_tone_modifier() {
186 assert_eq!(grapheme_width("👍🏿"), 2);
187 }
188
189 #[test]
192 fn char_width_ascii_printable() {
193 assert_eq!(char_width('A'), 1);
194 assert_eq!(char_width('z'), 1);
195 assert_eq!(char_width(' '), 1);
196 assert_eq!(char_width('~'), 1);
197 assert_eq!(char_width('!'), 1);
198 }
199
200 #[test]
201 fn char_width_ascii_whitespace() {
202 assert_eq!(char_width('\t'), 1);
203 assert_eq!(char_width('\n'), 1);
204 assert_eq!(char_width('\r'), 1);
205 }
206
207 #[test]
208 fn char_width_ascii_control() {
209 assert_eq!(char_width('\x00'), 0);
210 assert_eq!(char_width('\x01'), 0);
211 assert_eq!(char_width('\x1F'), 0);
212 assert_eq!(char_width('\x7F'), 0);
213 }
214
215 #[test]
216 fn char_width_zero_width_combining() {
217 assert_eq!(char_width('\u{0300}'), 0);
218 assert_eq!(char_width('\u{0301}'), 0);
219 }
220
221 #[test]
222 fn char_width_zero_width_special() {
223 assert_eq!(char_width('\u{200B}'), 0);
224 assert_eq!(char_width('\u{200D}'), 0);
225 assert_eq!(char_width('\u{FEFF}'), 0);
226 assert_eq!(char_width('\u{00AD}'), 0);
227 }
228
229 #[test]
230 fn char_width_variation_selectors() {
231 assert_eq!(char_width('\u{FE00}'), 0);
232 assert_eq!(char_width('\u{FE0F}'), 0);
233 }
234
235 #[test]
236 fn char_width_bidi_controls() {
237 assert_eq!(char_width('\u{200E}'), 0);
238 assert_eq!(char_width('\u{200F}'), 0);
239 }
240
241 #[test]
242 fn char_width_normal_non_ascii() {
243 assert_eq!(char_width('é'), 1);
244 assert_eq!(char_width('ñ'), 1);
245 }
246
247 #[test]
248 fn char_width_euro_sign() {
249 assert_eq!(char_width('€'), 1);
250 }
251}