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 pub fn ensure_inline_parsed_with_anchors(&mut self) {
148 self.ensure_inline_parsed();
149 if let TextRepresentation::Inlines { nodes, .. } = &mut self.inner {
150 crate::lex::anchoring::resolve_word_anchors(nodes);
151 }
152 }
153
154 #[inline]
165 pub fn inlines(&self) -> Option<&[InlineNode]> {
166 self.inline_nodes()
167 }
168
169 #[inline]
176 pub fn parse_inlines(&mut self) {
177 self.ensure_inline_parsed();
178 }
179
180 pub fn inlines_or_parse(&mut self) -> &[InlineNode] {
187 self.ensure_inline_parsed();
188 self.inline_nodes()
189 .expect("inline_nodes should be available after ensure_inline_parsed")
190 }
191}
192
193impl Default for TextContent {
194 fn default() -> Self {
195 Self::empty()
196 }
197}
198
199impl From<String> for TextContent {
200 fn from(text: String) -> Self {
201 Self::from_string(text, None)
202 }
203}
204
205impl From<&str> for TextContent {
206 fn from(text: &str) -> Self {
207 Self::from_string(text.to_string(), None)
208 }
209}
210
211impl AsRef<str> for TextContent {
212 fn as_ref(&self) -> &str {
213 self.as_string()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_create_from_string() {
223 let content = TextContent::from_string("Hello".to_string(), None);
224 assert_eq!(content.as_string(), "Hello");
225 }
226
227 #[test]
228 fn test_empty() {
229 let content = TextContent::empty();
230 assert!(content.is_empty());
231 assert_eq!(content.as_string().len(), 0);
232 }
233
234 #[test]
235 fn test_from_string_trait() {
236 let content = TextContent::from("Hello".to_string());
237 assert_eq!(content.as_string(), "Hello");
238 }
239
240 #[test]
241 fn test_from_str_trait() {
242 let content = TextContent::from("Hello");
243 assert_eq!(content.as_string(), "Hello");
244 }
245
246 #[test]
247 fn test_as_ref() {
248 let content = TextContent::from("Hello");
249 let text: &str = content.as_ref();
250 assert_eq!(text, "Hello");
251 }
252
253 #[test]
254 fn test() {
255 let location = Range::new(0..0, Position::new(0, 0), Position::new(0, 5));
256 let content = TextContent::from_string("Hello".to_string(), Some(location.clone()));
257 assert_eq!(content.location, Some(location));
258 }
259
260 #[test]
261 fn test_mutate() {
262 let mut content = TextContent::from_string("Hello".to_string(), None);
263 *content.as_string_mut() = "World".to_string();
264 assert_eq!(content.as_string(), "World");
265 }
266
267 #[test]
268 fn parses_inline_items() {
269 use crate::lex::inlines::InlineNode;
270
271 let content = TextContent::from_string("Hello *world*".to_string(), None);
272 let nodes = content.inline_items();
273 assert_eq!(nodes.len(), 2);
274 assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
275 match &nodes[1] {
276 InlineNode::Strong { content, .. } => {
277 assert_eq!(content, &vec![InlineNode::plain("world".into())]);
278 }
279 other => panic!("Unexpected inline node: {other:?}"),
280 }
281 }
282
283 #[test]
284 fn persists_inline_nodes_after_parsing() {
285 use crate::lex::inlines::InlineNode;
286
287 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
288 assert!(content.inline_nodes().is_none());
289
290 content.ensure_inline_parsed();
291 let nodes = content.inline_nodes().expect("expected inline nodes");
292 assert_eq!(nodes.len(), 2);
293 assert_eq!(nodes[0], InlineNode::plain("Hello ".into()));
294 match &nodes[1] {
295 InlineNode::Strong { content, .. } => {
296 assert_eq!(content, &vec![InlineNode::plain("world".into())]);
297 }
298 other => panic!("Unexpected inline node: {other:?}"),
299 }
300
301 assert_eq!(content.inline_items(), nodes.to_vec());
303 assert_eq!(content.as_string(), "Hello *world*");
304 }
305
306 use super::super::range::Position;
307
308 #[test]
313 fn test_inlines_alias() {
314 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
315
316 assert!(content.inlines().is_none());
318
319 content.parse_inlines();
321 let nodes = content.inlines().expect("expected inline nodes");
322 assert_eq!(nodes.len(), 2);
323 }
324
325 #[test]
326 fn test_parse_inlines_alias() {
327 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
328
329 content.parse_inlines();
330 assert!(content.inlines().is_some());
331
332 content.parse_inlines();
334 assert!(content.inlines().is_some());
335 }
336
337 #[test]
338 fn test_inlines_or_parse() {
339 let mut content = TextContent::from_string("Hello *world*".to_string(), None);
340
341 {
343 let nodes1 = content.inlines_or_parse();
344 assert_eq!(nodes1.len(), 2);
345 }
346
347 {
349 let nodes2 = content.inlines_or_parse();
350 assert_eq!(nodes2.len(), 2);
351 }
352 }
353
354 #[test]
355 fn test_inlines_or_parse_with_references() {
356 use crate::lex::inlines::InlineNode;
357
358 let mut content =
359 TextContent::from_string("See [42] and [https://example.com]".to_string(), None);
360 let nodes = content.inlines_or_parse();
361
362 assert_eq!(nodes.len(), 4);
364 assert!(matches!(nodes[0], InlineNode::Plain { .. }));
365 assert!(matches!(nodes[1], InlineNode::Reference { .. }));
366 assert!(matches!(nodes[2], InlineNode::Plain { .. }));
367 assert!(matches!(nodes[3], InlineNode::Reference { .. }));
368 }
369}