1use super::super::range::{Position, Range};
43use super::super::text_content::TextContent;
44use super::super::traits::{AstNode, Container, Visitor};
45use super::annotation::Annotation;
46use super::content_item::ContentItem;
47use super::session::Session;
48use super::typed_content;
49use std::fmt;
50
51#[derive(Debug, Clone, PartialEq)]
62pub struct DocumentTitle {
63 pub content: TextContent,
64 pub subtitle: Option<TextContent>,
65 pub location: Range,
66}
67
68impl DocumentTitle {
69 pub fn new(content: TextContent, location: Range) -> Self {
70 Self {
71 content,
72 subtitle: None,
73 location,
74 }
75 }
76
77 pub fn with_subtitle(content: TextContent, subtitle: TextContent, location: Range) -> Self {
78 Self {
79 content,
80 subtitle: Some(subtitle),
81 location,
82 }
83 }
84
85 pub fn from_string(text: String, location: Range) -> Self {
86 Self {
87 content: TextContent::from_string(text, Some(location.clone())),
88 subtitle: None,
89 location,
90 }
91 }
92
93 pub fn as_str(&self) -> &str {
94 self.content.as_string()
95 }
96
97 pub fn subtitle_str(&self) -> Option<&str> {
98 self.subtitle.as_ref().map(|s| s.as_string())
99 }
100}
101
102impl AstNode for DocumentTitle {
103 fn node_type(&self) -> &'static str {
104 "DocumentTitle"
105 }
106
107 fn display_label(&self) -> String {
108 match &self.subtitle {
109 Some(sub) => format!(
110 "DocumentTitle(\"{}\", subtitle: \"{}\")",
111 self.as_str(),
112 sub.as_string()
113 ),
114 None => format!("DocumentTitle(\"{}\")", self.as_str()),
115 }
116 }
117
118 fn range(&self) -> &Range {
119 &self.location
120 }
121
122 fn accept(&self, _visitor: &mut dyn Visitor) {}
123}
124
125#[derive(Debug, Clone, PartialEq)]
126pub struct Document {
127 pub annotations: Vec<Annotation>,
128 pub title: Option<DocumentTitle>,
129 pub root: Session,
131}
132
133impl Document {
134 pub fn new() -> Self {
135 Self {
136 annotations: Vec::new(),
137 title: None,
138 root: Session::with_title(String::new()),
139 }
140 }
141
142 pub fn with_content(content: Vec<ContentItem>) -> Self {
143 let mut root = Session::with_title(String::new());
144 let session_content = typed_content::into_session_contents(content);
145 root.children = super::container::SessionContainer::from_typed(session_content);
146 Self {
147 annotations: Vec::new(),
148 title: None,
149 root,
150 }
151 }
152
153 pub fn from_root(root: Session) -> Self {
155 Self {
156 annotations: Vec::new(),
157 title: None,
158 root,
159 }
160 }
161
162 pub fn from_title_and_root(title: Option<DocumentTitle>, root: Session) -> Self {
164 Self {
165 annotations: Vec::new(),
166 title,
167 root,
168 }
169 }
170
171 pub fn with_annotations_and_content(
172 annotations: Vec<Annotation>,
173 content: Vec<ContentItem>,
174 ) -> Self {
175 let mut root = Session::with_title(String::new());
176 let session_content = typed_content::into_session_contents(content);
177 root.children = super::container::SessionContainer::from_typed(session_content);
178 Self {
179 annotations,
180 title: None,
181 root,
182 }
183 }
184
185 pub fn with_root_location(mut self, location: Range) -> Self {
186 self.root.location = location;
187 self
188 }
189
190 pub fn root_session(&self) -> &Session {
191 &self.root
192 }
193
194 pub fn root_session_mut(&mut self) -> &mut Session {
195 &mut self.root
196 }
197
198 pub fn into_root(self) -> Session {
199 self.root
200 }
201
202 pub fn title(&self) -> &str {
206 match &self.title {
207 Some(dt) => dt.as_str(),
208 None => "",
209 }
210 }
211
212 pub fn set_title(&mut self, title: String) {
214 if title.is_empty() {
215 self.title = None;
216 } else {
217 let location = Range::default();
218 self.title = Some(DocumentTitle::from_string(title, location));
219 }
220 }
221
222 pub fn node_path_at_position(&self, pos: Position) -> Vec<&dyn AstNode> {
224 let path = self.root.node_path_at_position(pos);
225 if !path.is_empty() {
226 let mut nodes: Vec<&dyn AstNode> = Vec::with_capacity(path.len() + 1);
227 nodes.push(self);
228 nodes.extend(path);
229 nodes
230 } else {
231 Vec::new()
232 }
233 }
234
235 pub fn element_at(&self, pos: Position) -> Option<&ContentItem> {
237 self.root.element_at(pos)
238 }
239
240 pub fn visual_line_at(&self, pos: Position) -> Option<&ContentItem> {
242 self.root.visual_line_at(pos)
243 }
244
245 pub fn block_element_at(&self, pos: Position) -> Option<&ContentItem> {
247 self.root.block_element_at(pos)
248 }
249
250 pub fn annotations(&self) -> &[Annotation] {
252 &self.annotations
253 }
254
255 pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
257 &mut self.annotations
258 }
259
260 pub fn iter_annotations(&self) -> std::slice::Iter<'_, Annotation> {
262 self.annotations.iter()
263 }
264
265 pub fn iter_annotation_contents(&self) -> impl Iterator<Item = &ContentItem> {
267 self.annotations
268 .iter()
269 .flat_map(|annotation| annotation.children())
270 }
271
272 pub fn find_annotation_by_label(&self, label: &str) -> Option<&Annotation> {
296 self.annotations
298 .iter()
299 .find(|ann| ann.data.label.value == label)
300 .or_else(|| self.root.find_annotation_by_label(label))
301 }
302
303 pub fn find_annotation_by_label_in_origin(
327 &self,
328 label: &str,
329 origin: Option<&std::path::Path>,
330 ) -> Option<&Annotation> {
331 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
332 ann.data.label.value == label && ann.location.origin() == origin
333 }
334
335 if let Some(ann) = self.annotations.iter().find(|a| matches(a, label, origin)) {
337 return Some(ann);
338 }
339 find_annotation_in_session_with_origin(&self.root, label, origin)
342 }
343
344 pub fn find_annotations_by_label(&self, label: &str) -> Vec<&Annotation> {
364 let mut results: Vec<&Annotation> = self
365 .annotations
366 .iter()
367 .filter(|ann| ann.data.label.value == label)
368 .collect();
369
370 results.extend(self.root.find_annotations_by_label(label));
371 results
372 }
373
374 pub fn iter_all_references(
397 &self,
398 ) -> Box<dyn Iterator<Item = crate::lex::inlines::ReferenceInline> + '_> {
399 let title_refs = self
400 .title
401 .iter()
402 .flat_map(|t| {
403 let title_inlines = t.content.inline_items();
404 let subtitle_inlines = t
405 .subtitle
406 .iter()
407 .flat_map(|s| s.inline_items())
408 .collect::<Vec<_>>();
409 title_inlines.into_iter().chain(subtitle_inlines)
410 })
411 .filter_map(|node| {
412 if let crate::lex::inlines::InlineNode::Reference { data, .. } = node {
413 Some(data)
414 } else {
415 None
416 }
417 });
418 Box::new(title_refs.chain(self.root.iter_all_references()))
419 }
420
421 pub fn find_references_to(&self, target: &str) -> Vec<crate::lex::inlines::ReferenceInline> {
439 self.root.find_references_to(target)
440 }
441}
442
443impl AstNode for Document {
444 fn node_type(&self) -> &'static str {
445 "Document"
446 }
447
448 fn display_label(&self) -> String {
449 format!(
450 "Document ({} annotations, {} items)",
451 self.annotations.len(),
452 self.root.children.len()
453 )
454 }
455
456 fn range(&self) -> &Range {
457 &self.root.location
458 }
459
460 fn accept(&self, visitor: &mut dyn Visitor) {
461 for annotation in &self.annotations {
462 annotation.accept(visitor);
463 }
464 if let Some(title) = &self.title {
465 title.accept(visitor);
466 }
467 self.root.accept(visitor);
468 }
469}
470
471impl Default for Document {
472 fn default() -> Self {
473 Self::new()
474 }
475}
476
477fn find_annotation_in_session_with_origin<'a>(
481 s: &'a Session,
482 label: &str,
483 origin: Option<&std::path::Path>,
484) -> Option<&'a Annotation> {
485 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
486 ann.data.label.value == label && ann.location.origin() == origin
487 }
488 for ann in &s.annotations {
489 if matches(ann, label, origin) {
490 return Some(ann);
491 }
492 }
493 find_annotation_in_items_with_origin(&s.children, label, origin)
494}
495
496fn find_annotation_in_items_with_origin<'a>(
497 items: &'a [ContentItem],
498 label: &str,
499 origin: Option<&std::path::Path>,
500) -> Option<&'a Annotation> {
501 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
502 ann.data.label.value == label && ann.location.origin() == origin
503 }
504 for item in items {
505 let attached: &[Annotation] = match item {
511 ContentItem::Session(s) => &s.annotations,
512 ContentItem::Definition(d) => &d.annotations,
513 ContentItem::ListItem(li) => &li.annotations,
514 ContentItem::Paragraph(p) => &p.annotations,
515 ContentItem::List(l) => &l.annotations,
516 ContentItem::Table(t) => &t.annotations,
517 ContentItem::VerbatimBlock(v) => &v.annotations,
518 _ => &[],
519 };
520 for ann in attached {
521 if matches(ann, label, origin) {
522 return Some(ann);
523 }
524 }
525 if let ContentItem::Annotation(a) = item {
527 if matches(a, label, origin) {
528 return Some(a);
529 }
530 if let Some(found) = find_annotation_in_items_with_origin(&a.children, label, origin) {
532 return Some(found);
533 }
534 }
535 match item {
537 ContentItem::Session(s) => {
538 if let Some(found) = find_annotation_in_session_with_origin(s, label, origin) {
539 return Some(found);
540 }
541 }
542 ContentItem::Definition(d) => {
543 if let Some(found) =
544 find_annotation_in_items_with_origin(&d.children, label, origin)
545 {
546 return Some(found);
547 }
548 }
549 ContentItem::ListItem(li) => {
550 if let Some(found) =
551 find_annotation_in_items_with_origin(&li.children, label, origin)
552 {
553 return Some(found);
554 }
555 }
556 ContentItem::List(l) => {
557 if let Some(found) = find_annotation_in_items_with_origin(&l.items, label, origin) {
558 return Some(found);
559 }
560 }
561 _ => {}
562 }
563 }
564 None
565}
566
567impl fmt::Display for Document {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 write!(
570 f,
571 "Document({} annotations, {} items)",
572 self.annotations.len(),
573 self.root.children.len()
574 )
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::super::super::range::Position;
581 use super::super::paragraph::{Paragraph, TextLine};
582 use super::super::session::Session;
583 use super::*;
584 use crate::lex::ast::text_content::TextContent;
585 use crate::lex::ast::traits::AstNode;
586
587 #[test]
588 fn test_document_creation() {
589 let doc = Document::with_content(vec![
590 ContentItem::Paragraph(Paragraph::from_line("Para 1".to_string())),
591 ContentItem::Session(Session::with_title("Section 1".to_string())),
592 ]);
593 assert_eq!(doc.annotations.len(), 0);
594 assert_eq!(doc.root.children.len(), 2);
595 }
596
597 #[test]
598 fn test_document_element_at() {
599 let text_line1 = TextLine::new(TextContent::from_string("First".to_string(), None))
600 .at(Range::new(0..0, Position::new(0, 0), Position::new(0, 5)));
601 let para1 = Paragraph::new(vec![ContentItem::TextLine(text_line1)]).at(Range::new(
602 0..0,
603 Position::new(0, 0),
604 Position::new(0, 5),
605 ));
606
607 let text_line2 = TextLine::new(TextContent::from_string("Second".to_string(), None))
608 .at(Range::new(0..0, Position::new(1, 0), Position::new(1, 6)));
609 let para2 = Paragraph::new(vec![ContentItem::TextLine(text_line2)]).at(Range::new(
610 0..0,
611 Position::new(1, 0),
612 Position::new(1, 6),
613 ));
614
615 let doc = Document::with_content(vec![
616 ContentItem::Paragraph(para1),
617 ContentItem::Paragraph(para2),
618 ]);
619
620 let result = doc.root.element_at(Position::new(1, 3));
621 assert!(result.is_some(), "Expected to find element at position");
622 assert!(result.unwrap().is_text_line());
623 }
624
625 #[test]
626 fn test_document_traits() {
627 let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
628 "Line".to_string(),
629 ))]);
630
631 assert_eq!(doc.node_type(), "Document");
632 assert_eq!(doc.display_label(), "Document (0 annotations, 1 items)");
633 assert_eq!(doc.root.children.len(), 1);
634 }
635
636 #[test]
637 fn test_root_session_accessors() {
638 let doc = Document::with_content(vec![ContentItem::Session(Session::with_title(
639 "Section".to_string(),
640 ))]);
641
642 assert_eq!(doc.root_session().children.len(), 1);
643
644 let mut doc = doc;
645 doc.root_session_mut().title = TextContent::from_string("Updated".to_string(), None);
646 assert_eq!(doc.root_session().title.as_string(), "Updated");
647
648 let root = doc.into_root();
649 assert_eq!(root.title.as_string(), "Updated");
650 }
651
652 #[test]
653 fn test_document_title_field() {
654 let mut doc = Document::new();
655 assert!(doc.title.is_none());
656 assert_eq!(doc.title(), "");
657
658 doc.set_title("My Title".to_string());
659 assert!(doc.title.is_some());
660 assert_eq!(doc.title(), "My Title");
661
662 doc.set_title(String::new());
663 assert!(doc.title.is_none());
664 assert_eq!(doc.title(), "");
665 }
666
667 #[test]
668 fn test_from_title_and_root() {
669 let title = DocumentTitle::from_string("Test Title".to_string(), Range::default());
670 let root = Session::with_title(String::new());
671 let doc = Document::from_title_and_root(Some(title), root);
672 assert_eq!(doc.title(), "Test Title");
673 }
674}