1use ratatui::style::{Color, Style};
23use std::collections::HashMap;
24
25use crate::model::marker::{MarkerId, MarkerList};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum VirtualTextPosition {
30 BeforeChar,
33 AfterChar,
35
36 LineAbove,
41 LineBelow,
45}
46
47impl VirtualTextPosition {
48 pub fn is_line(&self) -> bool {
50 matches!(self, Self::LineAbove | Self::LineBelow)
51 }
52
53 pub fn is_inline(&self) -> bool {
55 matches!(self, Self::BeforeChar | Self::AfterChar)
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub struct VirtualTextNamespace(pub String);
63
64impl VirtualTextNamespace {
65 pub fn from_string(s: String) -> Self {
67 Self(s)
68 }
69
70 pub fn as_str(&self) -> &str {
72 &self.0
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct VirtualText {
79 pub marker_id: MarkerId,
81 pub text: String,
83 pub style: Style,
88 pub fg_theme_key: Option<String>,
92 pub bg_theme_key: Option<String>,
94 pub position: VirtualTextPosition,
96 pub priority: i32,
98 pub string_id: Option<String>,
100 pub namespace: Option<VirtualTextNamespace>,
102 pub gutter_glyph: Option<String>,
109 pub gutter_color: Option<Color>,
112 pub text_overlays: Vec<fresh_core::api::VirtualLineTextOverlay>,
116}
117
118impl VirtualText {
119 pub fn resolved_style(&self, theme: &crate::view::theme::Theme) -> Style {
126 let mut style = self.style;
127 if let Some(ref key) = self.fg_theme_key {
128 if let Some(color) = theme.resolve_theme_key(key) {
129 style = style.fg(color);
130 }
131 }
132 if let Some(ref key) = self.bg_theme_key {
133 if let Some(color) = theme.resolve_theme_key(key) {
134 style = style.bg(color);
135 }
136 }
137 style
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143pub struct VirtualTextId(pub u64);
144
145pub struct VirtualTextManager {
150 texts: HashMap<VirtualTextId, VirtualText>,
152 next_id: u64,
154 version: u32,
160}
161
162impl VirtualTextManager {
163 pub fn new() -> Self {
165 Self {
166 texts: HashMap::new(),
167 next_id: 0,
168 version: 0,
169 }
170 }
171
172 #[inline]
176 pub fn marker_id_of(&self, id: VirtualTextId) -> Option<MarkerId> {
182 self.texts.get(&id).map(|vt| vt.marker_id)
183 }
184
185 pub fn version(&self) -> u32 {
186 self.version
187 }
188
189 #[inline]
190 fn bump_version(&mut self) {
191 self.version = self.version.wrapping_add(1);
192 }
193
194 pub fn add(
207 &mut self,
208 marker_list: &mut MarkerList,
209 position: usize,
210 text: String,
211 style: Style,
212 vtext_position: VirtualTextPosition,
213 priority: i32,
214 ) -> VirtualTextId {
215 let marker_id = marker_list.create(position, false);
218
219 let id = VirtualTextId(self.next_id);
220 self.next_id += 1;
221
222 self.texts.insert(
223 id,
224 VirtualText {
225 marker_id,
226 text,
227 style,
228 fg_theme_key: None,
229 bg_theme_key: None,
230 position: vtext_position,
231 priority,
232 string_id: None,
233 namespace: None,
234 gutter_glyph: None,
235 gutter_color: None,
236 text_overlays: Vec::new(),
237 },
238 );
239 self.bump_version();
240
241 id
242 }
243
244 #[allow(clippy::too_many_arguments)]
252 pub fn add_with_theme_keys(
253 &mut self,
254 marker_list: &mut MarkerList,
255 position: usize,
256 text: String,
257 style: Style,
258 fg_theme_key: Option<String>,
259 bg_theme_key: Option<String>,
260 vtext_position: VirtualTextPosition,
261 priority: i32,
262 ) -> VirtualTextId {
263 debug_assert!(
264 vtext_position.is_inline(),
265 "add_with_theme_keys requires BeforeChar or AfterChar"
266 );
267
268 let marker_id = marker_list.create(position, false);
269
270 let id = VirtualTextId(self.next_id);
271 self.next_id += 1;
272
273 self.texts.insert(
274 id,
275 VirtualText {
276 marker_id,
277 text,
278 style,
279 fg_theme_key,
280 bg_theme_key,
281 position: vtext_position,
282 priority,
283 string_id: None,
284 namespace: None,
285 gutter_glyph: None,
286 gutter_color: None,
287 text_overlays: Vec::new(),
288 },
289 );
290 self.bump_version();
291
292 id
293 }
294
295 #[allow(clippy::too_many_arguments)]
299 pub fn add_with_id(
300 &mut self,
301 marker_list: &mut MarkerList,
302 position: usize,
303 text: String,
304 style: Style,
305 vtext_position: VirtualTextPosition,
306 priority: i32,
307 string_id: String,
308 ) -> VirtualTextId {
309 let marker_id = marker_list.create(position, false);
310
311 let id = VirtualTextId(self.next_id);
312 self.next_id += 1;
313
314 self.texts.insert(
315 id,
316 VirtualText {
317 marker_id,
318 text,
319 style,
320 fg_theme_key: None,
321 bg_theme_key: None,
322 position: vtext_position,
323 priority,
324 string_id: Some(string_id),
325 namespace: None,
326 gutter_glyph: None,
327 gutter_color: None,
328 text_overlays: Vec::new(),
329 },
330 );
331 self.bump_version();
332
333 id
334 }
335
336 #[allow(clippy::too_many_arguments)]
339 pub fn add_with_id_and_theme_keys(
340 &mut self,
341 marker_list: &mut MarkerList,
342 position: usize,
343 text: String,
344 style: Style,
345 fg_theme_key: Option<String>,
346 bg_theme_key: Option<String>,
347 vtext_position: VirtualTextPosition,
348 priority: i32,
349 string_id: String,
350 ) -> VirtualTextId {
351 debug_assert!(
352 vtext_position.is_inline(),
353 "add_with_id_and_theme_keys requires BeforeChar or AfterChar"
354 );
355
356 let marker_id = marker_list.create(position, false);
357
358 let id = VirtualTextId(self.next_id);
359 self.next_id += 1;
360
361 self.texts.insert(
362 id,
363 VirtualText {
364 marker_id,
365 text,
366 style,
367 fg_theme_key,
368 bg_theme_key,
369 position: vtext_position,
370 priority,
371 string_id: Some(string_id),
372 namespace: None,
373 gutter_glyph: None,
374 gutter_color: None,
375 text_overlays: Vec::new(),
376 },
377 );
378
379 id
380 }
381
382 #[allow(clippy::too_many_arguments)]
395 pub fn add_line(
396 &mut self,
397 marker_list: &mut MarkerList,
398 position: usize,
399 text: String,
400 style: Style,
401 placement: VirtualTextPosition,
402 namespace: VirtualTextNamespace,
403 priority: i32,
404 ) -> VirtualTextId {
405 self.add_line_with_theme_keys(
406 marker_list,
407 position,
408 text,
409 style,
410 None,
411 None,
412 placement,
413 namespace,
414 priority,
415 None,
416 None,
417 Vec::new(),
418 )
419 }
420
421 #[allow(clippy::too_many_arguments)]
429 pub fn add_line_with_theme_keys(
430 &mut self,
431 marker_list: &mut MarkerList,
432 position: usize,
433 text: String,
434 style: Style,
435 fg_theme_key: Option<String>,
436 bg_theme_key: Option<String>,
437 placement: VirtualTextPosition,
438 namespace: VirtualTextNamespace,
439 priority: i32,
440 gutter_glyph: Option<String>,
441 gutter_color: Option<Color>,
442 text_overlays: Vec<fresh_core::api::VirtualLineTextOverlay>,
443 ) -> VirtualTextId {
444 debug_assert!(
445 placement.is_line(),
446 "add_line requires LineAbove or LineBelow"
447 );
448
449 let marker_id = marker_list.create(position, false);
450
451 let id = VirtualTextId(self.next_id);
452 self.next_id += 1;
453
454 self.texts.insert(
455 id,
456 VirtualText {
457 marker_id,
458 text,
459 style,
460 fg_theme_key,
461 bg_theme_key,
462 position: placement,
463 priority,
464 string_id: None,
465 namespace: Some(namespace),
466 gutter_glyph,
467 gutter_color,
468 text_overlays,
469 },
470 );
471 self.bump_version();
472
473 id
474 }
475
476 pub fn remove_by_id(&mut self, marker_list: &mut MarkerList, string_id: &str) -> bool {
478 let to_remove: Vec<VirtualTextId> = self
480 .texts
481 .iter()
482 .filter_map(|(id, vtext)| {
483 if vtext.string_id.as_deref() == Some(string_id) {
484 Some(*id)
485 } else {
486 None
487 }
488 })
489 .collect();
490
491 let mut removed = false;
492 for id in to_remove {
493 if let Some(vtext) = self.texts.remove(&id) {
494 marker_list.delete(vtext.marker_id);
495 removed = true;
496 }
497 }
498 if removed {
499 self.bump_version();
500 }
501 removed
502 }
503
504 pub fn remove_by_prefix(&mut self, marker_list: &mut MarkerList, prefix: &str) {
506 let markers_to_delete: Vec<(VirtualTextId, MarkerId)> = self
508 .texts
509 .iter()
510 .filter_map(|(id, vtext)| {
511 if let Some(ref sid) = vtext.string_id {
512 if sid.starts_with(prefix) {
513 return Some((*id, vtext.marker_id));
514 }
515 }
516 None
517 })
518 .collect();
519
520 let removed = !markers_to_delete.is_empty();
522 for (id, marker_id) in markers_to_delete {
523 marker_list.delete(marker_id);
524 self.texts.remove(&id);
525 }
526 if removed {
527 self.bump_version();
528 }
529 }
530
531 pub fn remove(&mut self, marker_list: &mut MarkerList, id: VirtualTextId) -> bool {
533 if let Some(vtext) = self.texts.remove(&id) {
534 marker_list.delete(vtext.marker_id);
535 self.bump_version();
536 true
537 } else {
538 false
539 }
540 }
541
542 pub fn clear(&mut self, marker_list: &mut MarkerList) {
544 let was_non_empty = !self.texts.is_empty();
545 for vtext in self.texts.values() {
546 marker_list.delete(vtext.marker_id);
547 }
548 self.texts.clear();
549 if was_non_empty {
550 self.bump_version();
551 }
552 }
553
554 pub fn remove_in_range(
566 &mut self,
567 marker_list: &mut MarkerList,
568 start: usize,
569 end: usize,
570 ) -> usize {
571 if start >= end {
572 return 0;
573 }
574
575 let to_remove: Vec<VirtualTextId> = self
576 .texts
577 .iter()
578 .filter_map(|(id, vtext)| {
579 let pos = marker_list.get_position(vtext.marker_id)?;
580 if pos >= start && pos < end {
581 Some(*id)
582 } else {
583 None
584 }
585 })
586 .collect();
587
588 let count = to_remove.len();
589 for id in to_remove {
590 if let Some(vtext) = self.texts.remove(&id) {
591 marker_list.delete(vtext.marker_id);
592 }
593 }
594 if count > 0 {
595 self.bump_version();
596 }
597 count
598 }
599
600 pub fn len(&self) -> usize {
602 self.texts.len()
603 }
604
605 pub fn is_empty(&self) -> bool {
607 self.texts.is_empty()
608 }
609
610 pub fn query_range(
621 &self,
622 marker_list: &MarkerList,
623 start: usize,
624 end: usize,
625 ) -> Vec<(usize, &VirtualText)> {
626 let mut results: Vec<(usize, &VirtualText)> = self
627 .texts
628 .values()
629 .filter_map(|vtext| {
630 let pos = marker_list.get_position(vtext.marker_id)?;
631 if pos >= start && pos < end {
632 Some((pos, vtext))
633 } else {
634 None
635 }
636 })
637 .collect();
638
639 results.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.priority.cmp(&b.1.priority)));
641
642 results
643 }
644
645 pub fn build_lookup(
650 &self,
651 marker_list: &MarkerList,
652 start: usize,
653 end: usize,
654 ) -> HashMap<usize, Vec<&VirtualText>> {
655 let mut lookup: HashMap<usize, Vec<&VirtualText>> = HashMap::new();
656
657 for vtext in self.texts.values() {
658 if let Some(pos) = marker_list.get_position(vtext.marker_id) {
659 if pos >= start && pos < end {
660 lookup.entry(pos).or_default().push(vtext);
661 }
662 }
663 }
664
665 for texts in lookup.values_mut() {
667 texts.sort_by_key(|vt| vt.priority);
668 }
669
670 lookup
671 }
672
673 pub fn clear_namespace(
677 &mut self,
678 marker_list: &mut MarkerList,
679 namespace: &VirtualTextNamespace,
680 ) {
681 let to_remove: Vec<VirtualTextId> = self
682 .texts
683 .iter()
684 .filter_map(|(id, vtext)| {
685 if vtext.namespace.as_ref() == Some(namespace) {
686 Some(*id)
687 } else {
688 None
689 }
690 })
691 .collect();
692
693 let removed = !to_remove.is_empty();
694 for id in to_remove {
695 if let Some(vtext) = self.texts.remove(&id) {
696 marker_list.delete(vtext.marker_id);
697 }
698 }
699 if removed {
700 self.bump_version();
701 }
702 }
703
704 pub fn query_lines_in_range(
709 &self,
710 marker_list: &MarkerList,
711 start: usize,
712 end: usize,
713 ) -> Vec<(usize, &VirtualText)> {
714 let mut results: Vec<(usize, &VirtualText)> = self
715 .texts
716 .values()
717 .filter(|vtext| vtext.position.is_line())
718 .filter_map(|vtext| {
719 let pos = marker_list.get_position(vtext.marker_id)?;
720 if pos >= start && pos < end {
721 Some((pos, vtext))
722 } else {
723 None
724 }
725 })
726 .collect();
727
728 results.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.priority.cmp(&b.1.priority)));
730
731 results
732 }
733
734 pub fn query_inline_in_range(
738 &self,
739 marker_list: &MarkerList,
740 start: usize,
741 end: usize,
742 ) -> Vec<(usize, &VirtualText)> {
743 let mut results: Vec<(usize, &VirtualText)> = self
744 .texts
745 .values()
746 .filter(|vtext| vtext.position.is_inline())
747 .filter_map(|vtext| {
748 let pos = marker_list.get_position(vtext.marker_id)?;
749 if pos >= start && pos < end {
750 Some((pos, vtext))
751 } else {
752 None
753 }
754 })
755 .collect();
756
757 results.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.priority.cmp(&b.1.priority)));
759
760 results
761 }
762
763 pub fn build_lines_lookup(
768 &self,
769 marker_list: &MarkerList,
770 start: usize,
771 end: usize,
772 ) -> HashMap<usize, Vec<&VirtualText>> {
773 let mut lookup: HashMap<usize, Vec<&VirtualText>> = HashMap::new();
774
775 for vtext in self.texts.values() {
776 if !vtext.position.is_line() {
777 continue;
778 }
779 if let Some(pos) = marker_list.get_position(vtext.marker_id) {
780 if pos >= start && pos < end {
781 lookup.entry(pos).or_default().push(vtext);
782 }
783 }
784 }
785
786 for texts in lookup.values_mut() {
788 texts.sort_by_key(|vt| vt.priority);
789 }
790
791 lookup
792 }
793}
794
795impl Default for VirtualTextManager {
796 fn default() -> Self {
797 Self::new()
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804 use ratatui::style::Color;
805
806 fn hint_style() -> Style {
807 Style::default().fg(Color::DarkGray)
808 }
809
810 #[test]
811 fn test_new_manager() {
812 let manager = VirtualTextManager::new();
813 assert_eq!(manager.len(), 0);
814 assert!(manager.is_empty());
815 }
816
817 #[test]
818 fn test_add_virtual_text() {
819 let mut marker_list = MarkerList::new();
820 let mut manager = VirtualTextManager::new();
821
822 let id = manager.add(
823 &mut marker_list,
824 10,
825 ": i32".to_string(),
826 hint_style(),
827 VirtualTextPosition::AfterChar,
828 0,
829 );
830
831 assert_eq!(manager.len(), 1);
832 assert!(!manager.is_empty());
833 assert_eq!(id.0, 0);
834 }
835
836 #[test]
837 fn test_remove_virtual_text() {
838 let mut marker_list = MarkerList::new();
839 let mut manager = VirtualTextManager::new();
840
841 let id = manager.add(
842 &mut marker_list,
843 10,
844 ": i32".to_string(),
845 hint_style(),
846 VirtualTextPosition::AfterChar,
847 0,
848 );
849
850 assert_eq!(manager.len(), 1);
851
852 let removed = manager.remove(&mut marker_list, id);
853 assert!(removed);
854 assert_eq!(manager.len(), 0);
855
856 assert_eq!(marker_list.marker_count(), 0);
858 }
859
860 #[test]
861 fn test_remove_nonexistent() {
862 let mut marker_list = MarkerList::new();
863 let mut manager = VirtualTextManager::new();
864
865 let removed = manager.remove(&mut marker_list, VirtualTextId(999));
866 assert!(!removed);
867 }
868
869 #[test]
870 fn test_clear() {
871 let mut marker_list = MarkerList::new();
872 let mut manager = VirtualTextManager::new();
873
874 manager.add(
875 &mut marker_list,
876 10,
877 ": i32".to_string(),
878 hint_style(),
879 VirtualTextPosition::AfterChar,
880 0,
881 );
882 manager.add(
883 &mut marker_list,
884 20,
885 ": String".to_string(),
886 hint_style(),
887 VirtualTextPosition::AfterChar,
888 0,
889 );
890
891 assert_eq!(manager.len(), 2);
892 assert_eq!(marker_list.marker_count(), 2);
893
894 manager.clear(&mut marker_list);
895
896 assert_eq!(manager.len(), 0);
897 assert_eq!(marker_list.marker_count(), 0);
898 }
899
900 #[test]
901 fn test_query_range() {
902 let mut marker_list = MarkerList::new();
903 let mut manager = VirtualTextManager::new();
904
905 manager.add(
907 &mut marker_list,
908 10,
909 ": i32".to_string(),
910 hint_style(),
911 VirtualTextPosition::AfterChar,
912 0,
913 );
914 manager.add(
915 &mut marker_list,
916 20,
917 ": String".to_string(),
918 hint_style(),
919 VirtualTextPosition::AfterChar,
920 0,
921 );
922 manager.add(
923 &mut marker_list,
924 30,
925 ": bool".to_string(),
926 hint_style(),
927 VirtualTextPosition::AfterChar,
928 0,
929 );
930
931 let results = manager.query_range(&marker_list, 15, 35);
933 assert_eq!(results.len(), 2);
934 assert_eq!(results[0].0, 20);
935 assert_eq!(results[0].1.text, ": String");
936 assert_eq!(results[1].0, 30);
937 assert_eq!(results[1].1.text, ": bool");
938
939 let results = manager.query_range(&marker_list, 0, 15);
941 assert_eq!(results.len(), 1);
942 assert_eq!(results[0].0, 10);
943 assert_eq!(results[0].1.text, ": i32");
944 }
945
946 #[test]
947 fn test_query_empty_range() {
948 let mut marker_list = MarkerList::new();
949 let mut manager = VirtualTextManager::new();
950
951 manager.add(
952 &mut marker_list,
953 10,
954 ": i32".to_string(),
955 hint_style(),
956 VirtualTextPosition::AfterChar,
957 0,
958 );
959
960 let results = manager.query_range(&marker_list, 100, 200);
962 assert!(results.is_empty());
963 }
964
965 #[test]
966 fn test_priority_ordering() {
967 let mut marker_list = MarkerList::new();
968 let mut manager = VirtualTextManager::new();
969
970 manager.add(
972 &mut marker_list,
973 10,
974 "low".to_string(),
975 hint_style(),
976 VirtualTextPosition::AfterChar,
977 0,
978 );
979 manager.add(
980 &mut marker_list,
981 10,
982 "high".to_string(),
983 hint_style(),
984 VirtualTextPosition::AfterChar,
985 10,
986 );
987 manager.add(
988 &mut marker_list,
989 10,
990 "medium".to_string(),
991 hint_style(),
992 VirtualTextPosition::AfterChar,
993 5,
994 );
995
996 let results = manager.query_range(&marker_list, 0, 20);
997 assert_eq!(results.len(), 3);
998 assert_eq!(results[0].1.text, "low");
1000 assert_eq!(results[1].1.text, "medium");
1001 assert_eq!(results[2].1.text, "high");
1002 }
1003
1004 #[test]
1005 fn test_build_lookup() {
1006 let mut marker_list = MarkerList::new();
1007 let mut manager = VirtualTextManager::new();
1008
1009 manager.add(
1010 &mut marker_list,
1011 10,
1012 ": i32".to_string(),
1013 hint_style(),
1014 VirtualTextPosition::AfterChar,
1015 0,
1016 );
1017 manager.add(
1018 &mut marker_list,
1019 10,
1020 " = 5".to_string(),
1021 hint_style(),
1022 VirtualTextPosition::AfterChar,
1023 1,
1024 );
1025 manager.add(
1026 &mut marker_list,
1027 20,
1028 ": String".to_string(),
1029 hint_style(),
1030 VirtualTextPosition::AfterChar,
1031 0,
1032 );
1033
1034 let lookup = manager.build_lookup(&marker_list, 0, 30);
1035
1036 assert_eq!(lookup.len(), 2); let at_10 = lookup.get(&10).unwrap();
1039 assert_eq!(at_10.len(), 2);
1040 assert_eq!(at_10[0].text, ": i32"); assert_eq!(at_10[1].text, " = 5"); let at_20 = lookup.get(&20).unwrap();
1044 assert_eq!(at_20.len(), 1);
1045 assert_eq!(at_20[0].text, ": String");
1046 }
1047
1048 #[test]
1049 fn test_position_tracking_after_insert() {
1050 let mut marker_list = MarkerList::new();
1051 let mut manager = VirtualTextManager::new();
1052
1053 manager.add(
1054 &mut marker_list,
1055 10,
1056 ": i32".to_string(),
1057 hint_style(),
1058 VirtualTextPosition::AfterChar,
1059 0,
1060 );
1061
1062 marker_list.adjust_for_insert(5, 5);
1064
1065 let results = manager.query_range(&marker_list, 0, 20);
1067 assert_eq!(results.len(), 1);
1068 assert_eq!(results[0].0, 15);
1069 }
1070
1071 #[test]
1072 fn test_position_tracking_after_delete() {
1073 let mut marker_list = MarkerList::new();
1074 let mut manager = VirtualTextManager::new();
1075
1076 manager.add(
1077 &mut marker_list,
1078 20,
1079 ": i32".to_string(),
1080 hint_style(),
1081 VirtualTextPosition::AfterChar,
1082 0,
1083 );
1084
1085 marker_list.adjust_for_delete(10, 5);
1087
1088 let results = manager.query_range(&marker_list, 0, 20);
1090 assert_eq!(results.len(), 1);
1091 assert_eq!(results[0].0, 15);
1092 }
1093
1094 #[test]
1095 fn test_before_and_after_positions() {
1096 let mut marker_list = MarkerList::new();
1097 let mut manager = VirtualTextManager::new();
1098
1099 manager.add(
1100 &mut marker_list,
1101 10,
1102 "/*param=*/".to_string(),
1103 hint_style(),
1104 VirtualTextPosition::BeforeChar,
1105 0,
1106 );
1107 manager.add(
1108 &mut marker_list,
1109 10,
1110 ": Type".to_string(),
1111 hint_style(),
1112 VirtualTextPosition::AfterChar,
1113 0,
1114 );
1115
1116 let lookup = manager.build_lookup(&marker_list, 0, 20);
1117 let at_10 = lookup.get(&10).unwrap();
1118
1119 assert_eq!(at_10.len(), 2);
1120 let before = at_10
1122 .iter()
1123 .find(|vt| vt.position == VirtualTextPosition::BeforeChar);
1124 let after = at_10
1125 .iter()
1126 .find(|vt| vt.position == VirtualTextPosition::AfterChar);
1127
1128 assert!(before.is_some());
1129 assert!(after.is_some());
1130 assert_eq!(before.unwrap().text, "/*param=*/");
1131 assert_eq!(after.unwrap().text, ": Type");
1132 }
1133}