1use crate::ast::{ListBulletKind, ListItem, ListKind, ListOrderedKindOptions, TaskState};
2use crate::parser::util::*;
3use crate::parser::MarkdownParserState;
4use nom::combinator::verify;
5use nom::{
6 branch::alt,
7 character::complete::{char, one_of, space0},
8 combinator::{map, not, opt, peek, recognize, value},
9 multi::{many0, many1, many_m_n},
10 sequence::{delimited, preceded, terminated},
11 IResult, Parser,
12};
13use std::rc::Rc;
14
15fn list_item_task_state(input: &str) -> IResult<&str, TaskState> {
16 delimited(
17 char('['),
18 alt((
19 value(TaskState::Complete, one_of("xX")),
20 value(TaskState::Incomplete, char(' ')),
21 )),
22 char(']'),
23 )
24 .parse(input)
25}
26
27fn list_marker(input: &str) -> IResult<&str, ListKind> {
28 alt((
29 list_marker_ordered,
30 list_marker_star,
31 list_marker_plus,
32 list_marker_dash,
33 ))
34 .parse(input)
35}
36
37fn list_marker_star(input: &str) -> IResult<&str, ListKind> {
38 map(char('*'), |_| ListKind::Bullet(ListBulletKind::Star)).parse(input)
39}
40
41fn list_marker_plus(input: &str) -> IResult<&str, ListKind> {
42 map(char('+'), |_| ListKind::Bullet(ListBulletKind::Plus)).parse(input)
43}
44
45fn list_marker_dash(input: &str) -> IResult<&str, ListKind> {
46 map(char('-'), |_| ListKind::Bullet(ListBulletKind::Dash)).parse(input)
47}
48
49fn list_marker_ordered(input: &str) -> IResult<&str, ListKind> {
50 map(
51 terminated(nom::character::complete::u64, one_of(".)")),
52 |start| ListKind::Ordered(ListOrderedKindOptions { start }),
53 )
54 .parse(input)
55}
56
57fn list_marker_followed_by_spaces(
58 input: &str,
59) -> IResult<&str, (ListKind, usize, Option<TaskState>)> {
60 let (remaining, kind) = delimited(
61 many_m_n(0, 3, char(' ')),
62 list_marker,
63 many_m_n(1, 4, char(' ')),
64 )
65 .parse(input)?;
66
67 let consumed = input.len() - remaining.len();
68
69 let (input, task_state) = opt(terminated(list_item_task_state, char(' '))).parse(remaining)?;
70
71 Ok((input, (kind, consumed, task_state)))
72}
73
74fn list_marker_followed_by_newline(
75 input: &str,
76) -> IResult<&str, (ListKind, usize, Option<TaskState>)> {
77 let (remaining, kind) = preceded(many_m_n(0, 3, char(' ')), list_marker).parse(input)?;
78
79 if let Ok((tail, _)) = line_terminated(space0).parse(remaining) {
83 let consumed = input.len() - remaining.len() + 1;
85
86 return Ok((tail, (kind, consumed, None)));
87 }
88
89 let (remaining, _) = many_m_n(0, 3, char(' ')).parse(remaining)?;
90 let consumed = input.len() - remaining.len() + 1;
91
92 let (remaining, task_state) = line_terminated(list_item_task_state).parse(remaining)?;
93
94 Ok((remaining, (kind, consumed, Some(task_state))))
95}
96
97pub(crate) fn list_marker_with_span_size(
98 input: &str,
99) -> IResult<&str, (ListKind, usize, Option<TaskState>, String)> {
100 alt((
101 map(
102 list_marker_followed_by_newline,
103 |(list_kind, prefix_length, task_state)| {
104 (list_kind, prefix_length, task_state, String::new())
105 },
106 ),
107 (map(
108 (
109 list_marker_followed_by_spaces,
110 line_terminated(not_eof_or_eol0),
111 ),
112 |((list_kind, prefix_length, task_state), s)| {
113 (list_kind, prefix_length, task_state, s.to_string())
114 },
115 )),
116 ))
117 .parse(input)
118}
119
120fn list_item_rest_line(
121 state: Rc<MarkdownParserState>,
122 list_kind: ListKind,
123 prefix_length: usize,
124) -> impl FnMut(&str) -> IResult<&str, Vec<&str>> {
125 move |input: &str| {
126 if input.is_empty() {
128 return Err(nom::Err::Error(nom::error::Error::new(
129 input,
130 nom::error::ErrorKind::Eof,
131 )));
132 }
133
134 let marker_parser = match list_kind {
135 ListKind::Ordered(_) => list_marker_ordered,
136 ListKind::Bullet(ListBulletKind::Star) => list_marker_star,
137 ListKind::Bullet(ListBulletKind::Plus) => list_marker_plus,
138 ListKind::Bullet(ListBulletKind::Dash) => list_marker_dash,
139 };
140
141 line_terminated(preceded(
142 peek(not(alt((
143 value(
144 (),
145 crate::parser::blocks::thematic_break::thematic_break(state.clone()),
146 ),
147 value(
148 (),
149 (
150 verify(
151 recognize(many_m_n(0, prefix_length, char(' '))),
152 |indent: &str| indent.len() < prefix_length,
153 ),
154 marker_parser,
155 ),
156 ),
157 )))),
158 alt((
159 preceded(
161 many_m_n(0, prefix_length, char(' ')),
162 map(not_eof_or_eol1, |v| vec![v]),
163 ),
164 map(
166 (
167 recognize(many1(line_terminated(space0))),
168 preceded(
169 many_m_n(prefix_length, prefix_length, char(' ')),
170 not_eof_or_eol1,
171 ),
172 ),
173 |(newlines, content)| vec![newlines, content],
174 ),
175 )),
176 ))
177 .parse(input)
178 }
179}
180
181fn list_item_lines(
182 state: Rc<MarkdownParserState>,
183 list_kind: ListKind,
184 prefix_length: usize,
185) -> impl FnMut(&str) -> IResult<&str, Vec<Vec<&str>>> {
186 move |input: &str| {
187 many0(list_item_rest_line(
188 state.clone(),
189 list_kind.clone(),
190 prefix_length,
191 ))
192 .parse(input)
193 }
194}
195
196pub(crate) fn list_item(
197 state: Rc<MarkdownParserState>,
198) -> impl FnMut(&str) -> IResult<&str, (ListKind, ListItem)> {
199 move |input: &str| {
200 let (input, (list_kind, item_prefix_length, task_state, first_line)) =
201 list_marker_with_span_size(input)?;
202
203 let (input, rest_lines) =
204 list_item_lines(state.clone(), list_kind.clone(), item_prefix_length).parse(input)?;
205
206 let total_size = first_line.len() + rest_lines.len();
207 let mut item_content = String::with_capacity(total_size);
208 if !first_line.is_empty() {
209 item_content.push_str(&first_line)
210 }
211 for line in rest_lines {
212 item_content.push('\n');
213 for subline in line {
214 item_content.push_str(subline)
215 }
216 }
217
218 let (_, blocks) = many0(crate::parser::blocks::block(state.clone()))
219 .parse(&item_content)
220 .map_err(|err| err.map_input(|_| input))?;
221
222 let blocks = blocks.into_iter().flatten().collect();
223
224 let item = ListItem {
225 task: task_state,
226 blocks,
227 };
228 Ok((input, (list_kind, item)))
229 }
230}
231
232pub(crate) fn list(
233 state: Rc<MarkdownParserState>,
234) -> impl FnMut(&str) -> IResult<&str, crate::ast::List> {
235 move |input: &str| {
236 let (input, items) = many1(list_item(state.clone())).parse(input)?;
237
238 let first_item = items.first().unwrap();
240
241 let list = crate::ast::List {
242 kind: first_item.0.clone(),
243 items: items.into_iter().map(|(_, item)| item).collect(),
244 };
245
246 Ok((input, list))
247 }
248}