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 pub reference_lines: Vec<super::super::anchoring::ReferenceLine>,
138 pub reference_line_diagnostics: Vec<super::super::diagnostics::Diagnostic>,
141}
142
143impl Document {
144 pub fn new() -> Self {
145 Self {
146 annotations: Vec::new(),
147 title: None,
148 root: Session::with_title(String::new()),
149 reference_lines: Vec::new(),
150 reference_line_diagnostics: Vec::new(),
151 }
152 }
153
154 pub fn with_content(content: Vec<ContentItem>) -> Self {
155 let mut root = Session::with_title(String::new());
156 let session_content = typed_content::into_session_contents(content);
157 root.children = super::container::SessionContainer::from_typed(session_content);
158 Self {
159 annotations: Vec::new(),
160 title: None,
161 root,
162 reference_lines: Vec::new(),
163 reference_line_diagnostics: Vec::new(),
164 }
165 }
166
167 pub fn from_root(root: Session) -> Self {
169 Self {
170 annotations: Vec::new(),
171 title: None,
172 root,
173 reference_lines: Vec::new(),
174 reference_line_diagnostics: Vec::new(),
175 }
176 }
177
178 pub fn from_title_and_root(title: Option<DocumentTitle>, root: Session) -> Self {
180 Self {
181 annotations: Vec::new(),
182 title,
183 root,
184 reference_lines: Vec::new(),
185 reference_line_diagnostics: Vec::new(),
186 }
187 }
188
189 pub fn with_annotations_and_content(
190 annotations: Vec<Annotation>,
191 content: Vec<ContentItem>,
192 ) -> Self {
193 let mut root = Session::with_title(String::new());
194 let session_content = typed_content::into_session_contents(content);
195 root.children = super::container::SessionContainer::from_typed(session_content);
196 Self {
197 annotations,
198 title: None,
199 root,
200 reference_lines: Vec::new(),
201 reference_line_diagnostics: Vec::new(),
202 }
203 }
204
205 pub fn with_root_location(mut self, location: Range) -> Self {
206 self.root.location = location;
207 self
208 }
209
210 pub fn reference_lines(&self) -> &[super::super::anchoring::ReferenceLine] {
215 &self.reference_lines
216 }
217
218 pub fn root_session(&self) -> &Session {
219 &self.root
220 }
221
222 pub fn root_session_mut(&mut self) -> &mut Session {
223 &mut self.root
224 }
225
226 pub fn into_root(self) -> Session {
227 self.root
228 }
229
230 pub fn title(&self) -> &str {
234 match &self.title {
235 Some(dt) => dt.as_str(),
236 None => "",
237 }
238 }
239
240 pub fn set_title(&mut self, title: String) {
242 if title.is_empty() {
243 self.title = None;
244 } else {
245 let location = Range::default();
246 self.title = Some(DocumentTitle::from_string(title, location));
247 }
248 }
249
250 pub fn node_path_at_position(&self, pos: Position) -> Vec<&dyn AstNode> {
252 let path = self.root.node_path_at_position(pos);
253 if !path.is_empty() {
254 let mut nodes: Vec<&dyn AstNode> = Vec::with_capacity(path.len() + 1);
255 nodes.push(self);
256 nodes.extend(path);
257 nodes
258 } else {
259 Vec::new()
260 }
261 }
262
263 pub fn element_at(&self, pos: Position) -> Option<&ContentItem> {
265 self.root.element_at(pos)
266 }
267
268 pub fn visual_line_at(&self, pos: Position) -> Option<&ContentItem> {
270 self.root.visual_line_at(pos)
271 }
272
273 pub fn block_element_at(&self, pos: Position) -> Option<&ContentItem> {
275 self.root.block_element_at(pos)
276 }
277
278 pub fn annotations(&self) -> &[Annotation] {
280 &self.annotations
281 }
282
283 pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
285 &mut self.annotations
286 }
287
288 pub fn iter_annotations(&self) -> std::slice::Iter<'_, Annotation> {
290 self.annotations.iter()
291 }
292
293 pub fn iter_annotation_contents(&self) -> impl Iterator<Item = &ContentItem> {
295 self.annotations
296 .iter()
297 .flat_map(|annotation| annotation.children())
298 }
299
300 pub fn find_annotation_by_label(&self, label: &str) -> Option<&Annotation> {
324 self.annotations
326 .iter()
327 .find(|ann| ann.data.label.value == label)
328 .or_else(|| self.root.find_annotation_by_label(label))
329 }
330
331 pub fn find_annotation_by_label_in_origin(
355 &self,
356 label: &str,
357 origin: Option<&std::path::Path>,
358 ) -> Option<&Annotation> {
359 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
360 ann.data.label.value == label && ann.location.origin() == origin
361 }
362
363 if let Some(ann) = self.annotations.iter().find(|a| matches(a, label, origin)) {
365 return Some(ann);
366 }
367 find_annotation_in_session_with_origin(&self.root, label, origin)
370 }
371
372 pub fn find_annotations_by_label(&self, label: &str) -> Vec<&Annotation> {
392 let mut results: Vec<&Annotation> = self
393 .annotations
394 .iter()
395 .filter(|ann| ann.data.label.value == label)
396 .collect();
397
398 results.extend(self.root.find_annotations_by_label(label));
399 results
400 }
401
402 pub fn iter_all_references(
425 &self,
426 ) -> Box<dyn Iterator<Item = crate::lex::inlines::ReferenceInline> + '_> {
427 let title_refs = self
428 .title
429 .iter()
430 .flat_map(|t| {
431 let title_inlines = t.content.inline_items();
432 let subtitle_inlines = t
433 .subtitle
434 .iter()
435 .flat_map(|s| s.inline_items())
436 .collect::<Vec<_>>();
437 title_inlines.into_iter().chain(subtitle_inlines)
438 })
439 .filter_map(|node| {
440 if let crate::lex::inlines::InlineNode::Reference { data, .. } = node {
441 Some(data)
442 } else {
443 None
444 }
445 });
446 Box::new(title_refs.chain(self.root.iter_all_references()))
447 }
448
449 pub fn find_references_to(&self, target: &str) -> Vec<crate::lex::inlines::ReferenceInline> {
467 self.root.find_references_to(target)
468 }
469}
470
471impl AstNode for Document {
472 fn node_type(&self) -> &'static str {
473 "Document"
474 }
475
476 fn display_label(&self) -> String {
477 format!(
478 "Document ({} annotations, {} items)",
479 self.annotations.len(),
480 self.root.children.len()
481 )
482 }
483
484 fn range(&self) -> &Range {
485 &self.root.location
486 }
487
488 fn accept(&self, visitor: &mut dyn Visitor) {
489 for annotation in &self.annotations {
490 annotation.accept(visitor);
491 }
492 if let Some(title) = &self.title {
493 title.accept(visitor);
494 }
495 self.root.accept(visitor);
496 }
497}
498
499impl Default for Document {
500 fn default() -> Self {
501 Self::new()
502 }
503}
504
505fn find_annotation_in_session_with_origin<'a>(
509 s: &'a Session,
510 label: &str,
511 origin: Option<&std::path::Path>,
512) -> Option<&'a Annotation> {
513 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
514 ann.data.label.value == label && ann.location.origin() == origin
515 }
516 for ann in &s.annotations {
517 if matches(ann, label, origin) {
518 return Some(ann);
519 }
520 }
521 find_annotation_in_items_with_origin(&s.children, label, origin)
522}
523
524fn find_annotation_in_items_with_origin<'a>(
525 items: &'a [ContentItem],
526 label: &str,
527 origin: Option<&std::path::Path>,
528) -> Option<&'a Annotation> {
529 fn matches(ann: &Annotation, label: &str, origin: Option<&std::path::Path>) -> bool {
530 ann.data.label.value == label && ann.location.origin() == origin
531 }
532 for item in items {
533 let attached: &[Annotation] = match item {
539 ContentItem::Session(s) => &s.annotations,
540 ContentItem::Definition(d) => &d.annotations,
541 ContentItem::ListItem(li) => &li.annotations,
542 ContentItem::Paragraph(p) => &p.annotations,
543 ContentItem::List(l) => &l.annotations,
544 ContentItem::Table(t) => &t.annotations,
545 ContentItem::VerbatimBlock(v) => &v.annotations,
546 _ => &[],
547 };
548 for ann in attached {
549 if matches(ann, label, origin) {
550 return Some(ann);
551 }
552 }
553 if let ContentItem::Annotation(a) = item {
555 if matches(a, label, origin) {
556 return Some(a);
557 }
558 if let Some(found) = find_annotation_in_items_with_origin(&a.children, label, origin) {
560 return Some(found);
561 }
562 }
563 match item {
565 ContentItem::Session(s) => {
566 if let Some(found) = find_annotation_in_session_with_origin(s, label, origin) {
567 return Some(found);
568 }
569 }
570 ContentItem::Definition(d) => {
571 if let Some(found) =
572 find_annotation_in_items_with_origin(&d.children, label, origin)
573 {
574 return Some(found);
575 }
576 }
577 ContentItem::ListItem(li) => {
578 if let Some(found) =
579 find_annotation_in_items_with_origin(&li.children, label, origin)
580 {
581 return Some(found);
582 }
583 }
584 ContentItem::List(l) => {
585 if let Some(found) = find_annotation_in_items_with_origin(&l.items, label, origin) {
586 return Some(found);
587 }
588 }
589 _ => {}
590 }
591 }
592 None
593}
594
595impl fmt::Display for Document {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 write!(
598 f,
599 "Document({} annotations, {} items)",
600 self.annotations.len(),
601 self.root.children.len()
602 )
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use super::super::super::range::Position;
609 use super::super::paragraph::{Paragraph, TextLine};
610 use super::super::session::Session;
611 use super::*;
612 use crate::lex::ast::text_content::TextContent;
613 use crate::lex::ast::traits::AstNode;
614
615 #[test]
616 fn test_document_creation() {
617 let doc = Document::with_content(vec![
618 ContentItem::Paragraph(Paragraph::from_line("Para 1".to_string())),
619 ContentItem::Session(Session::with_title("Section 1".to_string())),
620 ]);
621 assert_eq!(doc.annotations.len(), 0);
622 assert_eq!(doc.root.children.len(), 2);
623 }
624
625 #[test]
626 fn test_document_element_at() {
627 let text_line1 = TextLine::new(TextContent::from_string("First".to_string(), None))
628 .at(Range::new(0..0, Position::new(0, 0), Position::new(0, 5)));
629 let para1 = Paragraph::new(vec![ContentItem::TextLine(text_line1)]).at(Range::new(
630 0..0,
631 Position::new(0, 0),
632 Position::new(0, 5),
633 ));
634
635 let text_line2 = TextLine::new(TextContent::from_string("Second".to_string(), None))
636 .at(Range::new(0..0, Position::new(1, 0), Position::new(1, 6)));
637 let para2 = Paragraph::new(vec![ContentItem::TextLine(text_line2)]).at(Range::new(
638 0..0,
639 Position::new(1, 0),
640 Position::new(1, 6),
641 ));
642
643 let doc = Document::with_content(vec![
644 ContentItem::Paragraph(para1),
645 ContentItem::Paragraph(para2),
646 ]);
647
648 let result = doc.root.element_at(Position::new(1, 3));
649 assert!(result.is_some(), "Expected to find element at position");
650 assert!(result.unwrap().is_text_line());
651 }
652
653 #[test]
654 fn test_document_traits() {
655 let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
656 "Line".to_string(),
657 ))]);
658
659 assert_eq!(doc.node_type(), "Document");
660 assert_eq!(doc.display_label(), "Document (0 annotations, 1 items)");
661 assert_eq!(doc.root.children.len(), 1);
662 }
663
664 #[test]
665 fn test_root_session_accessors() {
666 let doc = Document::with_content(vec![ContentItem::Session(Session::with_title(
667 "Section".to_string(),
668 ))]);
669
670 assert_eq!(doc.root_session().children.len(), 1);
671
672 let mut doc = doc;
673 doc.root_session_mut().title = TextContent::from_string("Updated".to_string(), None);
674 assert_eq!(doc.root_session().title.as_string(), "Updated");
675
676 let root = doc.into_root();
677 assert_eq!(root.title.as_string(), "Updated");
678 }
679
680 #[test]
681 fn test_document_title_field() {
682 let mut doc = Document::new();
683 assert!(doc.title.is_none());
684 assert_eq!(doc.title(), "");
685
686 doc.set_title("My Title".to_string());
687 assert!(doc.title.is_some());
688 assert_eq!(doc.title(), "My Title");
689
690 doc.set_title(String::new());
691 assert!(doc.title.is_none());
692 assert_eq!(doc.title(), "");
693 }
694
695 #[test]
696 fn test_from_title_and_root() {
697 let title = DocumentTitle::from_string("Test Title".to_string(), Range::default());
698 let root = Session::with_title(String::new());
699 let doc = Document::from_title_and_root(Some(title), root);
700 assert_eq!(doc.title(), "Test Title");
701 }
702}