use super::super::range::{Position, Range};
use super::super::text_content::TextContent;
use super::super::traits::{AstNode, Container, Visitor, VisualStructure};
use super::annotation::Annotation;
use super::content_item::ContentItem;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct TextLine {
pub content: TextContent,
pub location: Range,
}
impl TextLine {
fn default_location() -> Range {
Range::new(0..0, Position::new(0, 0), Position::new(0, 0))
}
pub fn new(content: TextContent) -> Self {
Self {
content,
location: Self::default_location(),
}
}
pub fn at(mut self, location: Range) -> Self {
self.location = location;
self
}
pub fn text(&self) -> &str {
self.content.as_string()
}
}
impl AstNode for TextLine {
fn node_type(&self) -> &'static str {
"TextLine"
}
fn display_label(&self) -> String {
let text = self.text();
if text.chars().count() > 50 {
format!("{}…", text.chars().take(50).collect::<String>())
} else {
text.to_string()
}
}
fn range(&self) -> &Range {
&self.location
}
fn accept(&self, visitor: &mut dyn Visitor) {
visitor.visit_text_line(self);
visitor.leave_text_line(self);
}
}
impl VisualStructure for TextLine {
fn is_source_line_node(&self) -> bool {
true
}
}
impl fmt::Display for TextLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TextLine('{}')", self.text())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Paragraph {
pub lines: Vec<ContentItem>,
pub annotations: Vec<Annotation>,
pub location: Range,
}
impl Paragraph {
fn default_location() -> Range {
Range::new(0..0, Position::new(0, 0), Position::new(0, 0))
}
pub fn new(lines: Vec<ContentItem>) -> Self {
debug_assert!(
lines
.iter()
.all(|item| matches!(item, ContentItem::TextLine(_))),
"Paragraph lines must be TextLine items"
);
Self {
lines,
annotations: Vec::new(),
location: Self::default_location(),
}
}
pub fn from_line(line: String) -> Self {
Self {
lines: vec![ContentItem::TextLine(TextLine::new(
TextContent::from_string(line, None),
))],
annotations: Vec::new(),
location: Self::default_location(),
}
}
pub fn from_line_at(line: String, location: Range) -> Self {
let mut para = Self {
lines: vec![ContentItem::TextLine(TextLine::new(
TextContent::from_string(line, None),
))],
annotations: Vec::new(),
location: Self::default_location(),
};
para = para.at(location);
para
}
pub fn at(mut self, location: Range) -> Self {
self.location = location.clone();
if self.lines.len() == 1 {
if let Some(super::content_item::ContentItem::TextLine(text_line)) =
self.lines.get_mut(0)
{
text_line.location = location;
}
}
self
}
pub fn text(&self) -> String {
self.lines
.iter()
.filter_map(|item| {
if let super::content_item::ContentItem::TextLine(tl) = item {
Some(tl.text().to_string())
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n")
}
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())
}
}
impl AstNode for Paragraph {
fn node_type(&self) -> &'static str {
"Paragraph"
}
fn display_label(&self) -> String {
format!("{} line(s)", self.lines.len())
}
fn range(&self) -> &Range {
&self.location
}
fn accept(&self, visitor: &mut dyn Visitor) {
visitor.visit_paragraph(self);
super::super::traits::visit_children(visitor, &self.lines);
visitor.leave_paragraph(self);
}
}
impl VisualStructure for Paragraph {
fn collapses_with_children(&self) -> bool {
true
}
}
impl fmt::Display for Paragraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Paragraph({} lines)", self.lines.len())
}
}
#[cfg(test)]
mod tests {
use super::super::content_item::ContentItem;
use super::*;
#[test]
fn test_paragraph_creation() {
let para = Paragraph::new(vec![
ContentItem::TextLine(TextLine::new(TextContent::from_string(
"Hello".to_string(),
None,
))),
ContentItem::TextLine(TextLine::new(TextContent::from_string(
"World".to_string(),
None,
))),
]);
assert_eq!(para.lines.len(), 2);
assert_eq!(para.text(), "Hello\nWorld");
}
#[test]
fn test_paragraph() {
let location = Range::new(
0..0,
super::super::super::range::Position::new(0, 0),
super::super::super::range::Position::new(0, 5),
);
let para = Paragraph::from_line("Hello".to_string()).at(location.clone());
assert_eq!(para.location, location);
}
}