panache_parser/parser/inlines/
code_spans.rs1use crate::syntax::SyntaxKind;
3use rowan::GreenNodeBuilder;
4
5use crate::parser::utils::attributes::{
7 AttributeBlock, emit_attributes, try_parse_trailing_attributes,
8};
9
10pub fn try_parse_code_span(text: &str) -> Option<(usize, &str, usize, Option<AttributeBlock>)> {
13 let opening_backticks = text.bytes().take_while(|&b| b == b'`').count();
15 if opening_backticks == 0 {
16 return None;
17 }
18
19 let rest = &text[opening_backticks..];
20
21 let mut pos = 0;
23 while pos < rest.len() {
24 if rest[pos..].starts_with('`') {
25 let closing_backticks = rest[pos..].bytes().take_while(|&b| b == b'`').count();
26
27 if closing_backticks == opening_backticks {
28 let code_content = &rest[..pos];
30 let after_close = opening_backticks + pos + closing_backticks;
31
32 let remaining = &text[after_close..];
34 if remaining.starts_with('{') {
35 if let Some(close_brace_pos) = remaining.find('}') {
37 let attr_text = &remaining[..=close_brace_pos];
38 if let Some((attrs, _)) = try_parse_trailing_attributes(attr_text) {
40 let total_len = after_close + close_brace_pos + 1;
41 return Some((total_len, code_content, opening_backticks, Some(attrs)));
42 }
43 }
44 }
45
46 return Some((after_close, code_content, opening_backticks, None));
48 }
49 pos += closing_backticks;
51 } else {
52 pos += rest[pos..].chars().next()?.len_utf8();
54 }
55 }
56
57 None
59}
60
61pub fn emit_code_span(
63 builder: &mut GreenNodeBuilder,
64 content: &str,
65 backtick_count: usize,
66 attributes: Option<AttributeBlock>,
67) {
68 builder.start_node(SyntaxKind::INLINE_CODE.into());
69
70 builder.token(
72 SyntaxKind::INLINE_CODE_MARKER.into(),
73 &"`".repeat(backtick_count),
74 );
75
76 builder.token(SyntaxKind::INLINE_CODE_CONTENT.into(), content);
78
79 builder.token(
81 SyntaxKind::INLINE_CODE_MARKER.into(),
82 &"`".repeat(backtick_count),
83 );
84
85 if let Some(attrs) = attributes {
87 emit_attributes(builder, &attrs);
88 }
89
90 builder.finish_node();
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_parse_simple_code_span() {
99 let result = try_parse_code_span("`code`");
100 assert_eq!(result, Some((6, "code", 1, None)));
101 }
102
103 #[test]
104 fn test_parse_code_span_with_backticks() {
105 let result = try_parse_code_span("`` `backtick` ``");
106 assert_eq!(result, Some((16, " `backtick` ", 2, None)));
107 }
108
109 #[test]
110 fn test_parse_code_span_triple_backticks() {
111 let result = try_parse_code_span("``` `` ```");
112 assert_eq!(result, Some((10, " `` ", 3, None)));
113 }
114
115 #[test]
116 fn test_parse_code_span_no_close() {
117 let result = try_parse_code_span("`no close");
118 assert_eq!(result, None);
119 }
120
121 #[test]
122 fn test_parse_code_span_mismatched_close() {
123 let result = try_parse_code_span("`single``");
124 assert_eq!(result, None);
125 }
126
127 #[test]
128 fn test_not_code_span() {
129 let result = try_parse_code_span("no backticks");
130 assert_eq!(result, None);
131 }
132
133 #[test]
134 fn test_code_span_with_trailing_text() {
135 let result = try_parse_code_span("`code` and more");
136 assert_eq!(result, Some((6, "code", 1, None)));
137 }
138
139 #[test]
140 fn test_code_span_with_simple_class() {
141 let result = try_parse_code_span("`code`{.python}");
142 let (len, content, backticks, attrs) = result.unwrap();
143 assert_eq!(len, 15);
144 assert_eq!(content, "code");
145 assert_eq!(backticks, 1);
146 assert!(attrs.is_some());
147 let attrs = attrs.unwrap();
148 assert_eq!(attrs.classes, vec!["python"]);
149 }
150
151 #[test]
152 fn test_code_span_with_id() {
153 let result = try_parse_code_span("`code`{#mycode}");
154 let (len, content, backticks, attrs) = result.unwrap();
155 assert_eq!(len, 15);
156 assert_eq!(content, "code");
157 assert_eq!(backticks, 1);
158 assert!(attrs.is_some());
159 let attrs = attrs.unwrap();
160 assert_eq!(attrs.identifier, Some("mycode".to_string()));
161 }
162
163 #[test]
164 fn test_code_span_with_full_attributes() {
165 let result = try_parse_code_span("`x + y`{#calc .haskell .eval}");
166 let (len, content, backticks, attrs) = result.unwrap();
167 assert_eq!(len, 29);
168 assert_eq!(content, "x + y");
169 assert_eq!(backticks, 1);
170 assert!(attrs.is_some());
171 let attrs = attrs.unwrap();
172 assert_eq!(attrs.identifier, Some("calc".to_string()));
173 assert_eq!(attrs.classes, vec!["haskell", "eval"]);
174 }
175
176 #[test]
177 fn test_code_span_attributes_must_be_adjacent() {
178 let result = try_parse_code_span("`code` {.python}");
180 assert_eq!(result, Some((6, "code", 1, None)));
181 }
182}