use super::range::Range;
use crate::lex::inlines::{InlineContent, InlineNode};
#[derive(Debug, Clone, PartialEq)]
pub struct TextContent {
pub location: Option<Range>,
inner: TextRepresentation,
}
#[derive(Debug, Clone, PartialEq)]
enum TextRepresentation {
Text(String),
Inlines { raw: String, nodes: InlineContent },
}
impl TextContent {
pub fn from_string(text: String, location: Option<Range>) -> Self {
Self {
location,
inner: TextRepresentation::Text(text),
}
}
pub fn empty() -> Self {
Self {
location: None,
inner: TextRepresentation::Text(String::new()),
}
}
pub fn as_string(&self) -> &str {
match &self.inner {
TextRepresentation::Text(s) => s,
TextRepresentation::Inlines { raw, .. } => raw,
}
}
pub fn as_string_mut(&mut self) -> &mut String {
match &mut self.inner {
TextRepresentation::Text(s) => s,
TextRepresentation::Inlines { .. } => {
panic!(
"TextContent::as_string_mut cannot be used after inline parsing has occurred"
)
}
}
}
pub fn is_empty(&self) -> bool {
self.as_string().is_empty()
}
pub fn len(&self) -> usize {
self.as_string().len()
}
pub fn inline_items(&self) -> InlineContent {
match &self.inner {
TextRepresentation::Text(s) => crate::lex::inlines::parse_inlines(s),
TextRepresentation::Inlines { nodes, .. } => nodes.clone(),
}
}
pub fn inline_nodes(&self) -> Option<&[InlineNode]> {
match &self.inner {
TextRepresentation::Inlines { nodes, .. } => Some(nodes),
_ => None,
}
}
pub fn ensure_inline_parsed(&mut self) {
if matches!(self.inner, TextRepresentation::Inlines { .. }) {
return;
}
let raw = match std::mem::replace(&mut self.inner, TextRepresentation::Text(String::new()))
{
TextRepresentation::Text(raw) => raw,
TextRepresentation::Inlines { raw, nodes } => {
self.inner = TextRepresentation::Inlines { raw, nodes };
return;
}
};
let nodes = crate::lex::inlines::parse_inlines(&raw);
self.inner = TextRepresentation::Inlines { raw, nodes };
}
#[inline]
pub fn inlines(&self) -> Option<&[InlineNode]> {
self.inline_nodes()
}
#[inline]
pub fn parse_inlines(&mut self) {
self.ensure_inline_parsed();
}
pub fn inlines_or_parse(&mut self) -> &[InlineNode] {
self.ensure_inline_parsed();
self.inline_nodes()
.expect("inline_nodes should be available after ensure_inline_parsed")
}
}
impl Default for TextContent {
fn default() -> Self {
Self::empty()
}
}
impl From<String> for TextContent {
fn from(text: String) -> Self {
Self::from_string(text, None)
}
}
impl From<&str> for TextContent {
fn from(text: &str) -> Self {
Self::from_string(text.to_string(), None)
}
}
impl AsRef<str> for TextContent {
fn as_ref(&self) -> &str {
self.as_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_from_string() {
let content = TextContent::from_string("Hello".to_string(), None);
assert_eq!(content.as_string(), "Hello");
}
#[test]
fn test_empty() {
let content = TextContent::empty();
assert!(content.is_empty());
assert_eq!(content.as_string().len(), 0);
}
#[test]
fn test_from_string_trait() {
let content = TextContent::from("Hello".to_string());
assert_eq!(content.as_string(), "Hello");
}
#[test]
fn test_from_str_trait() {
let content = TextContent::from("Hello");
assert_eq!(content.as_string(), "Hello");
}
#[test]
fn test_as_ref() {
let content = TextContent::from("Hello");
let text: &str = content.as_ref();
assert_eq!(text, "Hello");
}
#[test]
fn test() {
let location = Range::new(0..0, Position::new(0, 0), Position::new(0, 5));
let content = TextContent::from_string("Hello".to_string(), Some(location.clone()));
assert_eq!(content.location, Some(location));
}
#[test]
fn test_mutate() {
let mut content = TextContent::from_string("Hello".to_string(), None);
*content.as_string_mut() = "World".to_string();
assert_eq!(content.as_string(), "World");
}
#[test]
fn parses_inline_items() {
use crate::lex::inlines::InlineNode;
let content = TextContent::from_string("Hello *world*".to_string(), None);
let nodes = content.inline_items();
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
match &nodes[1] {
InlineNode::Strong { content, .. } => {
assert_eq!(content, &vec![InlineNode::plain("world".into())]);
}
other => panic!("Unexpected inline node: {other:?}"),
}
}
#[test]
fn persists_inline_nodes_after_parsing() {
use crate::lex::inlines::InlineNode;
let mut content = TextContent::from_string("Hello *world*".to_string(), None);
assert!(content.inline_nodes().is_none());
content.ensure_inline_parsed();
let nodes = content.inline_nodes().expect("expected inline nodes");
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
match &nodes[1] {
InlineNode::Strong { content, .. } => {
assert_eq!(content, &vec![InlineNode::plain("world".into())]);
}
other => panic!("Unexpected inline node: {other:?}"),
}
assert_eq!(content.inline_items(), nodes.to_vec());
assert_eq!(content.as_string(), "Hello *world*");
}
use super::super::range::Position;
#[test]
fn test_inlines_alias() {
let mut content = TextContent::from_string("Hello *world*".to_string(), None);
assert!(content.inlines().is_none());
content.parse_inlines();
let nodes = content.inlines().expect("expected inline nodes");
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_parse_inlines_alias() {
let mut content = TextContent::from_string("Hello *world*".to_string(), None);
content.parse_inlines();
assert!(content.inlines().is_some());
content.parse_inlines();
assert!(content.inlines().is_some());
}
#[test]
fn test_inlines_or_parse() {
let mut content = TextContent::from_string("Hello *world*".to_string(), None);
{
let nodes1 = content.inlines_or_parse();
assert_eq!(nodes1.len(), 2);
}
{
let nodes2 = content.inlines_or_parse();
assert_eq!(nodes2.len(), 2);
}
}
#[test]
fn test_inlines_or_parse_with_references() {
use crate::lex::inlines::InlineNode;
let mut content =
TextContent::from_string("See [42] and [https://example.com]".to_string(), None);
let nodes = content.inlines_or_parse();
assert_eq!(nodes.len(), 4);
assert!(matches!(nodes[0], InlineNode::Plain { .. }));
assert!(matches!(nodes[1], InlineNode::Reference { .. }));
assert!(matches!(nodes[2], InlineNode::Plain { .. }));
assert!(matches!(nodes[3], InlineNode::Reference { .. }));
}
}