lex_core/lex/ast/elements/
document.rs1use 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_annotations_by_label(&self, label: &str) -> Vec<&Annotation> {
323 let mut results: Vec<&Annotation> = self
324 .annotations
325 .iter()
326 .filter(|ann| ann.data.label.value == label)
327 .collect();
328
329 results.extend(self.root.find_annotations_by_label(label));
330 results
331 }
332
333 pub fn iter_all_references(
356 &self,
357 ) -> Box<dyn Iterator<Item = crate::lex::inlines::ReferenceInline> + '_> {
358 let title_refs = self
359 .title
360 .iter()
361 .flat_map(|t| {
362 let title_inlines = t.content.inline_items();
363 let subtitle_inlines = t
364 .subtitle
365 .iter()
366 .flat_map(|s| s.inline_items())
367 .collect::<Vec<_>>();
368 title_inlines.into_iter().chain(subtitle_inlines)
369 })
370 .filter_map(|node| {
371 if let crate::lex::inlines::InlineNode::Reference { data, .. } = node {
372 Some(data)
373 } else {
374 None
375 }
376 });
377 Box::new(title_refs.chain(self.root.iter_all_references()))
378 }
379
380 pub fn find_references_to(&self, target: &str) -> Vec<crate::lex::inlines::ReferenceInline> {
398 self.root.find_references_to(target)
399 }
400}
401
402impl AstNode for Document {
403 fn node_type(&self) -> &'static str {
404 "Document"
405 }
406
407 fn display_label(&self) -> String {
408 format!(
409 "Document ({} annotations, {} items)",
410 self.annotations.len(),
411 self.root.children.len()
412 )
413 }
414
415 fn range(&self) -> &Range {
416 &self.root.location
417 }
418
419 fn accept(&self, visitor: &mut dyn Visitor) {
420 for annotation in &self.annotations {
421 annotation.accept(visitor);
422 }
423 if let Some(title) = &self.title {
424 title.accept(visitor);
425 }
426 self.root.accept(visitor);
427 }
428}
429
430impl Default for Document {
431 fn default() -> Self {
432 Self::new()
433 }
434}
435
436impl fmt::Display for Document {
437 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438 write!(
439 f,
440 "Document({} annotations, {} items)",
441 self.annotations.len(),
442 self.root.children.len()
443 )
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::super::super::range::Position;
450 use super::super::paragraph::{Paragraph, TextLine};
451 use super::super::session::Session;
452 use super::*;
453 use crate::lex::ast::text_content::TextContent;
454 use crate::lex::ast::traits::AstNode;
455
456 #[test]
457 fn test_document_creation() {
458 let doc = Document::with_content(vec![
459 ContentItem::Paragraph(Paragraph::from_line("Para 1".to_string())),
460 ContentItem::Session(Session::with_title("Section 1".to_string())),
461 ]);
462 assert_eq!(doc.annotations.len(), 0);
463 assert_eq!(doc.root.children.len(), 2);
464 }
465
466 #[test]
467 fn test_document_element_at() {
468 let text_line1 = TextLine::new(TextContent::from_string("First".to_string(), None))
469 .at(Range::new(0..0, Position::new(0, 0), Position::new(0, 5)));
470 let para1 = Paragraph::new(vec![ContentItem::TextLine(text_line1)]).at(Range::new(
471 0..0,
472 Position::new(0, 0),
473 Position::new(0, 5),
474 ));
475
476 let text_line2 = TextLine::new(TextContent::from_string("Second".to_string(), None))
477 .at(Range::new(0..0, Position::new(1, 0), Position::new(1, 6)));
478 let para2 = Paragraph::new(vec![ContentItem::TextLine(text_line2)]).at(Range::new(
479 0..0,
480 Position::new(1, 0),
481 Position::new(1, 6),
482 ));
483
484 let doc = Document::with_content(vec![
485 ContentItem::Paragraph(para1),
486 ContentItem::Paragraph(para2),
487 ]);
488
489 let result = doc.root.element_at(Position::new(1, 3));
490 assert!(result.is_some(), "Expected to find element at position");
491 assert!(result.unwrap().is_text_line());
492 }
493
494 #[test]
495 fn test_document_traits() {
496 let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
497 "Line".to_string(),
498 ))]);
499
500 assert_eq!(doc.node_type(), "Document");
501 assert_eq!(doc.display_label(), "Document (0 annotations, 1 items)");
502 assert_eq!(doc.root.children.len(), 1);
503 }
504
505 #[test]
506 fn test_root_session_accessors() {
507 let doc = Document::with_content(vec![ContentItem::Session(Session::with_title(
508 "Section".to_string(),
509 ))]);
510
511 assert_eq!(doc.root_session().children.len(), 1);
512
513 let mut doc = doc;
514 doc.root_session_mut().title = TextContent::from_string("Updated".to_string(), None);
515 assert_eq!(doc.root_session().title.as_string(), "Updated");
516
517 let root = doc.into_root();
518 assert_eq!(root.title.as_string(), "Updated");
519 }
520
521 #[test]
522 fn test_document_title_field() {
523 let mut doc = Document::new();
524 assert!(doc.title.is_none());
525 assert_eq!(doc.title(), "");
526
527 doc.set_title("My Title".to_string());
528 assert!(doc.title.is_some());
529 assert_eq!(doc.title(), "My Title");
530
531 doc.set_title(String::new());
532 assert!(doc.title.is_none());
533 assert_eq!(doc.title(), "");
534 }
535
536 #[test]
537 fn test_from_title_and_root() {
538 let title = DocumentTitle::from_string("Test Title".to_string(), Range::default());
539 let root = Session::with_title(String::new());
540 let doc = Document::from_title_and_root(Some(title), root);
541 assert_eq!(doc.title(), "Test Title");
542 }
543}