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