Skip to main content

mos_parse/
list.rs

1use crate::parser::Parser;
2use crate::support::list_marker_at;
3use crate::{Inline, Item, ListItem, ListItemBlock};
4use mos_core::SourceSpan;
5
6enum RawListItemBlock {
7    Paragraph {
8        start: usize,
9        end: usize,
10    },
11    List {
12        ordered: bool,
13        items: Vec<ListItem>,
14        span: SourceSpan,
15    },
16}
17
18impl Parser<'_> {
19    /// Consume one list block starting at the current marker line and push it
20    /// onto `self.items`.
21    pub(crate) fn parse_list(&mut self) {
22        let Some((indent, ordered, _)) = list_marker_at(self.src.as_bytes(), self.pos) else {
23            return;
24        };
25        let item = self.parse_list_at(indent, ordered);
26        self.items.push(item);
27    }
28
29    fn parse_list_at(&mut self, base_indent: usize, base_ordered: bool) -> Item {
30        let list_start = self.pos;
31        let mut items: Vec<ListItem> = Vec::new();
32        let mut list_end = list_start;
33
34        while self.pos < self.src.len() {
35            if self.at_blank_line() {
36                break;
37            }
38            // A whole-line `//` or `/* … */` comment between items keeps the
39            // list intact (so ordered numbering does not restart) and renders
40            // nothing.
41            if self.at_line_comment() {
42                self.skip_line();
43                continue;
44            }
45            if self.at_block_comment() {
46                self.consume_block_comment();
47                if self.at_blank_line() {
48                    self.skip_line();
49                }
50                continue;
51            }
52            let Some((indent, ordered, _)) = list_marker_at(self.src.as_bytes(), self.pos) else {
53                break;
54            };
55            if indent != base_indent || ordered != base_ordered {
56                break;
57            }
58
59            let item = self.parse_list_item(base_indent);
60            list_end = list_end.max(item.span.end());
61            items.push(item);
62        }
63
64        Item::List {
65            ordered: base_ordered,
66            items,
67            span: self.span(list_start, list_end),
68        }
69    }
70
71    fn parse_list_item(&mut self, base_indent: usize) -> ListItem {
72        let Some((_, _, content_start)) = list_marker_at(self.src.as_bytes(), self.pos) else {
73            let span = self.span(self.pos, self.pos);
74            return ListItem {
75                inlines: Vec::new(),
76                blocks: Vec::new(),
77                span,
78            };
79        };
80
81        let (line_start, content_end, line_end) = self.current_line_bounds();
82        let text_column = content_start.saturating_sub(line_start);
83        let item_start = line_start;
84        let mut item_end = content_end;
85        let mut raw_blocks = vec![RawListItemBlock::Paragraph {
86            start: content_start,
87            end: content_end,
88        }];
89        self.pos = line_end;
90
91        while self.pos < self.src.len() {
92            if self.at_blank_line() {
93                break;
94            }
95
96            if let Some((indent, ordered, _)) = list_marker_at(self.src.as_bytes(), self.pos) {
97                if indent > base_indent {
98                    let nested = self.parse_list_at(indent, ordered);
99                    if let Item::List {
100                        ordered,
101                        items,
102                        span,
103                    } = nested
104                    {
105                        item_end = item_end.max(span.end());
106                        raw_blocks.push(RawListItemBlock::List {
107                            ordered,
108                            items,
109                            span,
110                        });
111                    }
112                    continue;
113                }
114                break;
115            }
116
117            let (line_start, content_end, line_end) = self.current_line_bounds();
118            let leading_spaces = self.leading_spaces_on_line(line_start, content_end);
119            if leading_spaces < text_column {
120                break;
121            }
122
123            let continuation_start = line_start + text_column;
124            match raw_blocks.last_mut() {
125                Some(RawListItemBlock::Paragraph { end, .. }) => {
126                    *end = content_end;
127                }
128                Some(RawListItemBlock::List { .. }) | None => {
129                    raw_blocks.push(RawListItemBlock::Paragraph {
130                        start: continuation_start,
131                        end: content_end,
132                    });
133                }
134            }
135            item_end = item_end.max(content_end);
136            self.pos = line_end;
137        }
138
139        self.finish_list_item(raw_blocks, item_start, item_end)
140    }
141
142    fn finish_list_item(
143        &mut self,
144        raw_blocks: Vec<RawListItemBlock>,
145        item_start: usize,
146        item_end: usize,
147    ) -> ListItem {
148        let mut inlines: Vec<Inline> = Vec::new();
149        let mut blocks: Vec<ListItemBlock> = Vec::new();
150        let mut saw_first_paragraph = false;
151
152        for raw in raw_blocks {
153            match raw {
154                RawListItemBlock::Paragraph { start, end } => {
155                    let paragraph = self.parse_list_paragraph(start, end);
156                    if !saw_first_paragraph {
157                        inlines.clone_from(&paragraph);
158                        saw_first_paragraph = true;
159                    }
160                    blocks.push(ListItemBlock::Paragraph {
161                        inlines: paragraph,
162                        span: self.span(start, end),
163                    });
164                }
165                RawListItemBlock::List {
166                    ordered,
167                    items,
168                    span,
169                } => {
170                    blocks.push(ListItemBlock::List {
171                        ordered,
172                        items,
173                        span,
174                    });
175                }
176            }
177        }
178
179        ListItem {
180            inlines,
181            blocks,
182            span: self.span(item_start, item_end),
183        }
184    }
185
186    fn parse_list_paragraph(&mut self, start: usize, end: usize) -> Vec<Inline> {
187        let slice = &self.src[start..end];
188        let mut inlines = self.parse_inlines(slice, start);
189        for inline in &mut inlines {
190            if inline.text.contains("\r\n") {
191                inline.text = inline.text.replace("\r\n", "\n");
192            }
193        }
194        inlines
195    }
196
197    fn leading_spaces_on_line(&self, line_start: usize, content_end: usize) -> usize {
198        let bytes = self.src.as_bytes();
199        let mut i = line_start;
200        while i < content_end && bytes[i] == b' ' {
201            i += 1;
202        }
203        i - line_start
204    }
205}