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) {
282 self.slots.clear();
283 self.generations.clear();
284 self.lookup.clear();
285 self.free_list.clear();
286 }
287
288 fn alloc_slot(&mut self) -> u32 {
290 if let Some(idx) = self.free_list.pop() {
291 idx
292 } else {
293 let idx = self.slots.len() as u32;
294 assert!(
295 idx <= GraphemeId::MAX_SLOT,
296 "grapheme pool capacity exceeded"
297 );
298 idx
299 }
300 }
301
302 pub fn gc(&mut self, buffers: &[&Buffer]) {
312 for slot in self.slots.iter_mut().flatten() {
314 slot.refcount = 0;
315 }
316
317 for buf in buffers {
319 for cell in buf.cells() {
320 if let Some(id) = cell.content.grapheme_id() {
321 let slot_idx = id.slot();
324 if let Some(Some(slot)) = self.slots.get_mut(slot_idx) {
325 if let Some(&slot_gen) = self.generations.get(slot_idx)
327 && slot_gen == id.generation()
328 {
329 slot.refcount = slot.refcount.saturating_add(1);
330 }
331 }
332 }
333 }
334 }
335
336 let mut keys_to_remove = Vec::new();
339
340 for (idx, slot_opt) in self.slots.iter_mut().enumerate() {
341 let should_free = slot_opt.as_ref().is_some_and(|s| s.refcount == 0);
343
344 if should_free {
345 if let Some(dead_slot) = slot_opt.take() {
347 keys_to_remove.push(dead_slot.text);
348 self.generations[idx] = self.generations[idx].wrapping_add(1);
349 self.free_list.push(idx as u32);
350 }
351 }
352 }
353
354 for text in keys_to_remove {
355 self.lookup.remove(&text);
356 }
357 }
358}
359
360impl Default for GraphemePool {
361 fn default() -> Self {
362 Self::new()
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn intern_and_get() {
372 let mut pool = GraphemePool::new();
373 let id = pool.intern("๐จโ๐ฉโ๐งโ๐ฆ", 2);
374
375 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐งโ๐ฆ"));
376 assert_eq!(id.width(), 2);
377 }
378
379 #[test]
380 fn deduplication() {
381 let mut pool = GraphemePool::new();
382 let id1 = pool.intern("๐", 2);
383 let id2 = pool.intern("๐", 2);
384
385 assert_eq!(id1, id2);
387 assert_eq!(pool.refcount(id1), 2);
389 assert_eq!(pool.len(), 1);
391 }
392
393 #[test]
394 fn retain_and_release() {
395 let mut pool = GraphemePool::new();
396 let id = pool.intern("๐", 2);
397 assert_eq!(pool.refcount(id), 1);
398
399 pool.retain(id);
400 assert_eq!(pool.refcount(id), 2);
401
402 pool.release(id);
403 assert_eq!(pool.refcount(id), 1);
404
405 pool.release(id);
406 assert_eq!(pool.get(id), None);
408 assert_eq!(pool.len(), 0);
409 }
410
411 #[test]
412 fn slot_reuse() {
413 let mut pool = GraphemePool::new();
414
415 let id1 = pool.intern("A", 1);
417 pool.release(id1);
418 assert_eq!(pool.len(), 0);
419
420 let id2 = pool.intern("B", 1);
422 assert_eq!(id1.slot(), id2.slot());
423 assert_eq!(pool.get(id2), Some("B"));
424 }
425
426 #[test]
427 fn empty_pool() {
428 let pool = GraphemePool::new();
429 assert!(pool.is_empty());
430 assert_eq!(pool.len(), 0);
431 }
432
433 #[test]
434 fn multiple_graphemes() {
435 let mut pool = GraphemePool::new();
436
437 let id1 = pool.intern("๐จโ๐ป", 2);
438 let id2 = pool.intern("๐ฉโ๐ฌ", 2);
439 let id3 = pool.intern("๐ง๐ฝโ๐", 2);
440
441 assert_eq!(pool.len(), 3);
442 assert_ne!(id1, id2);
443 assert_ne!(id2, id3);
444
445 assert_eq!(pool.get(id1), Some("๐จโ๐ป"));
446 assert_eq!(pool.get(id2), Some("๐ฉโ๐ฌ"));
447 assert_eq!(pool.get(id3), Some("๐ง๐ฝโ๐"));
448 }
449
450 #[test]
451 fn width_preserved() {
452 let mut pool = GraphemePool::new();
453
454 let id1 = pool.intern("๐", 2);
456 let id2 = pool.intern("A", 1);
457 let id3 = pool.intern("ๆฅ", 2);
458
459 assert_eq!(id1.width(), 2);
460 assert_eq!(id2.width(), 1);
461 assert_eq!(id3.width(), 2);
462 }
463
464 #[test]
465 fn clear_pool() {
466 let mut pool = GraphemePool::new();
467 pool.intern("A", 1);
468 pool.intern("B", 1);
469 pool.intern("C", 1);
470
471 assert_eq!(pool.len(), 3);
472
473 pool.clear();
474 assert!(pool.is_empty());
475 }
476
477 #[test]
478 fn invalid_id_returns_none() {
479 let pool = GraphemePool::new();
480 let fake_id = GraphemeId::new(999, 0, 1);
481 assert_eq!(pool.get(fake_id), None);
482 }
483
484 #[test]
485 fn release_invalid_id_is_safe() {
486 let mut pool = GraphemePool::new();
487 let fake_id = GraphemeId::new(999, 0, 1);
488 pool.release(fake_id); }
490
491 #[test]
492 fn retain_invalid_id_is_safe() {
493 let mut pool = GraphemePool::new();
494 let fake_id = GraphemeId::new(999, 0, 1);
495 pool.retain(fake_id); }
497
498 #[test]
499 fn stale_generation_returns_none() {
500 let mut pool = GraphemePool::new();
501 let id1 = pool.intern("A", 1);
502 pool.release(id1);
503
504 let id2 = pool.intern("B", 1);
506 assert_eq!(id1.slot(), id2.slot());
507 assert_ne!(id1.generation(), id2.generation());
508
509 assert_eq!(pool.get(id1), None);
511 assert_eq!(pool.get(id2), Some("B"));
512 }
513
514 #[test]
515 #[should_panic(expected = "width overflow")]
516 fn width_overflow_panics() {
517 let mut pool = GraphemePool::new();
518 pool.intern("X", GraphemeId::MAX_WIDTH + 1);
519 }
520
521 #[test]
522 fn with_capacity() {
523 let pool = GraphemePool::with_capacity(100);
524 assert!(pool.capacity() >= 100);
525 assert!(pool.is_empty());
526 }
527
528 mod gc_tests {
529 use super::*;
530 use crate::buffer::Buffer;
531 use crate::cell::{Cell, CellContent};
532
533 fn buf_with_grapheme(id: GraphemeId) -> Buffer {
535 let mut buf = Buffer::new(4, 1);
536 let content = CellContent::from_grapheme(id);
537 buf.set(0, 0, Cell::new(content));
538 buf
539 }
540
541 #[test]
542 fn gc_retains_referenced_grapheme() {
543 let mut pool = GraphemePool::new();
544 let id = pool.intern("๐", 2);
545
546 let buf = buf_with_grapheme(id);
547 pool.gc(&[&buf]);
548
549 assert_eq!(pool.get(id), Some("๐"));
550 assert_eq!(pool.refcount(id), 1);
551 }
552
553 #[test]
554 fn gc_frees_unreferenced_grapheme() {
555 let mut pool = GraphemePool::new();
556 let id = pool.intern("๐", 2);
557
558 let buf = Buffer::new(4, 1);
560 pool.gc(&[&buf]);
561
562 assert_eq!(pool.get(id), None);
563 assert_eq!(pool.refcount(id), 0);
564 assert!(pool.is_empty());
565 }
566
567 #[test]
568 fn gc_with_multiple_buffers() {
569 let mut pool = GraphemePool::new();
570 let id1 = pool.intern("๐", 2);
571 let id2 = pool.intern("๐งช", 2);
572 let id3 = pool.intern("๐ฅ", 2);
573
574 let buf1 = buf_with_grapheme(id1);
576 let buf2 = buf_with_grapheme(id3);
577
578 pool.gc(&[&buf1, &buf2]);
579
580 assert_eq!(pool.get(id1), Some("๐"));
581 assert_eq!(pool.get(id2), None); assert_eq!(pool.get(id3), Some("๐ฅ"));
583 assert_eq!(pool.len(), 2);
584 }
585
586 #[test]
587 fn gc_with_multiple_references_in_buffer() {
588 let mut pool = GraphemePool::new();
589 let id = pool.intern("๐จโ๐ฉโ๐ง", 2);
590
591 let mut buf = Buffer::new(4, 1);
593 let content = CellContent::from_grapheme(id);
594 buf.set(0, 0, Cell::new(content));
595 buf.set(2, 0, Cell::new(content));
596
597 pool.gc(&[&buf]);
598
599 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐ง"));
600 assert_eq!(pool.refcount(id), 2);
601 }
602
603 #[test]
604 fn gc_with_empty_pool() {
605 let mut pool = GraphemePool::new();
606 let buf = Buffer::new(4, 1);
607 pool.gc(&[&buf]); assert!(pool.is_empty());
609 }
610
611 #[test]
612 fn gc_with_no_buffers() {
613 let mut pool = GraphemePool::new();
614 let id = pool.intern("test", 1);
615 pool.gc(&[]);
616 assert_eq!(pool.get(id), None);
618 assert!(pool.is_empty());
619 }
620
621 #[test]
622 fn gc_freed_slots_are_reusable() {
623 let mut pool = GraphemePool::new();
624 let id1 = pool.intern("A", 1);
625 let _id2 = pool.intern("B", 1);
626 let slot1 = id1.slot();
627
628 let buf = buf_with_grapheme(id1);
630 pool.gc(&[&buf]);
631
632 let id3 = pool.intern("C", 1);
634 assert_eq!(pool.get(id3), Some("C"));
636 assert_eq!(pool.len(), 2); assert_eq!(pool.get(id1), Some("A"));
640 assert_eq!(id1.slot(), slot1);
641 }
642
643 #[test]
644 fn gc_resets_refcounts_accurately() {
645 let mut pool = GraphemePool::new();
646 let id = pool.intern("๐", 2);
647
648 pool.retain(id);
650 pool.retain(id);
651 assert_eq!(pool.refcount(id), 3);
652
653 let buf = buf_with_grapheme(id);
655 pool.gc(&[&buf]);
656
657 assert_eq!(pool.refcount(id), 1);
659 }
660
661 #[test]
662 fn gc_lookup_table_stays_consistent() {
663 let mut pool = GraphemePool::new();
664 let _id1 = pool.intern("A", 1);
665 let id2 = pool.intern("B", 1);
666
667 let buf = buf_with_grapheme(id2);
669 pool.gc(&[&buf]);
670
671 let id_new = pool.intern("A", 1);
673 assert_eq!(pool.get(id_new), Some("A"));
674
675 let id_b2 = pool.intern("B", 1);
677 assert_eq!(id_b2, id2);
678 }
679 }
680
681 mod property {
682 use super::*;
683 use proptest::prelude::*;
684
685 fn arb_grapheme() -> impl Strategy<Value = String> {
687 prop::string::string_regex(".{1,8}")
688 .unwrap()
689 .prop_filter("non-empty", |s| !s.is_empty())
690 }
691
692 fn arb_width() -> impl Strategy<Value = u8> {
694 0u8..=GraphemeId::MAX_WIDTH
695 }
696
697 proptest! {
698 #![proptest_config(ProptestConfig::with_cases(256))]
699
700 #[test]
702 fn intern_get_roundtrip(s in arb_grapheme(), w in arb_width()) {
703 let mut pool = GraphemePool::new();
704 let id = pool.intern(&s, w);
705 prop_assert_eq!(pool.get(id), Some(s.as_str()));
706 }
707
708 #[test]
710 fn intern_preserves_width(s in arb_grapheme(), w in arb_width()) {
711 let mut pool = GraphemePool::new();
712 let id = pool.intern(&s, w);
713 prop_assert_eq!(id.width(), w as usize);
714 }
715
716 #[test]
718 fn deduplication_same_id(s in arb_grapheme(), w in arb_width()) {
719 let mut pool = GraphemePool::new();
720 let id1 = pool.intern(&s, w);
721 let id2 = pool.intern(&s, w);
722 prop_assert_eq!(id1, id2);
723 prop_assert_eq!(pool.len(), 1);
724 }
725
726 #[test]
728 fn deduplication_refcount(s in arb_grapheme(), w in arb_width(), extra in 0u32..10) {
729 let mut pool = GraphemePool::new();
730 let id = pool.intern(&s, w);
731 for _ in 0..extra {
732 pool.intern(&s, w);
733 }
734 prop_assert_eq!(pool.refcount(id), 1 + extra);
735 }
736
737 #[test]
739 fn retain_release_refcount(
740 s in arb_grapheme(),
741 w in arb_width(),
742 retains in 0u32..10,
743 releases in 0u32..10
744 ) {
745 let mut pool = GraphemePool::new();
746 let id = pool.intern(&s, w);
747 for _ in 0..retains {
749 pool.retain(id);
750 }
751 let expected_after_retain = 1 + retains;
752 prop_assert_eq!(pool.refcount(id), expected_after_retain);
753
754 let actual_releases = releases.min(expected_after_retain - 1);
755 for _ in 0..actual_releases {
756 pool.release(id);
757 }
758 prop_assert_eq!(pool.refcount(id), expected_after_retain - actual_releases);
759 prop_assert_eq!(pool.get(id), Some(s.as_str()));
761 }
762
763 #[test]
765 fn release_to_zero_frees(s in arb_grapheme(), w in arb_width(), extra in 0u32..5) {
766 let mut pool = GraphemePool::new();
767 let id = pool.intern(&s, w);
768 for _ in 0..extra {
769 pool.retain(id);
770 }
771 for _ in 0..=extra {
773 pool.release(id);
774 }
775 prop_assert_eq!(pool.get(id), None);
776 prop_assert_eq!(pool.refcount(id), 0);
777 prop_assert!(pool.is_empty());
778 }
779
780 #[test]
782 fn slot_reuse_after_free(
783 s1 in arb_grapheme(),
784 s2 in arb_grapheme(),
785 w in arb_width()
786 ) {
787 let mut pool = GraphemePool::new();
788 let id1 = pool.intern(&s1, w);
789 let slot1 = id1.slot();
790 pool.release(id1);
791
792 let id2 = pool.intern(&s2, w);
794 prop_assert_eq!(id2.slot(), slot1);
795 prop_assert_eq!(pool.get(id2), Some(s2.as_str()));
796 }
797
798 #[test]
800 fn len_invariant(count in 1usize..20) {
801 let mut pool = GraphemePool::new();
802 let mut ids = Vec::new();
803 for i in 0..count {
804 let s = format!("g{i}");
805 ids.push(pool.intern(&s, 1));
806 }
807 prop_assert_eq!(pool.len(), count);
808
809 let release_count = count / 2;
811 for id in &ids[..release_count] {
812 pool.release(*id);
813 }
814 prop_assert_eq!(pool.len(), count - release_count);
815 }
816
817 #[test]
819 fn distinct_strings_distinct_ids(count in 2usize..15) {
820 let mut pool = GraphemePool::new();
821 let mut ids = Vec::new();
822 for i in 0..count {
823 let s = format!("unique_{i}");
824 ids.push(pool.intern(&s, 1));
825 }
826 for i in 0..ids.len() {
828 for j in (i + 1)..ids.len() {
829 prop_assert_ne!(ids[i], ids[j]);
830 }
831 }
832 }
833
834 #[test]
836 fn clear_resets_all(count in 1usize..20) {
837 let mut pool = GraphemePool::new();
838 let mut ids = Vec::new();
839 for i in 0..count {
840 let s = format!("c{i}");
841 ids.push(pool.intern(&s, 1));
842 }
843 pool.clear();
844 prop_assert!(pool.is_empty());
845 prop_assert_eq!(pool.len(), 0);
846 for id in &ids {
847 prop_assert_eq!(pool.get(*id), None);
848 }
849 }
850
851 #[test]
855 fn positive_refcount_implies_valid_slot(
856 count in 1usize..10,
857 retains in proptest::collection::vec(0u32..5, 1..10),
858 ) {
859 let mut pool = GraphemePool::new();
860 let mut ids = Vec::new();
861 for i in 0..count {
862 let s = format!("inv_{i}");
863 ids.push(pool.intern(&s, 1));
864 }
865
866 for (i, &extra) in retains.iter().enumerate() {
868 let id = ids[i % count];
869 for _ in 0..extra {
870 pool.retain(id);
871 }
872 }
873
874 for (i, &id) in ids.iter().enumerate() {
876 let rc = pool.refcount(id);
877 if rc > 0 {
878 prop_assert!(pool.get(id).is_some(),
879 "slot {} has refcount {} but get() returned None", i, rc);
880 }
881 }
882 }
883
884 #[test]
886 fn release_decrements_by_one(s in arb_grapheme(), w in arb_width(), retains in 1u32..8) {
887 let mut pool = GraphemePool::new();
888 let id = pool.intern(&s, w);
889 for _ in 0..retains {
890 pool.retain(id);
891 }
892 let rc_before = pool.refcount(id);
893 pool.release(id);
894 let rc_after = pool.refcount(id);
895 prop_assert_eq!(rc_after, rc_before - 1,
896 "release should decrement refcount by exactly 1");
897 }
898
899 #[test]
901 fn over_release_does_not_corrupt(count in 1usize..5) {
902 let mut pool = GraphemePool::new();
903 let mut ids = Vec::new();
904 for i in 0..count {
905 let s = format!("or_{i}");
906 ids.push(pool.intern(&s, 1));
907 }
908
909 let victim = ids[0];
911 pool.release(victim);
912 prop_assert_eq!(pool.refcount(victim), 0);
913 prop_assert_eq!(pool.get(victim), None);
914
915 pool.release(victim);
917 prop_assert_eq!(pool.refcount(victim), 0);
918
919 for &id in &ids[1..] {
921 prop_assert!(pool.get(id).is_some(),
922 "over-release corrupted unrelated slot");
923 prop_assert!(pool.refcount(id) > 0);
924 }
925 }
926
927 #[test]
929 fn cross_pool_id_is_invalid(s in arb_grapheme(), w in arb_width()) {
930 let mut pool_a = GraphemePool::new();
931 let pool_b = GraphemePool::new();
932 let id = pool_a.intern(&s, w);
933
934 prop_assert_eq!(pool_b.get(id), None,
936 "GraphemeId from pool A should not be valid in pool B");
937 }
938 }
939 }
940
941 #[test]
944 fn pool_debug_and_clone() {
945 let mut pool = GraphemePool::new();
946 pool.intern("๐", 2);
947 let dbg = format!("{:?}", pool);
948 assert!(dbg.contains("GraphemePool"), "Debug: {dbg}");
949 let cloned = pool.clone();
950 assert_eq!(cloned.len(), 1);
951 let id = cloned.lookup.values().next().copied().unwrap();
953 assert_eq!(cloned.get(id), Some("๐"));
954 }
955
956 #[test]
957 fn pool_default_is_new() {
958 let pool = GraphemePool::default();
959 assert!(pool.is_empty());
960 assert_eq!(pool.len(), 0);
961 }
962
963 #[test]
964 fn intern_width_zero() {
965 let mut pool = GraphemePool::new();
966 let id = pool.intern("zero-width", 0);
967 assert_eq!(id.width(), 0);
968 assert_eq!(pool.get(id), Some("zero-width"));
969 }
970
971 #[test]
972 fn intern_width_max() {
973 let mut pool = GraphemePool::new();
974 let id = pool.intern("max-width", GraphemeId::MAX_WIDTH);
975 assert_eq!(id.width(), GraphemeId::MAX_WIDTH as usize);
976 assert_eq!(pool.get(id), Some("max-width"));
977 }
978
979 #[test]
980 fn intern_empty_string() {
981 let mut pool = GraphemePool::new();
982 let id = pool.intern("", 0);
983 assert_eq!(pool.get(id), Some(""));
984 }
985
986 #[test]
987 fn intern_long_string() {
988 let mut pool = GraphemePool::new();
989 let long = "a".repeat(1000);
990 let id = pool.intern(&long, 1);
991 assert_eq!(pool.get(id), Some(long.as_str()));
992 }
993
994 #[test]
995 fn clear_then_intern_reuses_from_scratch() {
996 let mut pool = GraphemePool::new();
997 pool.intern("A", 1);
998 pool.intern("B", 1);
999 pool.clear();
1000 assert!(pool.is_empty());
1001 let id = pool.intern("C", 1);
1003 assert_eq!(id.slot(), 0);
1004 assert_eq!(pool.get(id), Some("C"));
1005 assert_eq!(pool.len(), 1);
1006 }
1007
1008 #[test]
1009 fn with_capacity_then_intern() {
1010 let mut pool = GraphemePool::with_capacity(50);
1011 for i in 0..50 {
1012 pool.intern(&format!("g{i}"), 1);
1013 }
1014 assert_eq!(pool.len(), 50);
1015 }
1016
1017 #[test]
1018 fn refcount_of_freed_slot_is_zero() {
1019 let mut pool = GraphemePool::new();
1020 let id = pool.intern("temp", 1);
1021 pool.release(id);
1022 assert_eq!(pool.refcount(id), 0);
1023 }
1024
1025 #[test]
1026 fn refcount_of_invalid_id_is_zero() {
1027 let pool = GraphemePool::new();
1028 assert_eq!(pool.refcount(GraphemeId::new(0, 0, 1)), 0);
1029 assert_eq!(pool.refcount(GraphemeId::new(999, 0, 1)), 0);
1030 }
1031
1032 #[test]
1033 fn retain_freed_slot_is_noop() {
1034 let mut pool = GraphemePool::new();
1035 let id = pool.intern("temp", 1);
1036 pool.release(id);
1037 pool.retain(id); assert_eq!(pool.refcount(id), 0);
1040 assert_eq!(pool.get(id), None);
1041 }
1042
1043 #[test]
1044 fn double_release_is_safe() {
1045 let mut pool = GraphemePool::new();
1046 let id = pool.intern("temp", 1);
1047 pool.release(id); pool.release(id); assert_eq!(pool.refcount(id), 0);
1050 }
1051
1052 #[test]
1053 fn multiple_slot_reuse_cycles() {
1054 let mut pool = GraphemePool::new();
1055 for cycle in 0..5 {
1056 let id = pool.intern(&format!("cycle{cycle}"), 1);
1057 assert_eq!(id.slot(), 0); assert_eq!(pool.get(id), Some(format!("cycle{cycle}").as_str()));
1059 pool.release(id);
1060 }
1061 assert!(pool.is_empty());
1062 }
1063
1064 #[test]
1065 fn free_list_ordering() {
1066 let mut pool = GraphemePool::new();
1067 let id0 = pool.intern("A", 1);
1068 let id1 = pool.intern("B", 1);
1069 let id2 = pool.intern("C", 1);
1070 assert_eq!(id0.slot(), 0);
1071 assert_eq!(id1.slot(), 1);
1072 assert_eq!(id2.slot(), 2);
1073
1074 pool.release(id0);
1076 pool.release(id2);
1077 assert_eq!(pool.len(), 1); let new1 = pool.intern("D", 1);
1081 assert_eq!(new1.slot(), 2);
1082 let new2 = pool.intern("E", 1);
1083 assert_eq!(new2.slot(), 0);
1084 }
1085
1086 #[test]
1087 fn intern_after_release_deduplicates_correctly() {
1088 let mut pool = GraphemePool::new();
1089 let id1 = pool.intern("X", 1);
1090 pool.release(id1);
1091 assert_eq!(pool.get(id1), None);
1093
1094 let id2 = pool.intern("X", 1);
1096 assert_eq!(pool.get(id2), Some("X"));
1097 assert_eq!(pool.refcount(id2), 1);
1098 }
1099
1100 #[test]
1101 fn clone_independence() {
1102 let mut pool = GraphemePool::new();
1103 let id = pool.intern("shared", 1);
1104
1105 let mut cloned = pool.clone();
1106 pool.release(id);
1108 assert_eq!(pool.get(id), None);
1109
1110 assert_eq!(cloned.get(id), Some("shared"));
1112 assert_eq!(cloned.refcount(id), 1);
1113
1114 cloned.retain(id);
1116 assert_eq!(cloned.refcount(id), 2);
1117 assert_eq!(pool.refcount(id), 0);
1119 }
1120
1121 #[test]
1122 fn gc_double_run_idempotent() {
1123 use crate::buffer::Buffer;
1124 use crate::cell::{Cell, CellContent};
1125
1126 let mut pool = GraphemePool::new();
1127 let id = pool.intern("keep", 1);
1128 let _id2 = pool.intern("drop", 1);
1129
1130 let mut buf = Buffer::new(4, 1);
1131 buf.set(0, 0, Cell::new(CellContent::from_grapheme(id)));
1132
1133 pool.gc(&[&buf]);
1134 assert_eq!(pool.len(), 1);
1135 assert_eq!(pool.get(id), Some("keep"));
1136
1137 pool.gc(&[&buf]);
1139 assert_eq!(pool.len(), 1);
1140 assert_eq!(pool.refcount(id), 1);
1141 }
1142
1143 #[test]
1144 fn gc_with_already_freed_slots() {
1145 use crate::buffer::Buffer;
1146
1147 let mut pool = GraphemePool::new();
1148 let id1 = pool.intern("A", 1);
1149 let id2 = pool.intern("B", 1);
1150
1151 pool.release(id1);
1153 assert_eq!(pool.len(), 1);
1154
1155 let buf = Buffer::new(4, 1);
1157 pool.gc(&[&buf]);
1158
1159 assert!(pool.is_empty());
1160 assert_eq!(pool.get(id2), None);
1161 }
1162
1163 #[test]
1164 fn stress_100_graphemes() {
1165 let mut pool = GraphemePool::new();
1166 let mut ids = Vec::new();
1167 for i in 0..100 {
1168 ids.push(pool.intern(&format!("g{i:03}"), 1));
1169 }
1170 assert_eq!(pool.len(), 100);
1171
1172 for (i, &id) in ids.iter().enumerate() {
1174 assert_eq!(pool.get(id), Some(format!("g{i:03}").as_str()));
1175 }
1176
1177 for i in (0..100).step_by(2) {
1179 pool.release(ids[i]);
1180 }
1181 assert_eq!(pool.len(), 50);
1182
1183 for i in (1..100).step_by(2) {
1185 assert_eq!(pool.get(ids[i]), Some(format!("g{i:03}").as_str()));
1186 }
1187 }
1188
1189 #[test]
1190 fn capacity_grows_with_interns() {
1191 let mut pool = GraphemePool::new();
1192 let cap_before = pool.capacity();
1193 for i in 0..20 {
1194 pool.intern(&format!("grow{i}"), 1);
1195 }
1196 assert!(pool.capacity() >= 20);
1198 assert!(pool.capacity() >= cap_before);
1199 }
1200
1201 #[test]
1202 fn len_after_mixed_operations() {
1203 let mut pool = GraphemePool::new();
1204 assert_eq!(pool.len(), 0);
1205
1206 let a = pool.intern("A", 1);
1207 assert_eq!(pool.len(), 1);
1208
1209 let b = pool.intern("B", 1);
1210 assert_eq!(pool.len(), 2);
1211
1212 pool.intern("A", 1);
1214 assert_eq!(pool.len(), 2);
1215
1216 pool.release(a);
1217 assert_eq!(pool.len(), 2);
1219
1220 pool.release(a);
1221 assert_eq!(pool.len(), 1);
1223
1224 pool.release(b);
1225 assert_eq!(pool.len(), 0);
1226 assert!(pool.is_empty());
1227 }
1228
1229 #[test]
1230 fn generation_overflow_handling() {
1231 let mut pool = GraphemePool::new();
1232 let id = pool.intern("initial", 0);
1234 pool.release(id); for i in 0..=GraphemeId::MAX_GENERATION {
1239 let s = format!("g{}", i);
1240 let id = pool.intern(&s, 0);
1241 assert_eq!(id.slot(), 0);
1242 pool.release(id);
1243 }
1244
1245 let id_overflow = pool.intern("overflow", 0);
1250
1251 assert_eq!(pool.get(id_overflow), Some("overflow"));
1253
1254 assert_eq!(id_overflow.width(), 0);
1256 }
1257}