1mod width;
6pub use width::*;
7
8#[cfg(feature = "fish")]
9mod widecharwidth;
10
11use std::{borrow::Cow, num::NonZeroUsize};
12
13use unicode_segmentation::UnicodeSegmentation;
14
15enum AsciiIterationResult {
16 Complete(String),
17 Remaining(usize),
18}
19
20macro_rules! add_ellipsis {
21 ($text:expr) => {{
22 const SIZE_OF_ELLIPSIS: usize = 3;
23 let mut ret = String::with_capacity($text.len() + SIZE_OF_ELLIPSIS);
24
25 if REVERSE {
26 ret.push('…');
27 }
28
29 ret.push_str($text);
30
31 if !REVERSE {
32 ret.push('…');
33 }
34
35 ret
36 }};
37}
38
39#[inline]
42fn greedy_ascii_add<const REVERSE: bool>(
43 content: &str,
44 width: NonZeroUsize,
45) -> AsciiIterationResult {
46 let width: usize = width.into();
47 debug_assert!(width < content.len());
48
49 let mut bytes_consumed = 0;
50
51 macro_rules! current_byte {
52 () => {
53 if REVERSE {
54 content.as_bytes()[content.len() - 1 - bytes_consumed]
55 } else {
56 content.as_bytes()[bytes_consumed]
57 }
58 };
59 }
60
61 macro_rules! consumed_slice {
62 () => {
63 unsafe {
67 if REVERSE {
68 content.get_unchecked(content.len() - bytes_consumed..)
69 } else {
70 content.get_unchecked(..bytes_consumed)
71 }
72 }
73 };
74 }
75
76 while bytes_consumed < width - 1 {
77 let current_byte = current_byte!();
78 if current_byte.is_ascii() {
79 bytes_consumed += 1;
80 } else {
81 debug_assert!(consumed_slice!().is_ascii());
82 return AsciiIterationResult::Remaining(bytes_consumed);
83 }
84 }
85
86 debug_assert!(consumed_slice!().is_ascii());
88
89 if current_byte!().is_ascii() {
90 AsciiIterationResult::Complete(add_ellipsis!(consumed_slice!()))
91 } else {
92 AsciiIterationResult::Remaining(bytes_consumed)
93 }
94}
95
96#[inline]
98fn handle_remaining<const REVERSE: bool>(
99 content: &str,
100 mut bytes_consumed: usize,
101 width: usize,
102) -> Cow<'_, str> {
103 let content_remaining = unsafe {
107 if REVERSE {
108 content.get_unchecked(..=content.len() - 1 - bytes_consumed)
109 } else {
110 content.get_unchecked(bytes_consumed..)
111 }
112 };
113
114 let mut curr_width = bytes_consumed;
115 let mut exceeded_width = false;
116
117 let mut last_grapheme_len = if curr_width == 0 { 0 } else { 1 };
121
122 macro_rules! measure_graphemes {
128 ($graphemes:expr) => {
129 for g in $graphemes {
130 let g_width = grapheme_width(g);
131
132 if curr_width + g_width <= width {
133 curr_width += g_width;
134 last_grapheme_len = g.len();
135 bytes_consumed += last_grapheme_len;
136 } else {
137 exceeded_width = true;
138 break;
139 }
140 }
141 };
142 }
143
144 let graphemes = UnicodeSegmentation::graphemes(content_remaining, true);
145
146 if REVERSE {
147 measure_graphemes!(graphemes.rev())
148 } else {
149 measure_graphemes!(graphemes)
150 }
151
152 macro_rules! consumed_slice {
153 () => {
154 unsafe {
158 if REVERSE {
159 content.get_unchecked(content.len() - bytes_consumed..)
160 } else {
161 content.get_unchecked(..bytes_consumed)
162 }
163 }
164 };
165 }
166
167 if exceeded_width {
168 if curr_width == width {
169 bytes_consumed -= last_grapheme_len;
171 }
172
173 add_ellipsis!(consumed_slice!()).into()
174 } else {
175 consumed_slice!().into()
176 }
177}
178
179#[inline]
181pub fn truncate_str(content: &str, width: usize) -> Cow<'_, str> {
182 truncate_str_inner::<false>(content, width)
183}
184
185#[inline]
187pub fn truncate_str_leading(content: &str, width: usize) -> Cow<'_, str> {
188 truncate_str_inner::<true>(content, width)
189}
190
191#[inline]
193fn truncate_str_inner<const REVERSE: bool>(content: &str, width: usize) -> Cow<'_, str> {
194 if content.len() <= width {
195 content.into()
199 } else if let Some(nz_width) = NonZeroUsize::new(width) {
200 match greedy_ascii_add::<REVERSE>(content, nz_width) {
209 AsciiIterationResult::Complete(text) => text.into(),
210 AsciiIterationResult::Remaining(bytes_consumed) => {
211 handle_remaining::<REVERSE>(content, bytes_consumed, width)
212 }
213 }
214 } else {
215 "".into()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
224
225 #[test]
226 fn test_truncate_str() {
227 let cpu_header = "CPU(c)▲";
228
229 assert_eq!(
230 truncate_str(cpu_header, 8),
231 cpu_header,
232 "should match base string as there is extra room"
233 );
234
235 assert_eq!(
236 truncate_str(cpu_header, 7),
237 cpu_header,
238 "should match base string as there is enough room"
239 );
240
241 assert_eq!(truncate_str(cpu_header, 6), "CPU(c…");
242 assert_eq!(truncate_str(cpu_header, 5), "CPU(…");
243 assert_eq!(truncate_str(cpu_header, 4), "CPU…");
244 assert_eq!(truncate_str(cpu_header, 1), "…");
245 assert_eq!(truncate_str(cpu_header, 0), "");
246 }
247
248 #[test]
249 fn test_truncate_str_leading() {
250 let cpu_header = "▲CPU(c)";
251
252 assert_eq!(
253 truncate_str_leading(cpu_header, 8),
254 cpu_header,
255 "should match base string as there is extra room"
256 );
257
258 assert_eq!(
259 truncate_str_leading(cpu_header, 7),
260 cpu_header,
261 "should match base string as there is enough room"
262 );
263
264 assert_eq!(truncate_str_leading(cpu_header, 6), "…PU(c)");
265 assert_eq!(truncate_str_leading(cpu_header, 5), "…U(c)");
266 assert_eq!(truncate_str_leading(cpu_header, 4), "…(c)");
267 assert_eq!(truncate_str_leading(cpu_header, 1), "…");
268 assert_eq!(truncate_str_leading(cpu_header, 0), "");
269 }
270
271 #[test]
272 fn test_truncate_ascii() {
273 let content = "0123456";
274
275 assert_eq!(
276 truncate_str(content, 8),
277 content,
278 "should match base string as there is extra room"
279 );
280
281 assert_eq!(
282 truncate_str(content, 7),
283 content,
284 "should match base string as there is enough room"
285 );
286
287 assert_eq!(truncate_str(content, 6), "01234…");
288 assert_eq!(truncate_str(content, 5), "0123…");
289 assert_eq!(truncate_str(content, 4), "012…");
290 assert_eq!(truncate_str(content, 1), "…");
291 assert_eq!(truncate_str(content, 0), "");
292 }
293
294 #[test]
295 fn test_truncate_ascii_leading() {
296 let content = "0123456";
297
298 assert_eq!(
299 truncate_str_leading(content, 8),
300 content,
301 "should match base string as there is extra room"
302 );
303
304 assert_eq!(
305 truncate_str_leading(content, 7),
306 content,
307 "should match base string as there is enough room"
308 );
309
310 assert_eq!(truncate_str_leading(content, 6), "…23456");
311 assert_eq!(truncate_str_leading(content, 5), "…3456");
312 assert_eq!(truncate_str_leading(content, 4), "…456");
313 assert_eq!(truncate_str_leading(content, 1), "…");
314 assert_eq!(truncate_str_leading(content, 0), "");
315 }
316
317 #[test]
318 fn test_truncate_cjk() {
319 let cjk = "施氏食獅史";
320
321 assert_eq!(
322 truncate_str(cjk, 11),
323 cjk,
324 "should match base string as there is extra room"
325 );
326
327 assert_eq!(
328 truncate_str(cjk, 10),
329 cjk,
330 "should match base string as there is enough room"
331 );
332
333 assert_eq!(truncate_str(cjk, 9), "施氏食獅…");
334 assert_eq!(truncate_str(cjk, 8), "施氏食…");
335 assert_eq!(truncate_str(cjk, 2), "…");
336 assert_eq!(truncate_str(cjk, 1), "…");
337 assert_eq!(truncate_str(cjk, 0), "");
338
339 let cjk_2 = "你好嗎";
340 assert_eq!(truncate_str(cjk_2, 5), "你好…");
341 assert_eq!(truncate_str(cjk_2, 4), "你…");
342 assert_eq!(truncate_str(cjk_2, 3), "你…");
343 assert_eq!(truncate_str(cjk_2, 2), "…");
344 assert_eq!(truncate_str(cjk_2, 1), "…");
345 assert_eq!(truncate_str(cjk_2, 0), "");
346 }
347
348 #[test]
349 fn test_truncate_cjk_leading() {
350 let cjk = "施氏食獅史";
351
352 assert_eq!(
353 truncate_str_leading(cjk, 11),
354 cjk,
355 "should match base string as there is extra room"
356 );
357
358 assert_eq!(
359 truncate_str_leading(cjk, 10),
360 cjk,
361 "should match base string as there is enough room"
362 );
363
364 assert_eq!(truncate_str_leading(cjk, 9), "…氏食獅史");
365 assert_eq!(truncate_str_leading(cjk, 8), "…食獅史");
366 assert_eq!(truncate_str_leading(cjk, 2), "…");
367 assert_eq!(truncate_str_leading(cjk, 1), "…");
368 assert_eq!(truncate_str_leading(cjk, 0), "");
369
370 let cjk_2 = "你好嗎";
371 assert_eq!(truncate_str_leading(cjk_2, 5), "…好嗎");
372 assert_eq!(truncate_str_leading(cjk_2, 4), "…嗎");
373 assert_eq!(truncate_str_leading(cjk_2, 3), "…嗎");
374 assert_eq!(truncate_str_leading(cjk_2, 2), "…");
375 assert_eq!(truncate_str_leading(cjk_2, 1), "…");
376 assert_eq!(truncate_str_leading(cjk_2, 0), "");
377 }
378
379 #[test]
380 fn test_truncate_mixed_one() {
381 let test = "Test (施氏食獅史) Test";
382
383 assert_eq!(
384 truncate_str(test, 30),
385 test,
386 "should match base string as there is extra room"
387 );
388
389 assert_eq!(
390 truncate_str(test, 22),
391 test,
392 "should match base string as there is just enough room"
393 );
394
395 assert_eq!(
396 truncate_str(test, 21),
397 "Test (施氏食獅史) Te…",
398 "should truncate the t and replace the s with ellipsis"
399 );
400
401 assert_eq!(truncate_str(test, 20), "Test (施氏食獅史) T…");
402 assert_eq!(truncate_str(test, 19), "Test (施氏食獅史) …");
403 assert_eq!(truncate_str(test, 18), "Test (施氏食獅史)…");
404 assert_eq!(truncate_str(test, 17), "Test (施氏食獅史…");
405 assert_eq!(truncate_str(test, 16), "Test (施氏食獅…");
406 assert_eq!(truncate_str(test, 15), "Test (施氏食獅…");
407 assert_eq!(truncate_str(test, 14), "Test (施氏食…");
408 assert_eq!(truncate_str(test, 13), "Test (施氏食…");
409 assert_eq!(truncate_str(test, 8), "Test (…");
410 assert_eq!(truncate_str(test, 7), "Test (…");
411 assert_eq!(truncate_str(test, 6), "Test …");
412 assert_eq!(truncate_str(test, 5), "Test…");
413 assert_eq!(truncate_str(test, 4), "Tes…");
414 }
415
416 #[test]
417 fn test_truncate_mixed_one_leading() {
418 let test = "Test (施氏食獅史) Test";
419
420 assert_eq!(
421 truncate_str_leading(test, 30),
422 test,
423 "should match base string as there is extra room"
424 );
425
426 assert_eq!(
427 truncate_str_leading(test, 22),
428 test,
429 "should match base string as there is just enough room"
430 );
431
432 assert_eq!(
433 truncate_str_leading(test, 21),
434 "…st (施氏食獅史) Test",
435 "should truncate the T and replace the e with ellipsis"
436 );
437
438 assert_eq!(truncate_str_leading(test, 20), "…t (施氏食獅史) Test");
439 assert_eq!(truncate_str_leading(test, 19), "… (施氏食獅史) Test");
440 assert_eq!(truncate_str_leading(test, 18), "…(施氏食獅史) Test");
441 assert_eq!(truncate_str_leading(test, 17), "…施氏食獅史) Test");
442 assert_eq!(truncate_str_leading(test, 16), "…氏食獅史) Test");
443 assert_eq!(truncate_str_leading(test, 15), "…氏食獅史) Test");
444 assert_eq!(truncate_str_leading(test, 14), "…食獅史) Test");
445 assert_eq!(truncate_str_leading(test, 13), "…食獅史) Test");
446 assert_eq!(truncate_str_leading(test, 8), "…) Test");
447 assert_eq!(truncate_str_leading(test, 7), "…) Test");
448 assert_eq!(truncate_str_leading(test, 6), "… Test");
449 assert_eq!(truncate_str_leading(test, 5), "…Test");
450 assert_eq!(truncate_str_leading(test, 4), "…est");
451 }
452
453 #[test]
454 fn test_truncate_mixed_two() {
455 let test = "Test (施氏abc食abc獅史) Test";
456
457 assert_eq!(
458 truncate_str(test, 30),
459 test,
460 "should match base string as there is extra room"
461 );
462
463 assert_eq!(
464 truncate_str(test, 28),
465 test,
466 "should match base string as there is just enough room"
467 );
468
469 assert_eq!(truncate_str(test, 26), "Test (施氏abc食abc獅史) T…");
470 assert_eq!(truncate_str(test, 21), "Test (施氏abc食abc獅…");
471 assert_eq!(truncate_str(test, 20), "Test (施氏abc食abc…");
472 assert_eq!(truncate_str(test, 16), "Test (施氏abc食…");
473 assert_eq!(truncate_str(test, 15), "Test (施氏abc…");
474 assert_eq!(truncate_str(test, 14), "Test (施氏abc…");
475 assert_eq!(truncate_str(test, 11), "Test (施氏…");
476 assert_eq!(truncate_str(test, 10), "Test (施…");
477 }
478
479 #[test]
480 fn test_truncate_mixed_two_leading() {
481 let test = "Test (施氏abc食abc獅史) Test";
482
483 assert_eq!(
484 truncate_str_leading(test, 30),
485 test,
486 "should match base string as there is extra room"
487 );
488
489 assert_eq!(
490 truncate_str_leading(test, 28),
491 test,
492 "should match base string as there is just enough room"
493 );
494
495 assert_eq!(truncate_str_leading(test, 26), "…t (施氏abc食abc獅史) Test");
496 assert_eq!(truncate_str_leading(test, 21), "…氏abc食abc獅史) Test");
497 assert_eq!(truncate_str_leading(test, 20), "…abc食abc獅史) Test");
498 assert_eq!(truncate_str_leading(test, 16), "…食abc獅史) Test");
499 assert_eq!(truncate_str_leading(test, 15), "…abc獅史) Test");
500 assert_eq!(truncate_str_leading(test, 14), "…abc獅史) Test");
501 assert_eq!(truncate_str_leading(test, 11), "…獅史) Test");
502 assert_eq!(truncate_str_leading(test, 10), "…史) Test");
503 }
504
505 #[test]
506 fn test_truncate_flags() {
507 let flag = "🇨🇦";
508 assert_eq!(truncate_str(flag, 3), flag);
509 assert_eq!(truncate_str(flag, 2), flag);
510 assert_eq!(truncate_str(flag, 1), "…");
511 assert_eq!(truncate_str(flag, 0), "");
512
513 let flag_text = "oh 🇨🇦";
514 assert_eq!(truncate_str(flag_text, 6), flag_text);
515 assert_eq!(truncate_str(flag_text, 5), flag_text);
516 assert_eq!(truncate_str(flag_text, 4), "oh …");
517
518 let flag_text_wrap = "!🇨🇦!";
519 assert_eq!(truncate_str(flag_text_wrap, 6), flag_text_wrap);
520 assert_eq!(truncate_str(flag_text_wrap, 4), flag_text_wrap);
521 assert_eq!(truncate_str(flag_text_wrap, 3), "!…");
522 assert_eq!(truncate_str(flag_text_wrap, 2), "!…");
523 assert_eq!(truncate_str(flag_text_wrap, 1), "…");
524
525 let flag_cjk = "加拿大🇨🇦";
526 assert_eq!(truncate_str(flag_cjk, 9), flag_cjk);
527 assert_eq!(truncate_str(flag_cjk, 8), flag_cjk);
528 assert_eq!(truncate_str(flag_cjk, 7), "加拿大…");
529 assert_eq!(truncate_str(flag_cjk, 6), "加拿…");
530 assert_eq!(truncate_str(flag_cjk, 5), "加拿…");
531 assert_eq!(truncate_str(flag_cjk, 4), "加…");
532
533 let flag_mix = "🇨🇦加gaa拿naa大daai🇨🇦";
534 assert_eq!(truncate_str(flag_mix, 20), flag_mix);
535 assert_eq!(truncate_str(flag_mix, 19), "🇨🇦加gaa拿naa大daai…");
536 assert_eq!(truncate_str(flag_mix, 18), "🇨🇦加gaa拿naa大daa…");
537 assert_eq!(truncate_str(flag_mix, 17), "🇨🇦加gaa拿naa大da…");
538 assert_eq!(truncate_str(flag_mix, 15), "🇨🇦加gaa拿naa大…");
539 assert_eq!(truncate_str(flag_mix, 14), "🇨🇦加gaa拿naa…");
540 assert_eq!(truncate_str(flag_mix, 13), "🇨🇦加gaa拿naa…");
541 assert_eq!(truncate_str(flag_mix, 3), "🇨🇦…");
542 assert_eq!(truncate_str(flag_mix, 2), "…");
543 assert_eq!(truncate_str(flag_mix, 1), "…");
544 assert_eq!(truncate_str(flag_mix, 0), "");
545 }
546
547 #[test]
548 fn test_truncate_flags_leading() {
549 let flag = "🇨🇦";
550 assert_eq!(truncate_str_leading(flag, 3), flag);
551 assert_eq!(truncate_str_leading(flag, 2), flag);
552 assert_eq!(truncate_str_leading(flag, 1), "…");
553 assert_eq!(truncate_str_leading(flag, 0), "");
554
555 let flag_text = "🇨🇦 oh";
556 assert_eq!(truncate_str_leading(flag_text, 6), flag_text);
557 assert_eq!(truncate_str_leading(flag_text, 5), flag_text);
558 assert_eq!(truncate_str_leading(flag_text, 4), "… oh");
559
560 let flag_text_wrap = "!🇨🇦!";
561 assert_eq!(truncate_str_leading(flag_text_wrap, 6), flag_text_wrap);
562 assert_eq!(truncate_str_leading(flag_text_wrap, 4), flag_text_wrap);
563 assert_eq!(truncate_str_leading(flag_text_wrap, 3), "…!");
564 assert_eq!(truncate_str_leading(flag_text_wrap, 2), "…!");
565 assert_eq!(truncate_str_leading(flag_text_wrap, 1), "…");
566
567 let flag_cjk = "🇨🇦加拿大";
568 assert_eq!(truncate_str_leading(flag_cjk, 9), flag_cjk);
569 assert_eq!(truncate_str_leading(flag_cjk, 8), flag_cjk);
570 assert_eq!(truncate_str_leading(flag_cjk, 7), "…加拿大");
571 assert_eq!(truncate_str_leading(flag_cjk, 6), "…拿大");
572 assert_eq!(truncate_str_leading(flag_cjk, 5), "…拿大");
573 assert_eq!(truncate_str_leading(flag_cjk, 4), "…大");
574
575 let flag_mix = "🇨🇦加gaa拿naa大daai🇨🇦";
576 assert_eq!(truncate_str_leading(flag_mix, 20), flag_mix);
577 assert_eq!(truncate_str_leading(flag_mix, 19), "…加gaa拿naa大daai🇨🇦");
578 assert_eq!(truncate_str_leading(flag_mix, 18), "…gaa拿naa大daai🇨🇦");
579 assert_eq!(truncate_str_leading(flag_mix, 17), "…gaa拿naa大daai🇨🇦");
580 assert_eq!(truncate_str_leading(flag_mix, 15), "…a拿naa大daai🇨🇦");
581 assert_eq!(truncate_str_leading(flag_mix, 14), "…拿naa大daai🇨🇦");
582 assert_eq!(truncate_str_leading(flag_mix, 13), "…naa大daai🇨🇦");
583 assert_eq!(truncate_str_leading(flag_mix, 3), "…🇨🇦");
584 assert_eq!(truncate_str_leading(flag_mix, 2), "…");
585 assert_eq!(truncate_str_leading(flag_mix, 1), "…");
586 assert_eq!(truncate_str_leading(flag_mix, 0), "");
587 }
588
589 #[test]
591 #[cfg(feature = "fish")]
592 fn test_truncate_hindi() {
593 let test = "हिन्दी";
595 assert_eq!(truncate_str(test, 10), test);
596 assert_eq!(truncate_str(test, 6), "हिन्दी");
597 assert_eq!(truncate_str(test, 5), "हिन्दी");
598 assert_eq!(truncate_str(test, 4), "हिन्दी");
599 assert_eq!(truncate_str(test, 3), "हिन्दी");
600 assert_eq!(truncate_str(test, 2), "हि…");
601 assert_eq!(truncate_str(test, 1), "…");
602 assert_eq!(truncate_str(test, 0), "");
603 }
605
606 #[test]
607 #[cfg(feature = "fish")]
608 fn test_truncate_hindi_leading() {
609 let test = "हिन्दी";
611 assert_eq!(truncate_str_leading(test, 10), test);
612 assert_eq!(truncate_str_leading(test, 6), "हिन्दी");
613 assert_eq!(truncate_str_leading(test, 5), "हिन्दी");
614 assert_eq!(truncate_str_leading(test, 4), "हिन्दी");
615 assert_eq!(truncate_str_leading(test, 3), "हिन्दी");
616 assert_eq!(truncate_str_leading(test, 2), "…");
617 assert_eq!(truncate_str_leading(test, 1), "…");
618 assert_eq!(truncate_str_leading(test, 0), "");
619 }
621
622 #[test]
623 fn truncate_emoji() {
624 let heart_1 = "♥";
625 assert_eq!(truncate_str(heart_1, 2), heart_1);
626 assert_eq!(truncate_str(heart_1, 1), heart_1);
627 assert_eq!(truncate_str(heart_1, 0), "");
628
629 let heart_2 = "❤";
630 assert_eq!(truncate_str(heart_2, 2), heart_2);
631 assert_eq!(truncate_str(heart_2, 1), heart_2);
632 assert_eq!(truncate_str(heart_2, 0), "");
633
634 let heart_emoji_pres = "❤️";
637 assert_eq!(truncate_str(heart_emoji_pres, 2), heart_emoji_pres);
638 #[cfg(feature = "fish")]
639 assert_eq!(truncate_str(heart_emoji_pres, 1), heart_emoji_pres);
640 assert_eq!(truncate_str(heart_emoji_pres, 0), "");
641
642 let emote = "💎";
643 assert_eq!(truncate_str(emote, 2), emote);
644 assert_eq!(truncate_str(emote, 1), "…");
645 assert_eq!(truncate_str(emote, 0), "");
646
647 let family = "👨👨👧👦";
648 assert_eq!(truncate_str(family, 2), family);
649 assert_eq!(truncate_str(family, 1), "…");
650 assert_eq!(truncate_str(family, 0), "");
651
652 let scientist = "👩🔬";
653 assert_eq!(truncate_str(scientist, 2), scientist);
654 assert_eq!(truncate_str(scientist, 1), "…");
655 assert_eq!(truncate_str(scientist, 0), "");
656 }
657
658 #[test]
659 fn truncate_emoji_leading() {
660 let heart_1 = "♥";
661 assert_eq!(truncate_str_leading(heart_1, 2), heart_1);
662 assert_eq!(truncate_str_leading(heart_1, 1), heart_1);
663 assert_eq!(truncate_str_leading(heart_1, 0), "");
664
665 let heart_2 = "❤";
666 assert_eq!(truncate_str_leading(heart_2, 2), heart_2);
667 assert_eq!(truncate_str_leading(heart_2, 1), heart_2);
668 assert_eq!(truncate_str_leading(heart_2, 0), "");
669
670 let heart_emoji_pres = "❤️";
673 assert_eq!(truncate_str_leading(heart_emoji_pres, 2), heart_emoji_pres);
674 #[cfg(feature = "fish")]
675 assert_eq!(truncate_str_leading(heart_emoji_pres, 1), heart_emoji_pres);
676 assert_eq!(truncate_str_leading(heart_emoji_pres, 0), "");
677
678 let emote = "💎";
679 assert_eq!(truncate_str_leading(emote, 2), emote);
680 assert_eq!(truncate_str_leading(emote, 1), "…");
681 assert_eq!(truncate_str_leading(emote, 0), "");
682
683 let family = "👨👨👧👦";
684 assert_eq!(truncate_str_leading(family, 2), family);
685 assert_eq!(truncate_str_leading(family, 1), "…");
686 assert_eq!(truncate_str_leading(family, 0), "");
687
688 let scientist = "👩🔬";
689 assert_eq!(truncate_str_leading(scientist, 2), scientist);
690 assert_eq!(truncate_str_leading(scientist, 1), "…");
691 assert_eq!(truncate_str_leading(scientist, 0), "");
692 }
693}