1#![no_std]
115#![forbid(unsafe_code)]
116#![forbid(elided_lifetimes_in_paths)]
117
118use core::fmt;
119use core::iter::FusedIterator;
120use core::ops::Range;
121use core::str::CharIndices;
122
123pub trait CharRangesExt {
124 fn char_ranges(&self) -> CharRanges<'_>;
128
129 #[inline]
134 fn char_ranges_offset(&self, offset: usize) -> CharRangesOffset<'_> {
135 self.char_ranges().offset(offset)
136 }
137}
138
139impl CharRangesExt for str {
140 #[inline]
141 fn char_ranges(&self) -> CharRanges<'_> {
142 CharRanges::new(self)
143 }
144}
145
146#[derive(Clone)]
152pub struct CharRanges<'a> {
153 iter: CharIndices<'a>,
154}
155
156impl<'a> CharRanges<'a> {
157 #[inline]
166 pub fn new(text: &'a str) -> Self {
167 Self {
168 iter: text.char_indices(),
169 }
170 }
171
172 #[inline]
190 pub fn as_str(&self) -> &'a str {
191 self.iter.as_str()
192 }
193
194 #[inline]
199 pub fn offset(self, offset: usize) -> CharRangesOffset<'a> {
200 CharRangesOffset { iter: self, offset }
201 }
202}
203
204impl Iterator for CharRanges<'_> {
205 type Item = (Range<usize>, char);
206
207 #[inline]
208 fn next(&mut self) -> Option<Self::Item> {
209 let (start, c) = self.iter.next()?;
210 let end = start + c.len_utf8();
211 Some((start..end, c))
212 }
213
214 #[inline]
215 fn count(self) -> usize {
216 self.iter.count()
217 }
218
219 #[inline]
220 fn size_hint(&self) -> (usize, Option<usize>) {
221 self.iter.size_hint()
222 }
223
224 #[inline]
225 fn last(mut self) -> Option<(Range<usize>, char)> {
226 self.next_back()
227 }
228
229 #[inline]
230 fn nth(&mut self, n: usize) -> Option<Self::Item> {
231 let (start, c) = self.iter.nth(n)?;
232 let end = start + c.len_utf8();
233 Some((start..end, c))
234 }
235}
236
237impl DoubleEndedIterator for CharRanges<'_> {
238 #[inline]
239 fn next_back(&mut self) -> Option<Self::Item> {
240 let (start, c) = self.iter.next_back()?;
241 let end = start + c.len_utf8();
242 Some((start..end, c))
243 }
244
245 #[inline]
246 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
247 let (start, c) = self.iter.nth_back(n)?;
248 let end = start + c.len_utf8();
249 Some((start..end, c))
250 }
251}
252
253impl FusedIterator for CharRanges<'_> {}
254
255impl fmt::Debug for CharRanges<'_> {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "CharRanges(")?;
258 f.debug_list().entries(self.clone()).finish()?;
259 write!(f, ")")?;
260 Ok(())
261 }
262}
263
264#[derive(Clone)]
271pub struct CharRangesOffset<'a> {
272 iter: CharRanges<'a>,
273 offset: usize,
274}
275
276impl<'a> CharRangesOffset<'a> {
277 #[inline]
290 pub fn new(offset: usize, text: &'a str) -> Self {
291 Self {
292 iter: text.char_ranges(),
293 offset,
294 }
295 }
296
297 #[inline]
318 pub fn as_str(&self) -> &'a str {
319 self.iter.as_str()
320 }
321
322 #[inline]
345 pub fn offset(&self) -> usize {
346 self.offset
347 }
348}
349
350impl Iterator for CharRangesOffset<'_> {
351 type Item = (Range<usize>, char);
352
353 #[inline]
354 fn next(&mut self) -> Option<Self::Item> {
355 let (r, c) = self.iter.next()?;
356 let start = r.start + self.offset;
357 let end = r.end + self.offset;
358 Some((start..end, c))
359 }
360
361 #[inline]
362 fn count(self) -> usize {
363 self.iter.count()
364 }
365
366 #[inline]
367 fn size_hint(&self) -> (usize, Option<usize>) {
368 self.iter.size_hint()
369 }
370
371 #[inline]
372 fn last(mut self) -> Option<(Range<usize>, char)> {
373 self.next_back()
374 }
375
376 #[inline]
377 fn nth(&mut self, n: usize) -> Option<Self::Item> {
378 let (r, c) = self.iter.nth(n)?;
379 let start = r.start + self.offset;
380 let end = r.end + self.offset;
381 Some((start..end, c))
382 }
383}
384
385impl DoubleEndedIterator for CharRangesOffset<'_> {
386 #[inline]
387 fn next_back(&mut self) -> Option<Self::Item> {
388 let (r, c) = self.iter.next_back()?;
389 let start = r.start + self.offset;
390 let end = r.end + self.offset;
391 Some((start..end, c))
392 }
393
394 #[inline]
395 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
396 let (r, c) = self.iter.nth_back(n)?;
397 let start = r.start + self.offset;
398 let end = r.end + self.offset;
399 Some((start..end, c))
400 }
401}
402
403impl FusedIterator for CharRangesOffset<'_> {}
404
405impl fmt::Debug for CharRangesOffset<'_> {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 write!(f, "CharRangesOffset(")?;
408 f.debug_list().entries(self.clone()).finish()?;
409 write!(f, ")")?;
410 Ok(())
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use core::iter;
417
418 use super::CharRangesExt;
419
420 #[test]
421 fn test_empty() {
422 let text = "";
423
424 let mut chars = text.char_ranges();
425 assert_eq!(chars.next(), None);
426 assert_eq!(chars.next_back(), None);
427 assert_eq!(chars.as_str(), "");
428
429 let mut chars = text.char_ranges();
430 assert_eq!(chars.next_back(), None);
431 assert_eq!(chars.next(), None);
432 assert_eq!(chars.as_str(), "");
433 }
434
435 #[test]
436 fn test_empty_single_char() {
437 let text = "a";
438
439 let mut chars = text.char_ranges();
440 assert_eq!(chars.as_str(), "a");
441 assert_eq!(chars.next(), Some((0..1, 'a')));
442 assert_eq!(chars.next_back(), None);
443 assert_eq!(chars.as_str(), "");
444
445 let mut chars = text.char_ranges();
446 assert_eq!(chars.as_str(), "a");
447 assert_eq!(chars.next_back(), Some((0..1, 'a')));
448 assert_eq!(chars.next(), None);
449 assert_eq!(chars.as_str(), "");
450 }
451
452 #[test]
453 fn test_empty_single_char_multi_byte() {
454 let text = "🌏";
455
456 let mut chars = text.char_ranges();
457 assert_eq!(chars.as_str(), "🌏");
458 assert_eq!(chars.next(), Some((0..4, '🌏')));
459 assert_eq!(chars.next_back(), None);
460 assert_eq!(chars.as_str(), "");
461
462 let mut chars = text.char_ranges();
463 assert_eq!(chars.as_str(), "🌏");
464 assert_eq!(chars.next_back(), Some((0..4, '🌏')));
465 assert_eq!(chars.next(), None);
466 assert_eq!(chars.as_str(), "");
467 }
468
469 #[test]
470 fn test_simple() {
471 let text = "Foo";
472 let mut chars = text.char_ranges();
473 assert_eq!(chars.as_str(), "Foo");
474
475 assert_eq!(chars.next(), Some((0..1, 'F')));
476 assert_eq!(chars.as_str(), "oo");
477
478 assert_eq!(chars.next(), Some((1..2, 'o')));
479 assert_eq!(chars.as_str(), "o");
480
481 assert_eq!(chars.next(), Some((2..3, 'o')));
482 assert_eq!(chars.as_str(), "");
483
484 assert_eq!(chars.next(), None);
485 assert_eq!(chars.as_str(), "");
486 }
487
488 #[test]
489 fn test_simple_multi_byte() {
490 let text = "🗻∈🌏";
491
492 let mut chars = text.char_ranges();
493 assert_eq!(chars.as_str(), "🗻∈🌏");
494
495 assert_eq!(chars.next(), Some((0..4, '🗻')));
496 assert_eq!(chars.next(), Some((4..7, '∈')));
497 assert_eq!(chars.next(), Some((7..11, '🌏')));
498 assert_eq!(chars.next(), None);
499 }
500
501 #[test]
502 fn test_simple_mixed_multi_byte() {
503 let text = "🗻12∈45🌏";
504 let mut chars = text.char_ranges();
505 assert_eq!(chars.as_str(), "🗻12∈45🌏");
506
507 assert_eq!(chars.next(), Some((0..4, '🗻')));
508 assert_eq!(chars.as_str(), "12∈45🌏");
509
510 assert_eq!(chars.next(), Some((4..5, '1')));
511 assert_eq!(chars.as_str(), "2∈45🌏");
512
513 assert_eq!(chars.next(), Some((5..6, '2')));
514 assert_eq!(chars.as_str(), "∈45🌏");
515
516 assert_eq!(chars.next(), Some((6..9, '∈')));
517 assert_eq!(chars.as_str(), "45🌏");
518
519 assert_eq!(chars.next(), Some((9..10, '4')));
520 assert_eq!(chars.as_str(), "5🌏");
521
522 assert_eq!(chars.next(), Some((10..11, '5')));
523 assert_eq!(chars.as_str(), "🌏");
524
525 assert_eq!(chars.next(), Some((11..15, '🌏')));
526 assert_eq!(chars.as_str(), "");
527
528 assert_eq!(chars.next(), None);
529 assert_eq!(chars.as_str(), "");
530 }
531
532 #[test]
533 fn test_simple_next_back() {
534 let text = "Foo";
535 let mut chars = text.char_ranges();
536 assert_eq!(chars.as_str(), "Foo");
537
538 assert_eq!(chars.next_back(), Some((2..3, 'o')));
539 assert_eq!(chars.as_str(), "Fo");
540
541 assert_eq!(chars.next_back(), Some((1..2, 'o')));
542 assert_eq!(chars.as_str(), "F");
543
544 assert_eq!(chars.next_back(), Some((0..1, 'F')));
545 assert_eq!(chars.as_str(), "");
546
547 assert_eq!(chars.next_back(), None);
548 assert_eq!(chars.as_str(), "");
549 }
550
551 #[test]
552 fn test_simple_next_back_multi_byte() {
553 let text = "🗻12∈45🌏";
554 let mut chars = text.char_ranges();
555 assert_eq!(chars.as_str(), "🗻12∈45🌏");
556
557 assert_eq!(chars.next_back(), Some((11..15, '🌏')));
558 assert_eq!(chars.as_str(), "🗻12∈45");
559
560 assert_eq!(chars.next_back(), Some((10..11, '5')));
561 assert_eq!(chars.as_str(), "🗻12∈4");
562
563 assert_eq!(chars.next_back(), Some((9..10, '4')));
564 assert_eq!(chars.as_str(), "🗻12∈");
565
566 assert_eq!(chars.next_back(), Some((6..9, '∈')));
567 assert_eq!(chars.as_str(), "🗻12");
568
569 assert_eq!(chars.next_back(), Some((5..6, '2')));
570 assert_eq!(chars.as_str(), "🗻1");
571
572 assert_eq!(chars.next_back(), Some((4..5, '1')));
573 assert_eq!(chars.as_str(), "🗻");
574
575 assert_eq!(chars.next_back(), Some((0..4, '🗻')));
576 assert_eq!(chars.as_str(), "");
577
578 assert_eq!(chars.next_back(), None);
579 assert_eq!(chars.as_str(), "");
580 }
581
582 #[test]
583 fn test_simple_next_and_next_back() {
584 let text = "Foo Bar";
585
586 let mut chars = text.char_ranges();
587 assert_eq!(chars.as_str(), "Foo Bar");
588
589 assert_eq!(chars.next_back(), Some((6..7, 'r')));
590 assert_eq!(chars.as_str(), "Foo Ba");
591
592 assert_eq!(chars.next_back(), Some((5..6, 'a')));
593 assert_eq!(chars.as_str(), "Foo B");
594
595 assert_eq!(chars.next(), Some((0..1, 'F')));
596 assert_eq!(chars.as_str(), "oo B");
597
598 assert_eq!(chars.next(), Some((1..2, 'o')));
599 assert_eq!(chars.as_str(), "o B");
600
601 assert_eq!(chars.next_back(), Some((4..5, 'B')));
602 assert_eq!(chars.as_str(), "o ");
603
604 assert_eq!(chars.next(), Some((2..3, 'o')));
605 assert_eq!(chars.as_str(), " ");
606
607 assert_eq!(chars.next(), Some((3..4, ' ')));
608 assert_eq!(chars.as_str(), "");
609
610 assert_eq!(chars.next(), None);
611 assert_eq!(chars.as_str(), "");
612 }
613
614 #[test]
615 fn test_simple_next_and_next_back_multi_byte() {
616 let text = "🗻12∈45🌏";
617 let mut chars = text.char_ranges();
618 assert_eq!(chars.as_str(), "🗻12∈45🌏");
619
620 assert_eq!(chars.next_back(), Some((11..15, '🌏')));
621 assert_eq!(chars.as_str(), "🗻12∈45");
622
623 assert_eq!(chars.next_back(), Some((10..11, '5')));
624 assert_eq!(chars.as_str(), "🗻12∈4");
625
626 assert_eq!(chars.next(), Some((0..4, '🗻')));
627 assert_eq!(chars.as_str(), "12∈4");
628
629 assert_eq!(chars.next(), Some((4..5, '1')));
630 assert_eq!(chars.as_str(), "2∈4");
631
632 assert_eq!(chars.next_back(), Some((9..10, '4')));
633 assert_eq!(chars.as_str(), "2∈");
634
635 assert_eq!(chars.next(), Some((5..6, '2')));
636 assert_eq!(chars.as_str(), "∈");
637
638 assert_eq!(chars.next(), Some((6..9, '∈')));
639 assert_eq!(chars.as_str(), "");
640
641 assert_eq!(chars.next(), None);
642 assert_eq!(chars.as_str(), "");
643 }
644
645 #[test]
646 fn test_last() {
647 let cases = [
648 ("Hello World", 10..11, 'd'),
649 ("Hello 👋 World 🌏", 17..21, '🌏'),
650 ("🗻12∈45🌏", 11..15, '🌏'),
651 ("Hello 🗻12∈45🌏 World", 26..27, 'd'),
652 ];
653 for (text, r, c) in cases {
654 let actual = text.char_ranges().last().unwrap();
655
656 assert_eq!(actual.0, r);
657 assert_eq!(actual.1, c);
658 assert!(text[r].chars().eq([c]));
659 }
660 }
661
662 #[test]
663 fn test_nth() {
664 let cases = [
665 "Hello World",
666 "Hello 👋 World 🌏",
667 "🗻12∈45🌏",
668 "Hello 🗻12∈45🌏 World",
669 ];
670 for text in cases {
671 let char_ranges1 = text.char_ranges();
674 let char_ranges2 = iter::from_fn({
675 let mut char_ranges = text.char_ranges();
676 move || char_ranges.nth(0)
677 });
678 assert!(char_ranges1.eq(char_ranges2));
679 }
680 }
681
682 #[test]
683 fn test_nth_back() {
684 let cases = [
685 "Hello World",
686 "Hello 👋 World 🌏",
687 "🗻12∈45🌏",
688 "Hello 🗻12∈45🌏 World",
689 ];
690 for text in cases {
691 let char_ranges1 = text.char_ranges().rev();
694 let char_ranges2 = iter::from_fn({
695 let mut char_ranges = text.char_ranges();
696 move || char_ranges.nth_back(0)
697 });
698 assert!(char_ranges1.eq(char_ranges2));
699 }
700 }
701
702 #[test]
703 fn test_char_ranges() {
704 let text = "Hello World";
705 for (r, c) in text.char_ranges() {
706 let mut chars = text[r].chars();
707 assert_eq!(chars.next(), Some(c));
708 assert_eq!(chars.next(), None);
709 }
710
711 let text = "🗻12∈45🌏";
712 for (r, c) in text.char_ranges() {
713 let mut chars = text[r].chars();
714 assert_eq!(chars.next(), Some(c));
715 assert_eq!(chars.next(), None);
716 }
717 }
718
719 #[test]
720 fn test_char_ranges_start() {
721 let text = "Hello 🗻12∈45🌏 World";
722 let mut chars = text.char_ranges();
723 while let Some((r, _c)) = chars.next_back() {
724 assert_eq!(chars.as_str(), &text[..r.start]);
725 }
726 }
727
728 #[test]
729 fn test_char_ranges_end() {
730 let text = "Hello 🗻12∈45🌏 World";
731 let mut chars = text.char_ranges();
732 while let Some((r, _c)) = chars.next() {
733 assert_eq!(chars.as_str(), &text[r.end..]);
734 }
735 }
736
737 #[test]
738 fn test_full_range() {
739 let text = "Hello 🗻12∈45🌏 World\n";
740 let mut chars = text.char_ranges();
741 while let Some((first, _)) = chars.next() {
742 let (last, _) = chars.next_back().unwrap();
743 assert_eq!(chars.as_str(), &text[first.end..last.start]);
744 }
745 }
746
747 #[test]
748 fn test_offset() {
749 let text = "Hello 👋 World 🌏";
750 let mut chars = text.char_ranges();
751
752 let emoji_end = {
753 assert_eq!(chars.next(), Some((0..1, 'H')));
754 assert_eq!(chars.next(), Some((1..2, 'e')));
755 assert_eq!(chars.next(), Some((2..3, 'l')));
756 assert_eq!(chars.next(), Some((3..4, 'l')));
757 assert_eq!(chars.next(), Some((4..5, 'o')));
758 assert_eq!(chars.next(), Some((5..6, ' ')));
759
760 let emoji_waving_hand = chars.next();
761 assert_eq!(emoji_waving_hand, Some((6..10, '👋')));
762
763 emoji_waving_hand.unwrap().0.end
764 };
765
766 let offset_chars = text[emoji_end..].char_ranges().offset(emoji_end);
767 assert_eq!(chars.as_str(), offset_chars.as_str());
768
769 for offset_char in offset_chars {
770 assert_eq!(chars.next(), Some(offset_char));
771 }
772
773 assert_eq!(chars.next(), None);
774 }
775}