use super::super::range::{Position, Range};
use super::super::text_content::TextContent;
use super::super::traits::{AstNode, Container, Visitor};
use super::annotation::Annotation;
use super::content_item::ContentItem;
use super::session::Session;
use super::typed_content;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct DocumentTitle {
pub content: TextContent,
pub subtitle: Option<TextContent>,
pub location: Range,
}
impl DocumentTitle {
pub fn new(content: TextContent, location: Range) -> Self {
Self {
content,
subtitle: None,
location,
}
}
pub fn with_subtitle(content: TextContent, subtitle: TextContent, location: Range) -> Self {
Self {
content,
subtitle: Some(subtitle),
location,
}
}
pub fn from_string(text: String, location: Range) -> Self {
Self {
content: TextContent::from_string(text, Some(location.clone())),
subtitle: None,
location,
}
}
pub fn as_str(&self) -> &str {
self.content.as_string()
}
pub fn subtitle_str(&self) -> Option<&str> {
self.subtitle.as_ref().map(|s| s.as_string())
}
}
impl AstNode for DocumentTitle {
fn node_type(&self) -> &'static str {
"DocumentTitle"
}
fn display_label(&self) -> String {
match &self.subtitle {
Some(sub) => format!(
"DocumentTitle(\"{}\", subtitle: \"{}\")",
self.as_str(),
sub.as_string()
),
None => format!("DocumentTitle(\"{}\")", self.as_str()),
}
}
fn range(&self) -> &Range {
&self.location
}
fn accept(&self, _visitor: &mut dyn Visitor) {}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
pub annotations: Vec<Annotation>,
pub title: Option<DocumentTitle>,
pub root: Session,
}
impl Document {
pub fn new() -> Self {
Self {
annotations: Vec::new(),
title: None,
root: Session::with_title(String::new()),
}
}
pub fn with_content(content: Vec<ContentItem>) -> Self {
let mut root = Session::with_title(String::new());
let session_content = typed_content::into_session_contents(content);
root.children = super::container::SessionContainer::from_typed(session_content);
Self {
annotations: Vec::new(),
title: None,
root,
}
}
pub fn from_root(root: Session) -> Self {
Self {
annotations: Vec::new(),
title: None,
root,
}
}
pub fn from_title_and_root(title: Option<DocumentTitle>, root: Session) -> Self {
Self {
annotations: Vec::new(),
title,
root,
}
}
pub fn with_annotations_and_content(
annotations: Vec<Annotation>,
content: Vec<ContentItem>,
) -> Self {
let mut root = Session::with_title(String::new());
let session_content = typed_content::into_session_contents(content);
root.children = super::container::SessionContainer::from_typed(session_content);
Self {
annotations,
title: None,
root,
}
}
pub fn with_root_location(mut self, location: Range) -> Self {
self.root.location = location;
self
}
pub fn root_session(&self) -> &Session {
&self.root
}
pub fn root_session_mut(&mut self) -> &mut Session {
&mut self.root
}
pub fn into_root(self) -> Session {
self.root
}
pub fn title(&self) -> &str {
match &self.title {
Some(dt) => dt.as_str(),
None => "",
}
}
pub fn set_title(&mut self, title: String) {
if title.is_empty() {
self.title = None;
} else {
let location = Range::default();
self.title = Some(DocumentTitle::from_string(title, location));
}
}
pub fn node_path_at_position(&self, pos: Position) -> Vec<&dyn AstNode> {
let path = self.root.node_path_at_position(pos);
if !path.is_empty() {
let mut nodes: Vec<&dyn AstNode> = Vec::with_capacity(path.len() + 1);
nodes.push(self);
nodes.extend(path);
nodes
} else {
Vec::new()
}
}
pub fn element_at(&self, pos: Position) -> Option<&ContentItem> {
self.root.element_at(pos)
}
pub fn visual_line_at(&self, pos: Position) -> Option<&ContentItem> {
self.root.visual_line_at(pos)
}
pub fn block_element_at(&self, pos: Position) -> Option<&ContentItem> {
self.root.block_element_at(pos)
}
pub fn annotations(&self) -> &[Annotation] {
&self.annotations
}
pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
&mut self.annotations
}
pub fn iter_annotations(&self) -> std::slice::Iter<'_, Annotation> {
self.annotations.iter()
}
pub fn iter_annotation_contents(&self) -> impl Iterator<Item = &ContentItem> {
self.annotations
.iter()
.flat_map(|annotation| annotation.children())
}
pub fn find_annotation_by_label(&self, label: &str) -> Option<&Annotation> {
self.annotations
.iter()
.find(|ann| ann.data.label.value == label)
.or_else(|| self.root.find_annotation_by_label(label))
}
pub fn find_annotations_by_label(&self, label: &str) -> Vec<&Annotation> {
let mut results: Vec<&Annotation> = self
.annotations
.iter()
.filter(|ann| ann.data.label.value == label)
.collect();
results.extend(self.root.find_annotations_by_label(label));
results
}
pub fn iter_all_references(
&self,
) -> Box<dyn Iterator<Item = crate::lex::inlines::ReferenceInline> + '_> {
let title_refs = self
.title
.iter()
.flat_map(|t| {
let title_inlines = t.content.inline_items();
let subtitle_inlines = t
.subtitle
.iter()
.flat_map(|s| s.inline_items())
.collect::<Vec<_>>();
title_inlines.into_iter().chain(subtitle_inlines)
})
.filter_map(|node| {
if let crate::lex::inlines::InlineNode::Reference { data, .. } = node {
Some(data)
} else {
None
}
});
Box::new(title_refs.chain(self.root.iter_all_references()))
}
pub fn find_references_to(&self, target: &str) -> Vec<crate::lex::inlines::ReferenceInline> {
self.root.find_references_to(target)
}
}
impl AstNode for Document {
fn node_type(&self) -> &'static str {
"Document"
}
fn display_label(&self) -> String {
format!(
"Document ({} annotations, {} items)",
self.annotations.len(),
self.root.children.len()
)
}
fn range(&self) -> &Range {
&self.root.location
}
fn accept(&self, visitor: &mut dyn Visitor) {
for annotation in &self.annotations {
annotation.accept(visitor);
}
if let Some(title) = &self.title {
title.accept(visitor);
}
self.root.accept(visitor);
}
}
impl Default for Document {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for Document {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Document({} annotations, {} items)",
self.annotations.len(),
self.root.children.len()
)
}
}
#[cfg(test)]
mod tests {
use super::super::super::range::Position;
use super::super::paragraph::{Paragraph, TextLine};
use super::super::session::Session;
use super::*;
use crate::lex::ast::text_content::TextContent;
use crate::lex::ast::traits::AstNode;
#[test]
fn test_document_creation() {
let doc = Document::with_content(vec![
ContentItem::Paragraph(Paragraph::from_line("Para 1".to_string())),
ContentItem::Session(Session::with_title("Section 1".to_string())),
]);
assert_eq!(doc.annotations.len(), 0);
assert_eq!(doc.root.children.len(), 2);
}
#[test]
fn test_document_element_at() {
let text_line1 = TextLine::new(TextContent::from_string("First".to_string(), None))
.at(Range::new(0..0, Position::new(0, 0), Position::new(0, 5)));
let para1 = Paragraph::new(vec![ContentItem::TextLine(text_line1)]).at(Range::new(
0..0,
Position::new(0, 0),
Position::new(0, 5),
));
let text_line2 = TextLine::new(TextContent::from_string("Second".to_string(), None))
.at(Range::new(0..0, Position::new(1, 0), Position::new(1, 6)));
let para2 = Paragraph::new(vec![ContentItem::TextLine(text_line2)]).at(Range::new(
0..0,
Position::new(1, 0),
Position::new(1, 6),
));
let doc = Document::with_content(vec![
ContentItem::Paragraph(para1),
ContentItem::Paragraph(para2),
]);
let result = doc.root.element_at(Position::new(1, 3));
assert!(result.is_some(), "Expected to find element at position");
assert!(result.unwrap().is_text_line());
}
#[test]
fn test_document_traits() {
let doc = Document::with_content(vec![ContentItem::Paragraph(Paragraph::from_line(
"Line".to_string(),
))]);
assert_eq!(doc.node_type(), "Document");
assert_eq!(doc.display_label(), "Document (0 annotations, 1 items)");
assert_eq!(doc.root.children.len(), 1);
}
#[test]
fn test_root_session_accessors() {
let doc = Document::with_content(vec![ContentItem::Session(Session::with_title(
"Section".to_string(),
))]);
assert_eq!(doc.root_session().children.len(), 1);
let mut doc = doc;
doc.root_session_mut().title = TextContent::from_string("Updated".to_string(), None);
assert_eq!(doc.root_session().title.as_string(), "Updated");
let root = doc.into_root();
assert_eq!(root.title.as_string(), "Updated");
}
#[test]
fn test_document_title_field() {
let mut doc = Document::new();
assert!(doc.title.is_none());
assert_eq!(doc.title(), "");
doc.set_title("My Title".to_string());
assert!(doc.title.is_some());
assert_eq!(doc.title(), "My Title");
doc.set_title(String::new());
assert!(doc.title.is_none());
assert_eq!(doc.title(), "");
}
#[test]
fn test_from_title_and_root() {
let title = DocumentTitle::from_string("Test Title".to_string(), Range::default());
let root = Session::with_title(String::new());
let doc = Document::from_title_and_root(Some(title), root);
assert_eq!(doc.title(), "Test Title");
}
}