panache_parser/parser/blocks/
definition_lists.rs1use crate::options::ParserOptions;
2use crate::syntax::SyntaxKind;
3use rowan::GreenNodeBuilder;
4
5use crate::parser::utils::container_stack::leading_indent;
6use crate::parser::utils::helpers::strip_newline;
7use crate::parser::utils::inline_emission;
8
9pub(crate) fn try_parse_definition_marker(line: &str) -> Option<(char, usize, usize, usize)> {
14 let (indent_cols, indent_bytes) = leading_indent(line);
16 if indent_cols > 3 {
17 return None;
18 }
19
20 let after_indent = &line[indent_bytes..];
21
22 let marker = after_indent.chars().next()?;
24 if !matches!(marker, ':' | '~') {
25 return None;
26 }
27
28 let after_marker = &after_indent[1..];
29
30 if !after_marker.starts_with(' ') && !after_marker.starts_with('\t') && !after_marker.is_empty()
32 {
33 return None;
34 }
35
36 let (spaces_after_cols, spaces_after_bytes) = leading_indent(after_marker);
37
38 Some((marker, indent_cols, spaces_after_cols, spaces_after_bytes))
39}
40
41pub(crate) fn emit_term(
43 builder: &mut GreenNodeBuilder<'static>,
44 line: &str,
45 config: &ParserOptions,
46) {
47 builder.start_node(SyntaxKind::TERM.into());
48 let (text, newline_str) = strip_newline(line);
50 let trimmed_text = text.trim_end();
51
52 if !trimmed_text.is_empty() {
53 inline_emission::emit_inlines(builder, trimmed_text, config);
54 }
55
56 if !newline_str.is_empty() {
57 builder.token(SyntaxKind::NEWLINE.into(), newline_str);
58 }
59 builder.finish_node(); }
61
62pub(crate) fn emit_definition_marker(
64 builder: &mut GreenNodeBuilder<'static>,
65 marker: char,
66 indent_cols: usize,
67) {
68 if indent_cols > 0 {
69 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent_cols));
70 }
71 builder.token(SyntaxKind::DEFINITION_MARKER.into(), &marker.to_string());
72}
73
74use crate::parser::blocks::tables::is_caption_followed_by_table;
77use crate::parser::utils::container_stack::{Container, ContainerStack};
78
79pub(in crate::parser) fn in_definition_list(containers: &ContainerStack) -> bool {
81 containers
82 .stack
83 .iter()
84 .any(|c| matches!(c, Container::DefinitionList { .. }))
85}
86
87pub(in crate::parser) fn next_line_is_definition_marker(
90 lines: &[&str],
91 pos: usize,
92) -> Option<usize> {
93 let mut check_pos = pos + 1;
94 let mut blank_count = 0;
95 while check_pos < lines.len() {
96 let line = lines[check_pos];
97 if line.trim().is_empty() {
98 blank_count += 1;
99 check_pos += 1;
100 continue;
101 }
102 if try_parse_definition_marker(line).is_some() {
103 if let Some((marker, ..)) = try_parse_definition_marker(line)
104 && marker == ':'
105 && is_caption_followed_by_table(lines, check_pos)
106 {
107 return None;
108 }
109 return Some(blank_count);
110 } else {
111 return None;
112 }
113 }
114 None
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::parser::blocks::tables::is_caption_followed_by_table;
121
122 #[test]
123 fn test_parse_definition_marker_colon() {
124 assert_eq!(
125 try_parse_definition_marker(": Definition"),
126 Some((':', 0, 3, 3))
127 );
128 }
129
130 #[test]
131 fn test_parse_definition_marker_tilde() {
132 assert_eq!(
133 try_parse_definition_marker("~ Definition"),
134 Some(('~', 0, 3, 3))
135 );
136 }
137
138 #[test]
139 fn test_parse_definition_marker_indented() {
140 assert_eq!(
141 try_parse_definition_marker(" : Definition"),
142 Some((':', 2, 1, 1))
143 );
144 assert_eq!(
145 try_parse_definition_marker(" ~ Definition"),
146 Some(('~', 3, 1, 1))
147 );
148 assert_eq!(try_parse_definition_marker("\t: Definition"), None);
149 }
150
151 #[test]
152 fn test_parse_definition_marker_too_indented() {
153 assert_eq!(try_parse_definition_marker(" : Definition"), None);
154 }
155
156 #[test]
157 fn test_parse_definition_marker_no_space_after() {
158 assert_eq!(try_parse_definition_marker(":Definition"), None);
159 }
160
161 #[test]
162 fn test_parse_definition_marker_at_eol() {
163 assert_eq!(try_parse_definition_marker(":"), Some((':', 0, 0, 0)));
164 }
165
166 #[test]
167 fn next_line_marker_ignores_colon_table_caption() {
168 let lines = vec![
169 "Here's a table with a reference:",
170 "",
171 ": (\\#tab:mytable) A table with a reference.",
172 "",
173 "| A | B | C |",
174 "| --- | --- | --- |",
175 "| 1 | 2 | 3 |",
176 ];
177 assert!(is_caption_followed_by_table(&lines, 2));
178 assert_eq!(next_line_is_definition_marker(&lines, 0), None);
179 }
180
181 #[test]
182 fn test_definition_list_preserves_first_content_line_losslessly() {
183 let input = "[`--reference-doc=`*FILE*]{#option--reference-doc}\n\n: Use the specified file as a style reference in producing a\n docx or ODT file.\n\n Docx\n\n : For best results, the reference docx should be a modified\n version of a docx file produced using pandoc.\n";
184 let tree = crate::parse(input, Some(crate::ParserOptions::default()));
185 assert_eq!(tree.text().to_string(), input);
186 }
187}