1#![forbid(unsafe_code)]
2
3use crate::buffer::Buffer;
47use crate::cell::GraphemeId;
48use ahash::AHashMap;
49
50#[derive(Debug, Clone)]
52struct GraphemeSlot {
53 text: String,
55 #[allow(dead_code)]
58 width: u8,
59 refcount: u32,
61}
62
63#[derive(Debug, Clone)]
67pub struct GraphemePool {
68 slots: Vec<Option<GraphemeSlot>>,
70 generations: Vec<u16>,
72 lookup: AHashMap<String, GraphemeId>,
74 free_list: Vec<u32>,
76}
77
78impl GraphemePool {
79 pub fn new() -> Self {
81 Self {
82 slots: Vec::new(),
83 generations: Vec::new(),
84 lookup: AHashMap::new(),
85 free_list: Vec::new(),
86 }
87 }
88
89 pub fn with_capacity(capacity: usize) -> Self {
91 Self {
92 slots: Vec::with_capacity(capacity),
93 generations: Vec::with_capacity(capacity),
94 lookup: AHashMap::with_capacity(capacity),
95 free_list: Vec::new(),
96 }
97 }
98
99 #[inline]
101 pub fn len(&self) -> usize {
102 self.slots.len().saturating_sub(self.free_list.len())
103 }
104
105 #[inline]
107 pub fn is_empty(&self) -> bool {
108 self.len() == 0
109 }
110
111 #[inline]
113 pub fn capacity(&self) -> usize {
114 self.slots.capacity()
115 }
116
117 pub fn intern(&mut self, text: &str, width: u8) -> GraphemeId {
131 assert!(width <= GraphemeId::MAX_WIDTH, "width overflow");
132
133 if let Some(&id) = self.lookup.get(text) {
135 debug_assert_eq!(
137 id.generation(),
138 self.generations[id.slot()],
139 "intern lookup returned stale ID"
140 );
141 debug_assert_eq!(
142 id.width() as u8,
143 width,
144 "intern() called with different width for the same text {:?}: existing={}, new={}",
145 text,
146 id.width(),
147 width
148 );
149 self.retain(id);
150 return id;
151 }
152
153 let slot_idx = self.alloc_slot();
155 let generation;
156
157 if (slot_idx as usize) < self.generations.len() {
158 self.generations[slot_idx as usize] =
160 self.generations[slot_idx as usize].wrapping_add(1) & GraphemeId::MAX_GENERATION;
161 generation = self.generations[slot_idx as usize];
162 } else {
163 generation = 0;
165 self.generations.push(0);
166 }
167
168 let id = GraphemeId::new(slot_idx, generation, width);
169
170 let slot = GraphemeSlot {
172 text: text.to_string(),
173 width,
174 refcount: 1,
175 };
176
177 if (slot_idx as usize) < self.slots.len() {
178 self.slots[slot_idx as usize] = Some(slot);
179 } else {
180 debug_assert_eq!(slot_idx as usize, self.slots.len());
181 self.slots.push(Some(slot));
182 }
183
184 self.lookup.insert(text.to_string(), id);
185 id
186 }
187
188 #[must_use]
192 pub fn get(&self, id: GraphemeId) -> Option<&str> {
193 let slot_idx = id.slot();
194 if let Some(&slot_gen) = self.generations.get(slot_idx) {
196 if slot_gen != id.generation() {
197 return None;
198 }
199 } else {
200 return None;
201 }
202
203 self.slots
204 .get(slot_idx)
205 .and_then(|slot| slot.as_ref())
206 .map(|slot| slot.text.as_str())
207 }
208
209 pub fn retain(&mut self, id: GraphemeId) {
213 let slot_idx = id.slot();
214 if let Some(&slot_gen) = self.generations.get(slot_idx) {
216 if slot_gen != id.generation() {
217 return;
218 }
219 } else {
220 return;
221 }
222
223 if let Some(Some(slot)) = self.slots.get_mut(slot_idx) {
224 slot.refcount = slot.refcount.saturating_add(1);
225 }
226 }
227
228 pub fn release(&mut self, id: GraphemeId) {
233 let slot_idx = id.slot();
234 if let Some(&slot_gen) = self.generations.get(slot_idx) {
236 if slot_gen != id.generation() {
237 return;
238 }
239 } else {
240 return;
241 }
242
243 if let Some(Some(slot)) = self.slots.get_mut(slot_idx) {
244 if slot.refcount == 0 {
245 debug_assert!(false, "double-free of grapheme slot {slot_idx}");
246 return;
247 }
248 slot.refcount -= 1;
249 if slot.refcount == 0 {
250 self.lookup.remove(&slot.text);
252 self.slots[slot_idx] = None;
254 self.free_list.push(slot_idx as u32);
256 }
257 }
258 }
259
260 pub fn refcount(&self, id: GraphemeId) -> u32 {
264 let slot_idx = id.slot();
265 if let Some(&slot_gen) = self.generations.get(slot_idx) {
266 if slot_gen != id.generation() {
267 return 0;
268 }
269 } else {
270 return 0;
271 }
272
273 self.slots
274 .get(slot_idx)
275 .and_then(|slot| slot.as_ref())
276 .map(|slot| slot.refcount)
277 .unwrap_or(0)
278 }
279
280 pub fn clear(&mut self) {
288 self.lookup.clear();
289 self.free_list.clear();
290 self.slots.fill(None);
291 for generation in &mut self.generations {
292 *generation = generation.wrapping_add(1) & GraphemeId::MAX_GENERATION;
293 }
294 self.free_list.extend((0..self.slots.len() as u32).rev());
297 }
298
299 fn alloc_slot(&mut self) -> u32 {
301 if let Some(idx) = self.free_list.pop() {
302 idx
303 } else {
304 let idx = self.slots.len() as u32;
305 assert!(
306 idx <= GraphemeId::MAX_SLOT,
307 "grapheme pool capacity exceeded"
308 );
309 idx
310 }
311 }
312
313 pub fn gc(&mut self, buffers: &[&Buffer]) {
323 for slot in self.slots.iter_mut().flatten() {
325 slot.refcount = 0;
326 }
327
328 for buf in buffers {
330 for cell in buf.cells() {
331 if let Some(id) = cell.content.grapheme_id() {
332 let slot_idx = id.slot();
335 if let Some(Some(slot)) = self.slots.get_mut(slot_idx) {
336 if let Some(&slot_gen) = self.generations.get(slot_idx)
338 && slot_gen == id.generation()
339 {
340 slot.refcount = slot.refcount.saturating_add(1);
341 }
342 }
343 }
344 }
345 }
346
347 let mut keys_to_remove = Vec::new();
350
351 for (idx, slot_opt) in self.slots.iter_mut().enumerate() {
352 let should_free = slot_opt.as_ref().is_some_and(|s| s.refcount == 0);
354
355 if should_free {
356 if let Some(dead_slot) = slot_opt.take() {
358 keys_to_remove.push(dead_slot.text);
359 self.generations[idx] =
362 self.generations[idx].wrapping_add(1) & GraphemeId::MAX_GENERATION;
363 self.free_list.push(idx as u32);
364 }
365 }
366 }
367
368 for text in keys_to_remove {
369 self.lookup.remove(&text);
370 }
371 }
372}
373
374impl Default for GraphemePool {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn intern_and_get() {
386 let mut pool = GraphemePool::new();
387 let id = pool.intern("๐จโ๐ฉโ๐งโ๐ฆ", 2);
388
389 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐งโ๐ฆ"));
390 assert_eq!(id.width(), 2);
391 }
392
393 #[test]
394 fn deduplication() {
395 let mut pool = GraphemePool::new();
396 let id1 = pool.intern("๐", 2);
397 let id2 = pool.intern("๐", 2);
398
399 assert_eq!(id1, id2);
401 assert_eq!(pool.refcount(id1), 2);
403 assert_eq!(pool.len(), 1);
405 }
406
407 #[test]
408 fn retain_and_release() {
409 let mut pool = GraphemePool::new();
410 let id = pool.intern("๐", 2);
411 assert_eq!(pool.refcount(id), 1);
412
413 pool.retain(id);
414 assert_eq!(pool.refcount(id), 2);
415
416 pool.release(id);
417 assert_eq!(pool.refcount(id), 1);
418
419 pool.release(id);
420 assert_eq!(pool.get(id), None);
422 assert_eq!(pool.len(), 0);
423 }
424
425 #[test]
426 fn slot_reuse() {
427 let mut pool = GraphemePool::new();
428
429 let id1 = pool.intern("A", 1);
431 pool.release(id1);
432 assert_eq!(pool.len(), 0);
433
434 let id2 = pool.intern("B", 1);
436 assert_eq!(id1.slot(), id2.slot());
437 assert_eq!(pool.get(id2), Some("B"));
438 }
439
440 #[test]
441 fn empty_pool() {
442 let pool = GraphemePool::new();
443 assert!(pool.is_empty());
444 assert_eq!(pool.len(), 0);
445 }
446
447 #[test]
448 fn multiple_graphemes() {
449 let mut pool = GraphemePool::new();
450
451 let id1 = pool.intern("๐จโ๐ป", 2);
452 let id2 = pool.intern("๐ฉโ๐ฌ", 2);
453 let id3 = pool.intern("๐ง๐ฝโ๐", 2);
454
455 assert_eq!(pool.len(), 3);
456 assert_ne!(id1, id2);
457 assert_ne!(id2, id3);
458
459 assert_eq!(pool.get(id1), Some("๐จโ๐ป"));
460 assert_eq!(pool.get(id2), Some("๐ฉโ๐ฌ"));
461 assert_eq!(pool.get(id3), Some("๐ง๐ฝโ๐"));
462 }
463
464 #[test]
465 fn width_preserved() {
466 let mut pool = GraphemePool::new();
467
468 let id1 = pool.intern("๐", 2);
470 let id2 = pool.intern("A", 1);
471 let id3 = pool.intern("ๆฅ", 2);
472
473 assert_eq!(id1.width(), 2);
474 assert_eq!(id2.width(), 1);
475 assert_eq!(id3.width(), 2);
476 }
477
478 #[test]
479 fn clear_pool() {
480 let mut pool = GraphemePool::new();
481 pool.intern("A", 1);
482 pool.intern("B", 1);
483 pool.intern("C", 1);
484
485 assert_eq!(pool.len(), 3);
486
487 pool.clear();
488 assert!(pool.is_empty());
489 }
490
491 #[test]
492 fn invalid_id_returns_none() {
493 let pool = GraphemePool::new();
494 let fake_id = GraphemeId::new(999, 0, 1);
495 assert_eq!(pool.get(fake_id), None);
496 }
497
498 #[test]
499 fn release_invalid_id_is_safe() {
500 let mut pool = GraphemePool::new();
501 let fake_id = GraphemeId::new(999, 0, 1);
502 pool.release(fake_id); }
504
505 #[test]
506 fn retain_invalid_id_is_safe() {
507 let mut pool = GraphemePool::new();
508 let fake_id = GraphemeId::new(999, 0, 1);
509 pool.retain(fake_id); }
511
512 #[test]
513 fn stale_generation_returns_none() {
514 let mut pool = GraphemePool::new();
515 let id1 = pool.intern("A", 1);
516 pool.release(id1);
517
518 let id2 = pool.intern("B", 1);
520 assert_eq!(id1.slot(), id2.slot());
521 assert_ne!(id1.generation(), id2.generation());
522
523 assert_eq!(pool.get(id1), None);
525 assert_eq!(pool.get(id2), Some("B"));
526 }
527
528 #[test]
529 #[should_panic(expected = "width overflow")]
530 fn width_overflow_panics() {
531 let mut pool = GraphemePool::new();
532 pool.intern("X", GraphemeId::MAX_WIDTH + 1);
533 }
534
535 #[test]
536 fn with_capacity() {
537 let pool = GraphemePool::with_capacity(100);
538 assert!(pool.capacity() >= 100);
539 assert!(pool.is_empty());
540 }
541
542 mod gc_tests {
543 use super::*;
544 use crate::buffer::Buffer;
545 use crate::cell::{Cell, CellContent};
546
547 fn buf_with_grapheme(id: GraphemeId) -> Buffer {
549 let mut buf = Buffer::new(4, 1);
550 let content = CellContent::from_grapheme(id);
551 buf.set(0, 0, Cell::new(content));
552 buf
553 }
554
555 #[test]
556 fn gc_retains_referenced_grapheme() {
557 let mut pool = GraphemePool::new();
558 let id = pool.intern("๐", 2);
559
560 let buf = buf_with_grapheme(id);
561 pool.gc(&[&buf]);
562
563 assert_eq!(pool.get(id), Some("๐"));
564 assert_eq!(pool.refcount(id), 1);
565 }
566
567 #[test]
568 fn gc_frees_unreferenced_grapheme() {
569 let mut pool = GraphemePool::new();
570 let id = pool.intern("๐", 2);
571
572 let buf = Buffer::new(4, 1);
574 pool.gc(&[&buf]);
575
576 assert_eq!(pool.get(id), None);
577 assert_eq!(pool.refcount(id), 0);
578 assert!(pool.is_empty());
579 }
580
581 #[test]
582 fn gc_with_multiple_buffers() {
583 let mut pool = GraphemePool::new();
584 let id1 = pool.intern("๐", 2);
585 let id2 = pool.intern("๐งช", 2);
586 let id3 = pool.intern("๐ฅ", 2);
587
588 let buf1 = buf_with_grapheme(id1);
590 let buf2 = buf_with_grapheme(id3);
591
592 pool.gc(&[&buf1, &buf2]);
593
594 assert_eq!(pool.get(id1), Some("๐"));
595 assert_eq!(pool.get(id2), None); assert_eq!(pool.get(id3), Some("๐ฅ"));
597 assert_eq!(pool.len(), 2);
598 }
599
600 #[test]
601 fn gc_with_multiple_references_in_buffer() {
602 let mut pool = GraphemePool::new();
603 let id = pool.intern("๐จโ๐ฉโ๐ง", 2);
604
605 let mut buf = Buffer::new(4, 1);
607 let content = CellContent::from_grapheme(id);
608 buf.set(0, 0, Cell::new(content));
609 buf.set(2, 0, Cell::new(content));
610
611 pool.gc(&[&buf]);
612
613 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐ง"));
614 assert_eq!(pool.refcount(id), 2);
615 }
616
617 #[test]
618 fn gc_with_empty_pool() {
619 let mut pool = GraphemePool::new();
620 let buf = Buffer::new(4, 1);
621 pool.gc(&[&buf]); assert!(pool.is_empty());
623 }
624
625 #[test]
626 fn gc_with_no_buffers() {
627 let mut pool = GraphemePool::new();
628 let id = pool.intern("test", 1);
629 pool.gc(&[]);
630 assert_eq!(pool.get(id), None);
632 assert!(pool.is_empty());
633 }
634
635 #[test]
636 fn gc_freed_slots_are_reusable() {
637 let mut pool = GraphemePool::new();
638 let id1 = pool.intern("A", 1);
639 let _id2 = pool.intern("B", 1);
640 let slot1 = id1.slot();
641
642 let buf = buf_with_grapheme(id1);
644 pool.gc(&[&buf]);
645
646 let id3 = pool.intern("C", 1);
648 assert_eq!(pool.get(id3), Some("C"));
650 assert_eq!(pool.len(), 2); assert_eq!(pool.get(id1), Some("A"));
654 assert_eq!(id1.slot(), slot1);
655 }
656
657 #[test]
658 fn gc_resets_refcounts_accurately() {
659 let mut pool = GraphemePool::new();
660 let id = pool.intern("๐", 2);
661
662 pool.retain(id);
664 pool.retain(id);
665 assert_eq!(pool.refcount(id), 3);
666
667 let buf = buf_with_grapheme(id);
669 pool.gc(&[&buf]);
670
671 assert_eq!(pool.refcount(id), 1);
673 }
674
675 #[test]
676 fn gc_lookup_table_stays_consistent() {
677 let mut pool = GraphemePool::new();
678 let _id1 = pool.intern("A", 1);
679 let id2 = pool.intern("B", 1);
680
681 let buf = buf_with_grapheme(id2);
683 pool.gc(&[&buf]);
684
685 let id_new = pool.intern("A", 1);
687 assert_eq!(pool.get(id_new), Some("A"));
688
689 let id_b2 = pool.intern("B", 1);
691 assert_eq!(id_b2, id2);
692 }
693 }
694
695 mod property {
696 use super::*;
697 use proptest::prelude::*;
698
699 fn arb_grapheme() -> impl Strategy<Value = String> {
701 prop::string::string_regex(".{1,8}")
702 .unwrap()
703 .prop_filter("non-empty", |s| !s.is_empty())
704 }
705
706 fn arb_width() -> impl Strategy<Value = u8> {
708 0u8..=GraphemeId::MAX_WIDTH
709 }
710
711 proptest! {
712 #![proptest_config(ProptestConfig::with_cases(256))]
713
714 #[test]
716 fn intern_get_roundtrip(s in arb_grapheme(), w in arb_width()) {
717 let mut pool = GraphemePool::new();
718 let id = pool.intern(&s, w);
719 prop_assert_eq!(pool.get(id), Some(s.as_str()));
720 }
721
722 #[test]
724 fn intern_preserves_width(s in arb_grapheme(), w in arb_width()) {
725 let mut pool = GraphemePool::new();
726 let id = pool.intern(&s, w);
727 prop_assert_eq!(id.width(), w as usize);
728 }
729
730 #[test]
732 fn deduplication_same_id(s in arb_grapheme(), w in arb_width()) {
733 let mut pool = GraphemePool::new();
734 let id1 = pool.intern(&s, w);
735 let id2 = pool.intern(&s, w);
736 prop_assert_eq!(id1, id2);
737 prop_assert_eq!(pool.len(), 1);
738 }
739
740 #[test]
742 fn deduplication_refcount(s in arb_grapheme(), w in arb_width(), extra in 0u32..10) {
743 let mut pool = GraphemePool::new();
744 let id = pool.intern(&s, w);
745 for _ in 0..extra {
746 pool.intern(&s, w);
747 }
748 prop_assert_eq!(pool.refcount(id), 1 + extra);
749 }
750
751 #[test]
753 fn retain_release_refcount(
754 s in arb_grapheme(),
755 w in arb_width(),
756 retains in 0u32..10,
757 releases in 0u32..10
758 ) {
759 let mut pool = GraphemePool::new();
760 let id = pool.intern(&s, w);
761 for _ in 0..retains {
763 pool.retain(id);
764 }
765 let expected_after_retain = 1 + retains;
766 prop_assert_eq!(pool.refcount(id), expected_after_retain);
767
768 let actual_releases = releases.min(expected_after_retain - 1);
769 for _ in 0..actual_releases {
770 pool.release(id);
771 }
772 prop_assert_eq!(pool.refcount(id), expected_after_retain - actual_releases);
773 prop_assert_eq!(pool.get(id), Some(s.as_str()));
775 }
776
777 #[test]
779 fn release_to_zero_frees(s in arb_grapheme(), w in arb_width(), extra in 0u32..5) {
780 let mut pool = GraphemePool::new();
781 let id = pool.intern(&s, w);
782 for _ in 0..extra {
783 pool.retain(id);
784 }
785 for _ in 0..=extra {
787 pool.release(id);
788 }
789 prop_assert_eq!(pool.get(id), None);
790 prop_assert_eq!(pool.refcount(id), 0);
791 prop_assert!(pool.is_empty());
792 }
793
794 #[test]
796 fn slot_reuse_after_free(
797 s1 in arb_grapheme(),
798 s2 in arb_grapheme(),
799 w in arb_width()
800 ) {
801 let mut pool = GraphemePool::new();
802 let id1 = pool.intern(&s1, w);
803 let slot1 = id1.slot();
804 pool.release(id1);
805
806 let id2 = pool.intern(&s2, w);
808 prop_assert_eq!(id2.slot(), slot1);
809 prop_assert_eq!(pool.get(id2), Some(s2.as_str()));
810 }
811
812 #[test]
814 fn len_invariant(count in 1usize..20) {
815 let mut pool = GraphemePool::new();
816 let mut ids = Vec::new();
817 for i in 0..count {
818 let s = format!("g{i}");
819 ids.push(pool.intern(&s, 1));
820 }
821 prop_assert_eq!(pool.len(), count);
822
823 let release_count = count / 2;
825 for id in &ids[..release_count] {
826 pool.release(*id);
827 }
828 prop_assert_eq!(pool.len(), count - release_count);
829 }
830
831 #[test]
833 fn distinct_strings_distinct_ids(count in 2usize..15) {
834 let mut pool = GraphemePool::new();
835 let mut ids = Vec::new();
836 for i in 0..count {
837 let s = format!("unique_{i}");
838 ids.push(pool.intern(&s, 1));
839 }
840 for i in 0..ids.len() {
842 for j in (i + 1)..ids.len() {
843 prop_assert_ne!(ids[i], ids[j]);
844 }
845 }
846 }
847
848 #[test]
850 fn clear_resets_all(count in 1usize..20) {
851 let mut pool = GraphemePool::new();
852 let mut ids = Vec::new();
853 for i in 0..count {
854 let s = format!("c{i}");
855 ids.push(pool.intern(&s, 1));
856 }
857 pool.clear();
858 prop_assert!(pool.is_empty());
859 prop_assert_eq!(pool.len(), 0);
860 for id in &ids {
861 prop_assert_eq!(pool.get(*id), None);
862 }
863 }
864
865 #[test]
869 fn positive_refcount_implies_valid_slot(
870 count in 1usize..10,
871 retains in proptest::collection::vec(0u32..5, 1..10),
872 ) {
873 let mut pool = GraphemePool::new();
874 let mut ids = Vec::new();
875 for i in 0..count {
876 let s = format!("inv_{i}");
877 ids.push(pool.intern(&s, 1));
878 }
879
880 for (i, &extra) in retains.iter().enumerate() {
882 let id = ids[i % count];
883 for _ in 0..extra {
884 pool.retain(id);
885 }
886 }
887
888 for (i, &id) in ids.iter().enumerate() {
890 let rc = pool.refcount(id);
891 if rc > 0 {
892 prop_assert!(pool.get(id).is_some(),
893 "slot {} has refcount {} but get() returned None", i, rc);
894 }
895 }
896 }
897
898 #[test]
900 fn release_decrements_by_one(s in arb_grapheme(), w in arb_width(), retains in 1u32..8) {
901 let mut pool = GraphemePool::new();
902 let id = pool.intern(&s, w);
903 for _ in 0..retains {
904 pool.retain(id);
905 }
906 let rc_before = pool.refcount(id);
907 pool.release(id);
908 let rc_after = pool.refcount(id);
909 prop_assert_eq!(rc_after, rc_before - 1,
910 "release should decrement refcount by exactly 1");
911 }
912
913 #[test]
915 fn over_release_does_not_corrupt(count in 1usize..5) {
916 let mut pool = GraphemePool::new();
917 let mut ids = Vec::new();
918 for i in 0..count {
919 let s = format!("or_{i}");
920 ids.push(pool.intern(&s, 1));
921 }
922
923 let victim = ids[0];
925 pool.release(victim);
926 prop_assert_eq!(pool.refcount(victim), 0);
927 prop_assert_eq!(pool.get(victim), None);
928
929 pool.release(victim);
931 prop_assert_eq!(pool.refcount(victim), 0);
932
933 for &id in &ids[1..] {
935 prop_assert!(pool.get(id).is_some(),
936 "over-release corrupted unrelated slot");
937 prop_assert!(pool.refcount(id) > 0);
938 }
939 }
940
941 #[test]
943 fn cross_pool_id_is_invalid(s in arb_grapheme(), w in arb_width()) {
944 let mut pool_a = GraphemePool::new();
945 let pool_b = GraphemePool::new();
946 let id = pool_a.intern(&s, w);
947
948 prop_assert_eq!(pool_b.get(id), None,
950 "GraphemeId from pool A should not be valid in pool B");
951 }
952 }
953 }
954
955 #[test]
958 fn pool_debug_and_clone() {
959 let mut pool = GraphemePool::new();
960 pool.intern("๐", 2);
961 let dbg = format!("{:?}", pool);
962 assert!(dbg.contains("GraphemePool"), "Debug: {dbg}");
963 let cloned = pool.clone();
964 assert_eq!(cloned.len(), 1);
965 let id = cloned.lookup.values().next().copied().unwrap();
967 assert_eq!(cloned.get(id), Some("๐"));
968 }
969
970 #[test]
971 fn pool_default_is_new() {
972 let pool = GraphemePool::default();
973 assert!(pool.is_empty());
974 assert_eq!(pool.len(), 0);
975 }
976
977 #[test]
978 fn intern_width_zero() {
979 let mut pool = GraphemePool::new();
980 let id = pool.intern("zero-width", 0);
981 assert_eq!(id.width(), 0);
982 assert_eq!(pool.get(id), Some("zero-width"));
983 }
984
985 #[test]
986 fn intern_width_max() {
987 let mut pool = GraphemePool::new();
988 let id = pool.intern("max-width", GraphemeId::MAX_WIDTH);
989 assert_eq!(id.width(), GraphemeId::MAX_WIDTH as usize);
990 assert_eq!(pool.get(id), Some("max-width"));
991 }
992
993 #[test]
994 fn intern_empty_string() {
995 let mut pool = GraphemePool::new();
996 let id = pool.intern("", 0);
997 assert_eq!(pool.get(id), Some(""));
998 }
999
1000 #[test]
1001 fn intern_long_string() {
1002 let mut pool = GraphemePool::new();
1003 let long = "a".repeat(1000);
1004 let id = pool.intern(&long, 1);
1005 assert_eq!(pool.get(id), Some(long.as_str()));
1006 }
1007
1008 #[test]
1009 fn clear_then_intern_reuses_from_scratch() {
1010 let mut pool = GraphemePool::new();
1011 pool.intern("A", 1);
1012 pool.intern("B", 1);
1013 pool.clear();
1014 assert!(pool.is_empty());
1015 let id = pool.intern("C", 1);
1017 assert_eq!(id.slot(), 0);
1018 assert_eq!(pool.get(id), Some("C"));
1019 assert_eq!(pool.len(), 1);
1020 }
1021
1022 #[test]
1023 fn id_held_across_clear_never_aliases_new_entry() {
1024 let mut pool = GraphemePool::new();
1028 let old_id = pool.intern("A", 1);
1029 pool.clear();
1030
1031 let new_id = pool.intern("B", 1);
1032 assert_eq!(old_id.slot(), new_id.slot(), "same slot reused");
1033 assert_ne!(old_id.generation(), new_id.generation());
1034 assert_eq!(pool.get(old_id), None, "stale ID must not resolve");
1035 assert_eq!(pool.get(new_id), Some("B"));
1036
1037 pool.retain(old_id);
1039 pool.release(old_id);
1040 assert_eq!(pool.refcount(new_id), 1);
1041 }
1042
1043 #[test]
1044 fn with_capacity_then_intern() {
1045 let mut pool = GraphemePool::with_capacity(50);
1046 for i in 0..50 {
1047 pool.intern(&format!("g{i}"), 1);
1048 }
1049 assert_eq!(pool.len(), 50);
1050 }
1051
1052 #[test]
1053 fn refcount_of_freed_slot_is_zero() {
1054 let mut pool = GraphemePool::new();
1055 let id = pool.intern("temp", 1);
1056 pool.release(id);
1057 assert_eq!(pool.refcount(id), 0);
1058 }
1059
1060 #[test]
1061 fn refcount_of_invalid_id_is_zero() {
1062 let pool = GraphemePool::new();
1063 assert_eq!(pool.refcount(GraphemeId::new(0, 0, 1)), 0);
1064 assert_eq!(pool.refcount(GraphemeId::new(999, 0, 1)), 0);
1065 }
1066
1067 #[test]
1068 fn retain_freed_slot_is_noop() {
1069 let mut pool = GraphemePool::new();
1070 let id = pool.intern("temp", 1);
1071 pool.release(id);
1072 pool.retain(id); assert_eq!(pool.refcount(id), 0);
1075 assert_eq!(pool.get(id), None);
1076 }
1077
1078 #[test]
1079 fn double_release_is_safe() {
1080 let mut pool = GraphemePool::new();
1081 let id = pool.intern("temp", 1);
1082 pool.release(id); pool.release(id); assert_eq!(pool.refcount(id), 0);
1085 }
1086
1087 #[test]
1088 fn multiple_slot_reuse_cycles() {
1089 let mut pool = GraphemePool::new();
1090 for cycle in 0..5 {
1091 let id = pool.intern(&format!("cycle{cycle}"), 1);
1092 assert_eq!(id.slot(), 0); assert_eq!(pool.get(id), Some(format!("cycle{cycle}").as_str()));
1094 pool.release(id);
1095 }
1096 assert!(pool.is_empty());
1097 }
1098
1099 #[test]
1100 fn free_list_ordering() {
1101 let mut pool = GraphemePool::new();
1102 let id0 = pool.intern("A", 1);
1103 let id1 = pool.intern("B", 1);
1104 let id2 = pool.intern("C", 1);
1105 assert_eq!(id0.slot(), 0);
1106 assert_eq!(id1.slot(), 1);
1107 assert_eq!(id2.slot(), 2);
1108
1109 pool.release(id0);
1111 pool.release(id2);
1112 assert_eq!(pool.len(), 1); let new1 = pool.intern("D", 1);
1116 assert_eq!(new1.slot(), 2);
1117 let new2 = pool.intern("E", 1);
1118 assert_eq!(new2.slot(), 0);
1119 }
1120
1121 #[test]
1122 fn intern_after_release_deduplicates_correctly() {
1123 let mut pool = GraphemePool::new();
1124 let id1 = pool.intern("X", 1);
1125 pool.release(id1);
1126 assert_eq!(pool.get(id1), None);
1128
1129 let id2 = pool.intern("X", 1);
1131 assert_eq!(pool.get(id2), Some("X"));
1132 assert_eq!(pool.refcount(id2), 1);
1133 }
1134
1135 #[test]
1136 fn clone_independence() {
1137 let mut pool = GraphemePool::new();
1138 let id = pool.intern("shared", 1);
1139
1140 let mut cloned = pool.clone();
1141 pool.release(id);
1143 assert_eq!(pool.get(id), None);
1144
1145 assert_eq!(cloned.get(id), Some("shared"));
1147 assert_eq!(cloned.refcount(id), 1);
1148
1149 cloned.retain(id);
1151 assert_eq!(cloned.refcount(id), 2);
1152 assert_eq!(pool.refcount(id), 0);
1154 }
1155
1156 #[test]
1157 fn gc_double_run_idempotent() {
1158 use crate::buffer::Buffer;
1159 use crate::cell::{Cell, CellContent};
1160
1161 let mut pool = GraphemePool::new();
1162 let id = pool.intern("keep", 1);
1163 let _id2 = pool.intern("drop", 1);
1164
1165 let mut buf = Buffer::new(4, 1);
1166 buf.set(0, 0, Cell::new(CellContent::from_grapheme(id)));
1167
1168 pool.gc(&[&buf]);
1169 assert_eq!(pool.len(), 1);
1170 assert_eq!(pool.get(id), Some("keep"));
1171
1172 pool.gc(&[&buf]);
1174 assert_eq!(pool.len(), 1);
1175 assert_eq!(pool.refcount(id), 1);
1176 }
1177
1178 #[test]
1179 fn gc_with_already_freed_slots() {
1180 use crate::buffer::Buffer;
1181
1182 let mut pool = GraphemePool::new();
1183 let id1 = pool.intern("A", 1);
1184 let id2 = pool.intern("B", 1);
1185
1186 pool.release(id1);
1188 assert_eq!(pool.len(), 1);
1189
1190 let buf = Buffer::new(4, 1);
1192 pool.gc(&[&buf]);
1193
1194 assert!(pool.is_empty());
1195 assert_eq!(pool.get(id2), None);
1196 }
1197
1198 #[test]
1199 fn stress_100_graphemes() {
1200 let mut pool = GraphemePool::new();
1201 let mut ids = Vec::new();
1202 for i in 0..100 {
1203 ids.push(pool.intern(&format!("g{i:03}"), 1));
1204 }
1205 assert_eq!(pool.len(), 100);
1206
1207 for (i, &id) in ids.iter().enumerate() {
1209 assert_eq!(pool.get(id), Some(format!("g{i:03}").as_str()));
1210 }
1211
1212 for i in (0..100).step_by(2) {
1214 pool.release(ids[i]);
1215 }
1216 assert_eq!(pool.len(), 50);
1217
1218 for i in (1..100).step_by(2) {
1220 assert_eq!(pool.get(ids[i]), Some(format!("g{i:03}").as_str()));
1221 }
1222 }
1223
1224 #[test]
1225 fn capacity_grows_with_interns() {
1226 let mut pool = GraphemePool::new();
1227 let cap_before = pool.capacity();
1228 for i in 0..20 {
1229 pool.intern(&format!("grow{i}"), 1);
1230 }
1231 assert!(pool.capacity() >= 20);
1233 assert!(pool.capacity() >= cap_before);
1234 }
1235
1236 #[test]
1237 fn len_after_mixed_operations() {
1238 let mut pool = GraphemePool::new();
1239 assert_eq!(pool.len(), 0);
1240
1241 let a = pool.intern("A", 1);
1242 assert_eq!(pool.len(), 1);
1243
1244 let b = pool.intern("B", 1);
1245 assert_eq!(pool.len(), 2);
1246
1247 pool.intern("A", 1);
1249 assert_eq!(pool.len(), 2);
1250
1251 pool.release(a);
1252 assert_eq!(pool.len(), 2);
1254
1255 pool.release(a);
1256 assert_eq!(pool.len(), 1);
1258
1259 pool.release(b);
1260 assert_eq!(pool.len(), 0);
1261 assert!(pool.is_empty());
1262 }
1263
1264 #[test]
1265 fn generation_overflow_handling() {
1266 let mut pool = GraphemePool::new();
1267 let id = pool.intern("initial", 0);
1269 pool.release(id); for i in 0..=GraphemeId::MAX_GENERATION {
1274 let s = format!("g{}", i);
1275 let id = pool.intern(&s, 0);
1276 assert_eq!(id.slot(), 0);
1277 pool.release(id);
1278 }
1279
1280 let id_overflow = pool.intern("overflow", 0);
1285
1286 assert_eq!(pool.get(id_overflow), Some("overflow"));
1288
1289 assert_eq!(id_overflow.width(), 0);
1291 }
1292}