1use super::super::range::{Position, Range};
136use super::super::traits::{AstNode, Visitor};
137use super::annotation::Annotation;
138use super::content_item::ContentItem;
139use super::definition::Definition;
140use super::list::{List, ListItem};
141use super::paragraph::Paragraph;
142use super::session::Session;
143use super::table::Table;
144use super::typed_content::{ContentElement, ListContent, SessionContent, VerbatimContent};
145use super::verbatim::Verbatim;
146use std::fmt;
147use std::marker::PhantomData;
148
149macro_rules! impl_recursive_iterator {
155 ($method_name:ident, $type:ty, $as_method:ident, $doc:expr) => {
156 #[doc = $doc]
157 pub fn $method_name(&self) -> Box<dyn Iterator<Item = &$type> + '_> {
158 Box::new(self.iter_all_nodes().filter_map(|item| item.$as_method()))
159 }
160 };
161}
162
163macro_rules! impl_first_method {
165 ($method_name:ident, $type:ty, $iter_method:ident, $doc:expr) => {
166 #[doc = $doc]
167 pub fn $method_name(&self) -> Option<&$type> {
168 self.$iter_method().next()
169 }
170 };
171}
172
173macro_rules! impl_find_method {
175 ($method_name:ident, $type:ty, $iter_method:ident, $doc:expr) => {
176 #[doc = $doc]
177 pub fn $method_name<F>(&self, predicate: F) -> Vec<&$type>
178 where
179 F: Fn(&$type) -> bool,
180 {
181 self.$iter_method().filter(|x| predicate(x)).collect()
182 }
183 };
184}
185
186pub trait ContainerPolicy: 'static {
195 type ContentType: Into<ContentItem> + Clone;
197
198 const ALLOWS_SESSIONS: bool;
200
201 const ALLOWS_ANNOTATIONS: bool;
203
204 const POLICY_NAME: &'static str;
206
207 fn validate(item: &ContentItem) -> Result<(), String>;
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct SessionPolicy;
218
219impl ContainerPolicy for SessionPolicy {
220 type ContentType = SessionContent;
221
222 const ALLOWS_SESSIONS: bool = true;
223 const ALLOWS_ANNOTATIONS: bool = true;
224 const POLICY_NAME: &'static str = "SessionPolicy";
225
226 fn validate(_item: &ContentItem) -> Result<(), String> {
227 Ok(())
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub struct GeneralPolicy;
237
238impl ContainerPolicy for GeneralPolicy {
239 type ContentType = ContentElement;
240
241 const ALLOWS_SESSIONS: bool = false;
242 const ALLOWS_ANNOTATIONS: bool = true;
243 const POLICY_NAME: &'static str = "GeneralPolicy";
244
245 fn validate(item: &ContentItem) -> Result<(), String> {
246 match item {
247 ContentItem::Session(_) => Err("GeneralPolicy does not allow Sessions".to_string()),
248 ContentItem::ListItem(_) => {
249 Err("GeneralPolicy does not allow ListItems (use List instead)".to_string())
250 }
251 _ => Ok(()),
252 }
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub struct ListPolicy;
261
262impl ContainerPolicy for ListPolicy {
263 type ContentType = ListContent;
264
265 const ALLOWS_SESSIONS: bool = false;
266 const ALLOWS_ANNOTATIONS: bool = false;
267 const POLICY_NAME: &'static str = "ListPolicy";
268
269 fn validate(item: &ContentItem) -> Result<(), String> {
270 match item {
271 ContentItem::ListItem(_) => Ok(()),
272 _ => Err(format!(
273 "ListPolicy only allows ListItems, found {}",
274 item.node_type()
275 )),
276 }
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub struct VerbatimPolicy;
285
286impl ContainerPolicy for VerbatimPolicy {
287 type ContentType = VerbatimContent;
288
289 const ALLOWS_SESSIONS: bool = false;
290 const ALLOWS_ANNOTATIONS: bool = false;
291 const POLICY_NAME: &'static str = "VerbatimPolicy";
292
293 fn validate(item: &ContentItem) -> Result<(), String> {
294 match item {
295 ContentItem::VerbatimLine(_) => Ok(()),
296 _ => Err(format!(
297 "VerbatimPolicy only allows VerbatimLines, found {}",
298 item.node_type()
299 )),
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq)]
313pub struct Container<P: ContainerPolicy> {
314 children: Vec<ContentItem>,
315 pub location: Range,
316 _policy: PhantomData<P>,
317}
318
319pub type SessionContainer = Container<SessionPolicy>;
327
328pub type GeneralContainer = Container<GeneralPolicy>;
333
334pub type ListContainer = Container<ListPolicy>;
338
339pub type VerbatimContainer = Container<VerbatimPolicy>;
344
345impl<P: ContainerPolicy> Container<P> {
350 pub fn from_typed(children: Vec<P::ContentType>) -> Self {
361 Self {
362 children: children.into_iter().map(|c| c.into()).collect(),
363 location: Range::default(),
364 _policy: PhantomData,
365 }
366 }
367
368 pub fn empty() -> Self {
370 Self {
371 children: Vec::new(),
372 location: Range::default(),
373 _policy: PhantomData,
374 }
375 }
376
377 pub fn at(mut self, location: Range) -> Self {
379 self.location = location;
380 self
381 }
382
383 pub fn len(&self) -> usize {
385 self.children.len()
386 }
387
388 pub fn is_empty(&self) -> bool {
390 self.children.is_empty()
391 }
392
393 pub fn push_typed(&mut self, item: P::ContentType) {
404 self.children.push(item.into());
405 }
406
407 pub fn push(&mut self, item: ContentItem) {
425 P::validate(&item)
426 .unwrap_or_else(|err| panic!("Invalid item for {}: {}", P::POLICY_NAME, err));
427 self.children.push(item);
428 }
429
430 pub fn extend<I>(&mut self, items: I)
435 where
436 I: IntoIterator<Item = ContentItem>,
437 {
438 for item in items {
439 self.push(item);
440 }
441 }
442
443 pub fn get(&self, index: usize) -> Option<&ContentItem> {
445 self.children.get(index)
446 }
447
448 pub fn get_mut(&mut self, index: usize) -> Option<&mut ContentItem> {
452 self.children.get_mut(index)
453 }
454
455 pub fn clear(&mut self) {
457 self.children.clear();
458 }
459
460 pub fn remove(&mut self, index: usize) -> ContentItem {
465 self.children.remove(index)
466 }
467
468 pub fn iter(&self) -> std::slice::Iter<'_, ContentItem> {
470 self.children.iter()
471 }
472
473 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, ContentItem> {
475 self.children.iter_mut()
476 }
477
478 pub fn as_mut_vec(&mut self) -> &mut Vec<ContentItem> {
487 &mut self.children
488 }
489
490 pub fn iter_paragraphs(&self) -> impl Iterator<Item = &Paragraph> {
496 self.children.iter().filter_map(|item| item.as_paragraph())
497 }
498
499 pub fn iter_sessions(&self) -> impl Iterator<Item = &Session> {
501 self.children.iter().filter_map(|item| item.as_session())
502 }
503
504 pub fn iter_lists(&self) -> impl Iterator<Item = &List> {
506 self.children.iter().filter_map(|item| item.as_list())
507 }
508
509 pub fn iter_verbatim_blocks(&self) -> impl Iterator<Item = &Verbatim> {
511 self.children
512 .iter()
513 .filter_map(|item| item.as_verbatim_block())
514 }
515
516 pub fn iter_definitions(&self) -> impl Iterator<Item = &Definition> {
518 self.children.iter().filter_map(|item| item.as_definition())
519 }
520
521 pub fn iter_annotations(&self) -> impl Iterator<Item = &Annotation> {
523 self.children.iter().filter_map(|item| item.as_annotation())
524 }
525
526 pub fn iter_all_nodes(&self) -> Box<dyn Iterator<Item = &ContentItem> + '_> {
532 Box::new(
533 self.children
534 .iter()
535 .flat_map(|item| std::iter::once(item).chain(item.descendants())),
536 )
537 }
538
539 pub fn iter_all_nodes_with_depth(
541 &self,
542 ) -> Box<dyn Iterator<Item = (&ContentItem, usize)> + '_> {
543 Box::new(
544 self.children
545 .iter()
546 .flat_map(|item| std::iter::once((item, 0)).chain(item.descendants_with_depth(1))),
547 )
548 }
549
550 impl_recursive_iterator!(
551 iter_paragraphs_recursive,
552 Paragraph,
553 as_paragraph,
554 "Recursively iterate all paragraphs at any depth in the container"
555 );
556 impl_recursive_iterator!(
557 iter_sessions_recursive,
558 Session,
559 as_session,
560 "Recursively iterate all sessions at any depth in the container"
561 );
562 impl_recursive_iterator!(
563 iter_lists_recursive,
564 List,
565 as_list,
566 "Recursively iterate all lists at any depth in the container"
567 );
568 impl_recursive_iterator!(
569 iter_verbatim_blocks_recursive,
570 Verbatim,
571 as_verbatim_block,
572 "Recursively iterate all verbatim blocks at any depth in the container"
573 );
574 impl_recursive_iterator!(
575 iter_list_items_recursive,
576 ListItem,
577 as_list_item,
578 "Recursively iterate all list items at any depth in the container"
579 );
580 impl_recursive_iterator!(
581 iter_definitions_recursive,
582 Definition,
583 as_definition,
584 "Recursively iterate all definitions at any depth in the container"
585 );
586 impl_recursive_iterator!(
587 iter_annotations_recursive,
588 Annotation,
589 as_annotation,
590 "Recursively iterate all annotations at any depth in the container"
591 );
592 impl_recursive_iterator!(
593 iter_tables_recursive,
594 Table,
595 as_table,
596 "Recursively iterate all tables at any depth in the container"
597 );
598
599 impl_first_method!(
604 first_paragraph,
605 Paragraph,
606 iter_paragraphs_recursive,
607 "Get the first paragraph in the container (returns None if not found)"
608 );
609 impl_first_method!(
610 first_session,
611 Session,
612 iter_sessions_recursive,
613 "Get the first session in the container tree (returns None if not found)"
614 );
615 impl_first_method!(
616 first_list,
617 List,
618 iter_lists_recursive,
619 "Get the first list in the container (returns None if not found)"
620 );
621 impl_first_method!(
622 first_definition,
623 Definition,
624 iter_definitions_recursive,
625 "Get the first definition in the container (returns None if not found)"
626 );
627 impl_first_method!(
628 first_annotation,
629 Annotation,
630 iter_annotations_recursive,
631 "Get the first annotation in the container (returns None if not found)"
632 );
633 impl_first_method!(
634 first_verbatim,
635 Verbatim,
636 iter_verbatim_blocks_recursive,
637 "Get the first verbatim block in the container (returns None if not found)"
638 );
639 impl_first_method!(
640 first_table,
641 Table,
642 iter_tables_recursive,
643 "Get the first table in the container (returns None if not found)"
644 );
645
646 pub fn expect_paragraph(&self) -> &Paragraph {
652 self.first_paragraph()
653 .expect("No paragraph found in container")
654 }
655
656 pub fn expect_session(&self) -> &Session {
658 self.first_session()
659 .expect("No session found in container tree")
660 }
661
662 pub fn expect_list(&self) -> &List {
664 self.first_list().expect("No list found in container")
665 }
666
667 pub fn expect_definition(&self) -> &Definition {
669 self.first_definition()
670 .expect("No definition found in container")
671 }
672
673 pub fn expect_annotation(&self) -> &Annotation {
675 self.first_annotation()
676 .expect("No annotation found in container")
677 }
678
679 pub fn expect_verbatim(&self) -> &Verbatim {
681 self.first_verbatim()
682 .expect("No verbatim block found in container")
683 }
684
685 pub fn expect_table(&self) -> &Table {
687 self.first_table().expect("No table found in container")
688 }
689
690 impl_find_method!(
695 find_paragraphs,
696 Paragraph,
697 iter_paragraphs_recursive,
698 "Find all paragraphs matching a predicate"
699 );
700 impl_find_method!(
701 find_sessions,
702 Session,
703 iter_sessions_recursive,
704 "Find all sessions matching a predicate"
705 );
706 impl_find_method!(
707 find_lists,
708 List,
709 iter_lists_recursive,
710 "Find all lists matching a predicate"
711 );
712 impl_find_method!(
713 find_definitions,
714 Definition,
715 iter_definitions_recursive,
716 "Find all definitions matching a predicate"
717 );
718 impl_find_method!(
719 find_annotations,
720 Annotation,
721 iter_annotations_recursive,
722 "Find all annotations matching a predicate"
723 );
724
725 pub fn find_nodes<F>(&self, predicate: F) -> Vec<&ContentItem>
727 where
728 F: Fn(&ContentItem) -> bool,
729 {
730 self.iter_all_nodes().filter(|n| predicate(n)).collect()
731 }
732
733 pub fn find_nodes_at_depth(&self, target_depth: usize) -> Vec<&ContentItem> {
739 self.iter_all_nodes_with_depth()
740 .filter(|(_, depth)| *depth == target_depth)
741 .map(|(node, _)| node)
742 .collect()
743 }
744
745 pub fn find_nodes_in_depth_range(
747 &self,
748 min_depth: usize,
749 max_depth: usize,
750 ) -> Vec<&ContentItem> {
751 self.iter_all_nodes_with_depth()
752 .filter(|(_, depth)| *depth >= min_depth && *depth <= max_depth)
753 .map(|(node, _)| node)
754 .collect()
755 }
756
757 pub fn find_nodes_with_depth<F>(&self, target_depth: usize, predicate: F) -> Vec<&ContentItem>
759 where
760 F: Fn(&ContentItem) -> bool,
761 {
762 self.iter_all_nodes_with_depth()
763 .filter(|(node, depth)| *depth == target_depth && predicate(node))
764 .map(|(node, _)| node)
765 .collect()
766 }
767
768 pub fn count_by_type(&self) -> (usize, usize, usize, usize) {
774 let paragraphs = self.iter_paragraphs().count();
775 let sessions = self.iter_sessions().count();
776 let lists = self.iter_lists().count();
777 let verbatim_blocks = self.iter_verbatim_blocks().count();
778 (paragraphs, sessions, lists, verbatim_blocks)
779 }
780
781 pub fn element_at(&self, pos: Position) -> Option<&ContentItem> {
787 for item in &self.children {
788 if let Some(result) = item.element_at(pos) {
789 return Some(result);
790 }
791 }
792 None
793 }
794
795 pub fn visual_line_at(&self, pos: Position) -> Option<&ContentItem> {
800 for item in &self.children {
801 if let Some(result) = item.visual_line_at(pos) {
802 return Some(result);
803 }
804 }
805 None
806 }
807
808 pub fn block_element_at(&self, pos: Position) -> Option<&ContentItem> {
813 for item in &self.children {
814 if let Some(result) = item.block_element_at(pos) {
815 return Some(result);
816 }
817 }
818 None
819 }
820
821 pub fn node_path_at_position(&self, pos: Position) -> Vec<&ContentItem> {
823 for item in &self.children {
824 let path = item.node_path_at_position(pos);
825 if !path.is_empty() {
826 return path;
827 }
828 }
829 Vec::new()
830 }
831
832 pub fn find_nodes_at_position(&self, position: Position) -> Vec<&dyn AstNode> {
834 if let Some(item) = self.element_at(position) {
835 vec![item as &dyn AstNode]
836 } else {
837 Vec::new()
838 }
839 }
840
841 pub fn format_at_position(&self, position: Position) -> String {
843 let nodes = self.find_nodes_at_position(position);
844 if nodes.is_empty() {
845 "No AST nodes at this position".to_string()
846 } else {
847 nodes
848 .iter()
849 .map(|node| format!("- {}: {}", node.node_type(), node.display_label()))
850 .collect::<Vec<_>>()
851 .join("\n")
852 }
853 }
854}
855
856impl<P: ContainerPolicy> AstNode for Container<P> {
857 fn node_type(&self) -> &'static str {
858 P::POLICY_NAME
859 }
860
861 fn display_label(&self) -> String {
862 format!("{} items", self.children.len())
863 }
864
865 fn range(&self) -> &Range {
866 &self.location
867 }
868
869 fn accept(&self, visitor: &mut dyn Visitor) {
870 super::super::traits::visit_children(visitor, &self.children);
873 }
874}
875
876impl<P: ContainerPolicy> std::ops::Deref for Container<P> {
878 type Target = [ContentItem];
879
880 fn deref(&self) -> &Self::Target {
881 &self.children
882 }
883}
884
885impl<P: ContainerPolicy> fmt::Display for Container<P> {
889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890 write!(f, "{}({} items)", P::POLICY_NAME, self.children.len())
891 }
892}
893
894impl<'a, P: ContainerPolicy> IntoIterator for &'a Container<P> {
896 type Item = &'a ContentItem;
897 type IntoIter = std::slice::Iter<'a, ContentItem>;
898
899 fn into_iter(self) -> Self::IntoIter {
900 self.children.iter()
901 }
902}
903
904impl<'a, P: ContainerPolicy> IntoIterator for &'a mut Container<P> {
905 type Item = &'a mut ContentItem;
906 type IntoIter = std::slice::IterMut<'a, ContentItem>;
907
908 fn into_iter(self) -> Self::IntoIter {
909 self.children.iter_mut()
910 }
911}
912
913#[cfg(test)]
914mod tests {
915 use super::super::list::ListItem;
916 use super::super::paragraph::Paragraph;
917 use super::super::session::Session;
918 use super::super::typed_content::{ContentElement, ListContent, SessionContent};
919 use super::*;
920
921 #[test]
926 fn test_session_container_creation() {
927 let container = SessionContainer::empty();
928 assert_eq!(container.len(), 0);
929 assert!(container.is_empty());
930 }
931
932 #[test]
933 fn test_general_container_creation() {
934 let container = GeneralContainer::empty();
935 assert_eq!(container.len(), 0);
936 assert!(container.is_empty());
937 }
938
939 #[test]
940 fn test_list_container_creation() {
941 let container = ListContainer::empty();
942 assert_eq!(container.len(), 0);
943 assert!(container.is_empty());
944 }
945
946 #[test]
947 fn test_verbatim_container_creation() {
948 let container = VerbatimContainer::empty();
949 assert_eq!(container.len(), 0);
950 assert!(container.is_empty());
951 }
952
953 #[test]
954 fn test_container_with_items() {
955 let para = Paragraph::from_line("Test".to_string());
956 let container = SessionContainer::from_typed(vec![SessionContent::Element(
957 ContentElement::Paragraph(para),
958 )]);
959 assert_eq!(container.len(), 1);
960 assert!(!container.is_empty());
961 }
962
963 #[test]
964 fn test_container_push() {
965 let mut container = GeneralContainer::empty();
966 let para = Paragraph::from_line("Test".to_string());
967 container.push(ContentItem::Paragraph(para));
968 assert_eq!(container.len(), 1);
969 }
970
971 #[test]
972 fn test_container_deref() {
973 let list_item = ListItem::new("-".to_string(), "Item".to_string());
974 let container = ListContainer::from_typed(vec![ListContent::ListItem(list_item)]);
975 assert_eq!(container.len(), 1);
977 assert!(!container.is_empty());
978 }
979
980 #[test]
985 fn test_iter_paragraphs_recursive() {
986 let mut inner_session = Session::with_title("Inner".to_string());
987 inner_session
988 .children
989 .push(ContentItem::Paragraph(Paragraph::from_line(
990 "Nested 2".to_string(),
991 )));
992
993 let mut outer_session = Session::with_title("Outer".to_string());
994 outer_session
995 .children
996 .push(ContentItem::Paragraph(Paragraph::from_line(
997 "Nested 1".to_string(),
998 )));
999 outer_session
1000 .children
1001 .push(ContentItem::Session(inner_session));
1002
1003 let mut container = SessionContainer::empty();
1004 container.push(ContentItem::Paragraph(Paragraph::from_line(
1005 "Top".to_string(),
1006 )));
1007 container.push(ContentItem::Session(outer_session));
1008
1009 assert_eq!(container.iter_paragraphs().count(), 1);
1010 let paragraphs: Vec<_> = container.iter_paragraphs_recursive().collect();
1011 assert_eq!(paragraphs.len(), 3);
1012 }
1013
1014 #[test]
1015 fn test_iter_sessions_recursive() {
1016 let inner_session = Session::with_title("Inner".to_string());
1017 let mut outer_session = Session::with_title("Outer".to_string());
1018 outer_session
1019 .children
1020 .push(ContentItem::Session(inner_session));
1021
1022 let mut container = SessionContainer::empty();
1023 container.push(ContentItem::Session(outer_session));
1024
1025 assert_eq!(container.iter_sessions().count(), 1);
1026 assert_eq!(container.iter_sessions_recursive().count(), 2);
1027 }
1028
1029 #[test]
1030 fn test_iter_all_nodes_with_depth() {
1031 let mut inner_session = Session::with_title("Inner".to_string());
1032 inner_session
1033 .children
1034 .push(ContentItem::Paragraph(Paragraph::from_line(
1035 "Deep".to_string(),
1036 )));
1037
1038 let mut outer_session = Session::with_title("Outer".to_string());
1039 outer_session
1040 .children
1041 .push(ContentItem::Session(inner_session));
1042
1043 let mut container = SessionContainer::empty();
1044 container.push(ContentItem::Paragraph(Paragraph::from_line(
1045 "Top".to_string(),
1046 )));
1047 container.push(ContentItem::Session(outer_session));
1048
1049 let nodes_with_depth: Vec<_> = container.iter_all_nodes_with_depth().collect();
1050 assert_eq!(nodes_with_depth.len(), 6);
1051 assert_eq!(nodes_with_depth[0].1, 0);
1052 assert!(nodes_with_depth[0].0.is_paragraph());
1053 assert_eq!(nodes_with_depth[1].1, 1);
1054 assert!(nodes_with_depth[1].0.is_text_line());
1055 }
1056
1057 #[test]
1062 fn test_find_paragraphs_with_predicate() {
1063 let mut container = SessionContainer::empty();
1064 container.push(ContentItem::Paragraph(Paragraph::from_line(
1065 "Hello, world!".to_string(),
1066 )));
1067 container.push(ContentItem::Paragraph(Paragraph::from_line(
1068 "Goodbye, world!".to_string(),
1069 )));
1070 container.push(ContentItem::Paragraph(Paragraph::from_line(
1071 "Hello again!".to_string(),
1072 )));
1073
1074 let hello_paras = container.find_paragraphs(|p| p.text().starts_with("Hello"));
1075 assert_eq!(hello_paras.len(), 2);
1076
1077 let goodbye_paras = container.find_paragraphs(|p| p.text().contains("Goodbye"));
1078 assert_eq!(goodbye_paras.len(), 1);
1079 }
1080
1081 #[test]
1082 fn test_find_sessions_with_predicate() {
1083 let mut session1 = Session::with_title("Chapter 1: Introduction".to_string());
1084 session1
1085 .children
1086 .push(ContentItem::Paragraph(Paragraph::from_line(
1087 "Intro".to_string(),
1088 )));
1089 let session2 = Session::with_title("Chapter 2: Advanced".to_string());
1090 let section = Session::with_title("Section 1.1".to_string());
1091 session1.children.push(ContentItem::Session(section));
1092
1093 let mut container = SessionContainer::empty();
1094 container.push(ContentItem::Session(session1));
1095 container.push(ContentItem::Session(session2));
1096
1097 let chapters = container.find_sessions(|s| s.title.as_string().contains("Chapter"));
1098 assert_eq!(chapters.len(), 2);
1099 }
1100
1101 #[test]
1102 fn test_find_nodes_generic_predicate() {
1103 let mut session = Session::with_title("Test".to_string());
1104 session
1105 .children
1106 .push(ContentItem::Paragraph(Paragraph::from_line(
1107 "Child 1".to_string(),
1108 )));
1109 session
1110 .children
1111 .push(ContentItem::Paragraph(Paragraph::from_line(
1112 "Child 2".to_string(),
1113 )));
1114 session
1115 .children
1116 .push(ContentItem::Paragraph(Paragraph::from_line(
1117 "Child 3".to_string(),
1118 )));
1119
1120 let mut container = SessionContainer::empty();
1121 container.push(ContentItem::Paragraph(Paragraph::from_line(
1122 "Top".to_string(),
1123 )));
1124 container.push(ContentItem::Session(session));
1125
1126 let big_sessions = container.find_nodes(|node| {
1127 matches!(node, ContentItem::Session(_))
1128 && node.children().map(|c| c.len() > 2).unwrap_or(false)
1129 });
1130 assert_eq!(big_sessions.len(), 1);
1131 }
1132
1133 #[test]
1138 fn test_find_nodes_at_depth() {
1139 let mut inner = Session::with_title("Inner".to_string());
1140 inner
1141 .children
1142 .push(ContentItem::Paragraph(Paragraph::from_line(
1143 "Deep".to_string(),
1144 )));
1145 let mut outer = Session::with_title("Outer".to_string());
1146 outer.children.push(ContentItem::Session(inner));
1147
1148 let mut container = SessionContainer::empty();
1149 container.push(ContentItem::Paragraph(Paragraph::from_line(
1150 "Top".to_string(),
1151 )));
1152 container.push(ContentItem::Session(outer));
1153
1154 assert_eq!(container.find_nodes_at_depth(0).len(), 2);
1155 assert!(!container.find_nodes_at_depth(1).is_empty());
1156 }
1157
1158 #[test]
1159 fn test_find_sessions_at_depth() {
1160 let mut level2 = Session::with_title("Level 2".to_string());
1161 level2
1162 .children
1163 .push(ContentItem::Paragraph(Paragraph::from_line(
1164 "Leaf".to_string(),
1165 )));
1166 let mut level1 = Session::with_title("Level 1".to_string());
1167 level1.children.push(ContentItem::Session(level2));
1168
1169 let mut container = SessionContainer::empty();
1170 container.push(ContentItem::Session(level1));
1171
1172 let level_0: Vec<_> = container
1173 .find_nodes_at_depth(0)
1174 .into_iter()
1175 .filter_map(|n| n.as_session())
1176 .collect();
1177 assert_eq!(level_0.len(), 1);
1178
1179 let level_1: Vec<_> = container
1180 .find_nodes_at_depth(1)
1181 .into_iter()
1182 .filter_map(|n| n.as_session())
1183 .collect();
1184 assert_eq!(level_1.len(), 1);
1185 }
1186
1187 #[test]
1188 fn test_find_nodes_in_depth_range() {
1189 let mut deep = Session::with_title("Deep".to_string());
1190 deep.children
1191 .push(ContentItem::Paragraph(Paragraph::from_line(
1192 "Very deep".to_string(),
1193 )));
1194 let mut mid = Session::with_title("Mid".to_string());
1195 mid.children.push(ContentItem::Session(deep));
1196
1197 let mut container = SessionContainer::empty();
1198 container.push(ContentItem::Paragraph(Paragraph::from_line(
1199 "Root".to_string(),
1200 )));
1201 container.push(ContentItem::Session(mid));
1202
1203 assert!(!container.find_nodes_in_depth_range(0, 1).is_empty());
1204 assert!(!container.find_nodes_in_depth_range(1, 2).is_empty());
1205 }
1206
1207 #[test]
1208 fn test_find_nodes_with_depth_and_predicate() {
1209 let mut session = Session::with_title("Test Session".to_string());
1210 session
1211 .children
1212 .push(ContentItem::Paragraph(Paragraph::from_line(
1213 "Hello from nested".to_string(),
1214 )));
1215
1216 let mut container = SessionContainer::empty();
1217 container.push(ContentItem::Paragraph(Paragraph::from_line(
1218 "Hello from top".to_string(),
1219 )));
1220 container.push(ContentItem::Session(session));
1221
1222 let depth_0_hello = container.find_nodes_with_depth(0, |node| {
1223 node.as_paragraph()
1224 .map(|p| p.text().contains("Hello"))
1225 .unwrap_or(false)
1226 });
1227 assert_eq!(depth_0_hello.len(), 1);
1228 }
1229
1230 #[test]
1235 fn test_comprehensive_query_api() {
1236 let mut chapter1 = Session::with_title("Chapter 1: Introduction".to_string());
1237 chapter1
1238 .children
1239 .push(ContentItem::Paragraph(Paragraph::from_line(
1240 "Hello, this is the intro.".to_string(),
1241 )));
1242
1243 let mut section1_1 = Session::with_title("Section 1.1".to_string());
1244 section1_1
1245 .children
1246 .push(ContentItem::Paragraph(Paragraph::from_line(
1247 "Nested content here.".to_string(),
1248 )));
1249 chapter1.children.push(ContentItem::Session(section1_1));
1250
1251 let mut chapter2 = Session::with_title("Chapter 2: Advanced".to_string());
1252 chapter2
1253 .children
1254 .push(ContentItem::Paragraph(Paragraph::from_line(
1255 "Advanced topics.".to_string(),
1256 )));
1257
1258 let mut container = SessionContainer::empty();
1259 container.push(ContentItem::Paragraph(Paragraph::from_line(
1260 "Preamble".to_string(),
1261 )));
1262 container.push(ContentItem::Session(chapter1));
1263 container.push(ContentItem::Session(chapter2));
1264
1265 assert_eq!(container.iter_paragraphs_recursive().count(), 4);
1266 assert_eq!(container.iter_sessions_recursive().count(), 3);
1267
1268 let hello_paragraphs: Vec<_> = container
1269 .iter_paragraphs_recursive()
1270 .filter(|p| p.text().contains("Hello"))
1271 .collect();
1272 assert_eq!(hello_paragraphs.len(), 1);
1273
1274 let nested_sessions: Vec<_> = container
1275 .iter_all_nodes_with_depth()
1276 .filter(|(node, depth)| node.is_session() && *depth >= 1)
1277 .collect();
1278 assert_eq!(nested_sessions.len(), 1);
1279 }
1280
1281 #[test]
1286 fn test_first_methods() {
1287 let mut container = SessionContainer::empty();
1288 container.push(ContentItem::Paragraph(Paragraph::from_line(
1289 "First para".to_string(),
1290 )));
1291
1292 assert!(container.first_paragraph().is_some());
1293 assert!(container.first_session().is_none());
1294 }
1295
1296 #[test]
1297 fn test_expect_paragraph() {
1298 let mut container = SessionContainer::empty();
1299 container.push(ContentItem::Paragraph(Paragraph::from_line(
1300 "Test".to_string(),
1301 )));
1302
1303 let para = container.expect_paragraph();
1304 assert_eq!(para.text(), "Test");
1305 }
1306
1307 #[test]
1308 #[should_panic(expected = "No paragraph found in container")]
1309 fn test_expect_paragraph_panics() {
1310 let container = SessionContainer::empty();
1311 container.expect_paragraph();
1312 }
1313
1314 #[test]
1319 fn test_count_by_type() {
1320 let mut container = SessionContainer::empty();
1321 container.push(ContentItem::Paragraph(Paragraph::from_line(
1322 "Para 1".to_string(),
1323 )));
1324 container.push(ContentItem::Paragraph(Paragraph::from_line(
1325 "Para 2".to_string(),
1326 )));
1327 container.push(ContentItem::Session(Session::with_title(
1328 "Session".to_string(),
1329 )));
1330
1331 let (paragraphs, sessions, lists, verbatim) = container.count_by_type();
1332 assert_eq!(paragraphs, 2);
1333 assert_eq!(sessions, 1);
1334 assert_eq!(lists, 0);
1335 assert_eq!(verbatim, 0);
1336 }
1337
1338 #[test]
1343 fn test_element_at_position_outside_document() {
1344 use crate::lex::ast::range::Position;
1345
1346 let mut container = SessionContainer::empty();
1347 container.push(ContentItem::Paragraph(Paragraph::from_line(
1348 "Test".to_string(),
1349 )));
1350
1351 let far_position = Position::new(1000, 1000);
1352 assert!(container.element_at(far_position).is_none());
1353 }
1354
1355 #[test]
1356 fn test_element_at_empty_container() {
1357 use crate::lex::ast::range::Position;
1358
1359 let container = SessionContainer::empty();
1360 let position = Position::new(1, 1);
1361 assert!(container.element_at(position).is_none());
1362 }
1363
1364 #[test]
1365 fn test_find_nodes_at_position_no_results() {
1366 use crate::lex::ast::range::Position;
1367
1368 let container = SessionContainer::empty();
1369 let position = Position::new(1, 1);
1370 let nodes = container.find_nodes_at_position(position);
1371 assert!(nodes.is_empty());
1372 }
1373
1374 #[test]
1375 fn test_format_at_position_no_nodes() {
1376 use crate::lex::ast::range::Position;
1377
1378 let container = SessionContainer::empty();
1379 let position = Position::new(1, 1);
1380 let output = container.format_at_position(position);
1381 assert_eq!(output, "No AST nodes at this position");
1382 }
1383}