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