panache_parser/parser/inlines/
bracketed_spans.rs1use super::core::parse_inline_text;
6use super::sink::InlineSink;
7use crate::options::ParserOptions;
8use crate::parser::utils::attributes::emit_span_attributes_node;
9use crate::syntax::SyntaxKind;
10
11pub(crate) fn try_parse_bracketed_span(text: &str) -> Option<(usize, String, String)> {
19 let bytes = text.as_bytes();
20
21 if bytes.first() != Some(&b'[') {
22 return None;
23 }
24
25 let mut pos = 1;
27 let mut depth = 1;
28 let mut escaped = false;
29
30 while pos < text.len() {
31 if escaped {
32 escaped = false;
33 pos += 1;
34 continue;
35 }
36
37 match bytes[pos] {
38 b'\\' => escaped = true,
39 b'[' => depth += 1,
40 b']' => {
41 depth -= 1;
42 if depth == 0 {
43 let content = &text[1..pos];
45
46 if pos + 1 >= text.len() || bytes[pos + 1] != b'{' {
48 return None;
49 }
50
51 let attr_start = pos + 2;
53 let mut attr_pos = attr_start;
54 let mut attr_escaped = false;
55
56 while attr_pos < text.len() {
57 if attr_escaped {
58 attr_escaped = false;
59 attr_pos += 1;
60 continue;
61 }
62
63 match bytes[attr_pos] {
64 b'\\' => attr_escaped = true,
65 b'}' => {
66 let attributes = &text[attr_start..attr_pos];
68 let total_len = attr_pos + 1;
69 return Some((
70 total_len,
71 content.to_string(),
72 attributes.to_string(),
73 ));
74 }
75 _ => {}
76 }
77 attr_pos += 1;
78 }
79
80 return None;
82 }
83 }
84 _ => {}
85 }
86 pos += 1;
87 }
88
89 None
90}
91
92pub(crate) fn emit_bracketed_span(
94 builder: &mut impl InlineSink,
95 content: &str,
96 attributes: &str,
97 config: &ParserOptions,
98 suppress_footnote_refs: bool,
99) {
100 builder.start_node(SyntaxKind::BRACKETED_SPAN.into());
101
102 builder.token(SyntaxKind::SPAN_BRACKET_OPEN.into(), "[");
104
105 builder.start_node(SyntaxKind::SPAN_CONTENT.into());
107 parse_inline_text(builder, content, config, false, suppress_footnote_refs);
108 builder.finish_node(); builder.token(SyntaxKind::SPAN_BRACKET_CLOSE.into(), "]");
112
113 emit_span_attributes_node(builder, &format!("{{{attributes}}}"));
117
118 builder.finish_node(); }
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn parses_simple_span() {
127 let result = try_parse_bracketed_span("[text]{.class}");
128 assert!(result.is_some());
129 let (len, content, attrs) = result.unwrap();
130 assert_eq!(len, 14);
131 assert_eq!(content, "text");
132 assert_eq!(attrs, ".class");
133 }
134
135 #[test]
136 fn parses_span_with_multiple_attributes() {
137 let result = try_parse_bracketed_span("[text]{.class key=\"val\"}");
138 assert!(result.is_some());
139 let (len, content, attrs) = result.unwrap();
140 assert_eq!(len, 24);
141 assert_eq!(content, "text");
142 assert_eq!(attrs, ".class key=\"val\"");
143 }
144
145 #[test]
146 fn parses_span_with_emphasis() {
147 let result = try_parse_bracketed_span("[**bold** text]{.highlight}");
148 assert!(result.is_some());
149 let (len, content, attrs) = result.unwrap();
150 assert_eq!(len, 27);
151 assert_eq!(content, "**bold** text");
152 assert_eq!(attrs, ".highlight");
153 }
154
155 #[test]
156 fn handles_nested_brackets() {
157 let result = try_parse_bracketed_span("[[nested]]{.class}");
158 assert!(result.is_some());
159 let (len, content, attrs) = result.unwrap();
160 assert_eq!(len, 18);
161 assert_eq!(content, "[nested]");
162 assert_eq!(attrs, ".class");
163 }
164
165 #[test]
166 fn requires_attributes() {
167 let result = try_parse_bracketed_span("[text]");
169 assert!(result.is_none());
170 }
171
172 #[test]
173 fn requires_immediate_attributes() {
174 let result = try_parse_bracketed_span("[text] {.class}");
176 assert!(result.is_none());
177 }
178
179 #[test]
180 fn handles_escaped_brackets() {
181 let result = try_parse_bracketed_span(r"[text \] more]{.class}");
182 assert!(result.is_some());
183 let (len, content, _) = result.unwrap();
184 assert_eq!(len, 22);
185 assert_eq!(content, r"text \] more");
186 }
187
188 #[test]
189 fn handles_escaped_braces_in_attributes() {
190 let result = try_parse_bracketed_span(r"[text]{key=\}}");
191 assert!(result.is_some());
192 let (len, _, attrs) = result.unwrap();
193 assert_eq!(len, 14);
194 assert_eq!(attrs, r"key=\}");
195 }
196}