lex_core/lex/ast/
text_content.rs1use super::range::Range;
16use crate::lex::inlines::{InlineContent, InlineNode};
17
18#[derive(Debug, Clone, PartialEq)]
24pub struct TextContent {
25 pub location: Option<Range>,
27 inner: TextRepresentation,
29}
30
31#[derive(Debug, Clone, PartialEq)]
36enum TextRepresentation {
37 Text(String),
41 Inlines { raw: String, nodes: InlineContent },
43}
44
45impl TextContent {
46 pub fn from_string(text: String, location: Option<Range>) -> Self {
54 Self {
55 location,
56 inner: TextRepresentation::Text(text),
57 }
58 }
59
60 pub fn empty() -> Self {
62 Self {
63 location: None,
64 inner: TextRepresentation::Text(String::new()),
65 }
66 }
67
68 pub fn as_string(&self) -> &str {
75 match &self.inner {
76 TextRepresentation::Text(s) => s,
77 TextRepresentation::Inlines { raw, .. } => raw,
78 }
79 }
80
81 pub fn as_string_mut(&mut self) -> &mut String {
89 match &mut self.inner {
90 TextRepresentation::Text(s) => s,
91 TextRepresentation::Inlines { .. } => {
92 panic!(
93 "TextContent::as_string_mut cannot be used after inline parsing has occurred"
94 )
95 }
96 }
97 }
98
99 pub fn is_empty(&self) -> bool {
101 self.as_string().is_empty()
102 }
103
104 pub fn len(&self) -> usize {
106 self.as_string().len()
107 }
108
109 pub fn inline_items(&self) -> InlineContent {
111 match &self.inner {
112 TextRepresentation::Text(s) => crate::lex::inlines::parse_inlines(s),
113 TextRepresentation::Inlines { nodes, .. } => nodes.clone(),
114 }
115 }
116
117 pub fn inline_nodes(&self) -> Option<&[InlineNode]> {
119 match &self.inner {
120 TextRepresentation::Inlines { nodes, .. } => Some(nodes),
121 _ => None,
122 }
123 }
124
125 pub fn ensure_inline_parsed(&mut self) {
127 if matches!(self.inner, TextRepresentation::Inlines { .. }) {
128 return;
129 }
130
131 let raw = match std::mem::replace(&mut self.inner, TextRepresentation::Text(String::new()))
132 {
133 TextRepresentation::Text(raw) => raw,
134 TextRepresentation::Inlines { raw, nodes } => {
135 self.inner = TextRepresentation::Inlines { raw, nodes };
136 return;
137 }
138 };
139 let nodes = crate::lex::inlines::parse_inlines(&raw);
140 self.inner = TextRepresentation::Inlines { raw, nodes };
141 }
142
143 #[inline]
154 pub fn inlines(&self) -> Option<&[InlineNode]> {
155 self.inline_nodes()
156 }
157
158 #[inline]
165 pub fn parse_inlines(&mut self) {
166 self.ensure_inline_parsed();
167 }
168
169 pub fn inlines_or_parse(&mut self) -> &[InlineNode] {
176 self.ensure_inline_parsed();
177 self.inline_nodes()
178 .expect("inline_nodes should be available after ensure_inline_parsed")
179 }
180}
181
182impl Default for TextContent {
183 fn default() -> Self {
184 Self::empty()
185 }
186}
187
188impl From<String> for TextContent {
189 fn from(text: String) -> Self {
190 Self::from_string(text, None)
191 }
192}
193
194impl From<&str> for TextContent {
195 fn from(text: &str) -> Self {
196 Self::from_string(text.to_string(), None)
197 }
198}
199
200impl AsRef<str> for TextContent {
201 fn as_ref(&self) -> &str {
202 self.as_string()
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_create_from_string() {
212 let content = TextContent::from_string("Hello".to_string(), None);
213 assert_eq!(content.as_string(), "Hello");
214 }
215
216 #[test]
217 fn test_empty() {
218 let content = TextContent::empty();
219 assert!(content.is_empty());
220 assert_eq!(content.as_string().len(), 0);
221 }
222
223 #[test]
224 fn test_from_string_trait() {
225 let content = TextContent::from("Hello".to_string());
226 assert_eq!(content.as_string(), "Hello");
227 }
228
229 #[test]
230 fn test_from_str_trait() {
231 let content = TextContent::from("Hello");
232 assert_eq!(content.as_string(), "Hello");
233 }
234
235 #[test]
236 fn test_as_ref() {
237 let content = TextContent::from("Hello");
238 let text: &str = content.as_ref();
239 assert_eq!(text, "Hello");
240 }
241
242 #[test]
243 fn test() {
244 let location = Range::new(0..0, Position::new(0, 0), Position::new(0, 5));
245 let content = TextContent::from_string("Hello".to_string(), Some(location.clone()));
246 assert_eq!(content.location, Some(location));
247 }
248
249 #[test]
250 fn test_mutate() {
251 let mut content = TextContent::from_string("Hello".to_string(), None);
252 *content.as_string_mut() = "World".to_string();
253 assert_eq!(content.as_string(), "World");
254 }
255
256 #[test]
257 fn parses_inline_items() {
258 use crate::lex::inlines::InlineNode;
259
260 let content = TextContent::from_string("Hello *world*".to_string(), None);
261 let nodes = content.inline_items();
262 assert_eq!(nodes.len(), 2);
263 assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
264 match &nodes[1] {
265 InlineNode::Strong { content, .. } => {
266 assert_eq!(content, &vec![InlineNode::plain("world".into())]);
267 }
268 other => panic!("Unexpected inline node: {other:?}"),
269 }
270 }
271
272 #[test]
273 fn persists_inline_nodes_after_parsing() {
274 use crate::lex::inlines::InlineNode;
275
276 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
277 assert!(content.inline_nodes().is_none());
278
279 content.ensure_inline_parsed();
280 let nodes = content.inline_nodes().expect("expected inline nodes");
281 assert_eq!(nodes.len(), 2);
282 assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
283 match &nodes[1] {
284 InlineNode::Strong { content, .. } => {
285 assert_eq!(content, &vec![InlineNode::plain("world".into())]);
286 }
287 other => panic!("Unexpected inline node: {other:?}"),
288 }
289
290 assert_eq!(content.inline_items(), nodes.to_vec());
292 assert_eq!(content.as_string(), "Hello *world*");
293 }
294
295 use super::super::range::Position;
296
297 #[test]
302 fn test_inlines_alias() {
303 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
304
305 assert!(content.inlines().is_none());
307
308 content.parse_inlines();
310 let nodes = content.inlines().expect("expected inline nodes");
311 assert_eq!(nodes.len(), 2);
312 }
313
314 #[test]
315 fn test_parse_inlines_alias() {
316 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
317
318 content.parse_inlines();
319 assert!(content.inlines().is_some());
320
321 content.parse_inlines();
323 assert!(content.inlines().is_some());
324 }
325
326 #[test]
327 fn test_inlines_or_parse() {
328 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
329
330 {
332 let nodes1 = content.inlines_or_parse();
333 assert_eq!(nodes1.len(), 2);
334 }
335
336 {
338 let nodes2 = content.inlines_or_parse();
339 assert_eq!(nodes2.len(), 2);
340 }
341 }
342
343 #[test]
344 fn test_inlines_or_parse_with_references() {
345 use crate::lex::inlines::InlineNode;
346
347 let mut content =
348 TextContent::from_string("See [42] and [https://example.com]".to_string(), None);
349 let nodes = content.inlines_or_parse();
350
351 assert_eq!(nodes.len(), 4);
353 assert!(matches!(nodes[0], InlineNode::Plain { .. }));
354 assert!(matches!(nodes[1], InlineNode::Reference { .. }));
355 assert!(matches!(nodes[2], InlineNode::Plain { .. }));
356 assert!(matches!(nodes[3], InlineNode::Reference { .. }));
357 }
358}