panache_parser/parser/inlines/
bracketed_spans.rs1use super::core::parse_inline_text;
6use crate::options::ParserOptions;
7use crate::syntax::SyntaxKind;
8use rowan::GreenNodeBuilder;
9
10pub(crate) fn try_parse_bracketed_span(text: &str) -> Option<(usize, String, String)> {
18 let bytes = text.as_bytes();
19
20 if bytes.first() != Some(&b'[') {
21 return None;
22 }
23
24 let mut pos = 1;
26 let mut depth = 1;
27 let mut escaped = false;
28
29 while pos < text.len() {
30 if escaped {
31 escaped = false;
32 pos += 1;
33 continue;
34 }
35
36 match bytes[pos] {
37 b'\\' => escaped = true,
38 b'[' => depth += 1,
39 b']' => {
40 depth -= 1;
41 if depth == 0 {
42 let content = &text[1..pos];
44
45 if pos + 1 >= text.len() || bytes[pos + 1] != b'{' {
47 return None;
48 }
49
50 let attr_start = pos + 2;
52 let mut attr_pos = attr_start;
53 let mut attr_escaped = false;
54
55 while attr_pos < text.len() {
56 if attr_escaped {
57 attr_escaped = false;
58 attr_pos += 1;
59 continue;
60 }
61
62 match bytes[attr_pos] {
63 b'\\' => attr_escaped = true,
64 b'}' => {
65 let attributes = &text[attr_start..attr_pos];
67 let total_len = attr_pos + 1;
68 return Some((
69 total_len,
70 content.to_string(),
71 attributes.to_string(),
72 ));
73 }
74 _ => {}
75 }
76 attr_pos += 1;
77 }
78
79 return None;
81 }
82 }
83 _ => {}
84 }
85 pos += 1;
86 }
87
88 None
89}
90
91pub(crate) fn emit_bracketed_span(
93 builder: &mut GreenNodeBuilder,
94 content: &str,
95 attributes: &str,
96 config: &ParserOptions,
97) {
98 builder.start_node(SyntaxKind::BRACKETED_SPAN.into());
99
100 builder.token(SyntaxKind::SPAN_BRACKET_OPEN.into(), "[");
102
103 builder.start_node(SyntaxKind::SPAN_CONTENT.into());
105 parse_inline_text(builder, content, config, false);
106 builder.finish_node(); builder.token(SyntaxKind::SPAN_BRACKET_CLOSE.into(), "]");
110
111 builder.start_node(SyntaxKind::SPAN_ATTRIBUTES.into());
113 builder.token(SyntaxKind::TEXT.into(), "{");
114
115 let mut pos = 0;
117 let bytes = attributes.as_bytes();
118 while pos < bytes.len() {
119 if bytes[pos].is_ascii_whitespace() {
120 let start = pos;
122 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
123 pos += 1;
124 }
125 builder.token(SyntaxKind::WHITESPACE.into(), &attributes[start..pos]);
126 } else {
127 let start = pos;
129 while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
130 pos += 1;
131 }
132 builder.token(SyntaxKind::TEXT.into(), &attributes[start..pos]);
133 }
134 }
135
136 builder.token(SyntaxKind::TEXT.into(), "}");
137 builder.finish_node(); builder.finish_node(); }
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn parses_simple_span() {
148 let result = try_parse_bracketed_span("[text]{.class}");
149 assert!(result.is_some());
150 let (len, content, attrs) = result.unwrap();
151 assert_eq!(len, 14);
152 assert_eq!(content, "text");
153 assert_eq!(attrs, ".class");
154 }
155
156 #[test]
157 fn parses_span_with_multiple_attributes() {
158 let result = try_parse_bracketed_span("[text]{.class key=\"val\"}");
159 assert!(result.is_some());
160 let (len, content, attrs) = result.unwrap();
161 assert_eq!(len, 24);
162 assert_eq!(content, "text");
163 assert_eq!(attrs, ".class key=\"val\"");
164 }
165
166 #[test]
167 fn parses_span_with_emphasis() {
168 let result = try_parse_bracketed_span("[**bold** text]{.highlight}");
169 assert!(result.is_some());
170 let (len, content, attrs) = result.unwrap();
171 assert_eq!(len, 27);
172 assert_eq!(content, "**bold** text");
173 assert_eq!(attrs, ".highlight");
174 }
175
176 #[test]
177 fn handles_nested_brackets() {
178 let result = try_parse_bracketed_span("[[nested]]{.class}");
179 assert!(result.is_some());
180 let (len, content, attrs) = result.unwrap();
181 assert_eq!(len, 18);
182 assert_eq!(content, "[nested]");
183 assert_eq!(attrs, ".class");
184 }
185
186 #[test]
187 fn requires_attributes() {
188 let result = try_parse_bracketed_span("[text]");
190 assert!(result.is_none());
191 }
192
193 #[test]
194 fn requires_immediate_attributes() {
195 let result = try_parse_bracketed_span("[text] {.class}");
197 assert!(result.is_none());
198 }
199
200 #[test]
201 fn handles_escaped_brackets() {
202 let result = try_parse_bracketed_span(r"[text \] more]{.class}");
203 assert!(result.is_some());
204 let (len, content, _) = result.unwrap();
205 assert_eq!(len, 22);
206 assert_eq!(content, r"text \] more");
207 }
208
209 #[test]
210 fn handles_escaped_braces_in_attributes() {
211 let result = try_parse_bracketed_span(r"[text]{key=\}}");
212 assert!(result.is_some());
213 let (len, _, attrs) = result.unwrap();
214 assert_eq!(len, 14);
215 assert_eq!(attrs, r"key=\}");
216 }
217}