wayver's git archive


an obsidian renderer
git clone https://git.wayver.dev/sable

sable-markdown/src/parser/blocks/paragraph.rs@337ba67f65eaa17b44e371af7c0f0c761d6aa914

raw
Date Commit Message Author Files + -
2026-02-23 01:55 initial mvp wayverd 139 17808 0
...

1use nom::{
2    IResult, Parser,
3    branch::alt,
4    character::complete::{char, line_ending, space0},
5    combinator::{not, peek, value},
6    multi::{many_m_n, separated_list0},
7    sequence::preceded,
8};
9
10use crate::{
11    ast::Inline,
12    parser::util::{line_terminated, not_eof_or_eol1},
13};
14
15pub(super) fn paragraph<'a>(
16    check_first_line: bool,
17) -> impl FnMut(&'a str) -> IResult<&'a str, Vec<Inline>> {
18    move |input: &'a str| {
19        let mut lines = Vec::new();
20        let input = if check_first_line {
21            input
22        } else {
23            // Skip checks for the first line, just make it a paragraph
24            let (input, first_line) =
25                preceded(many_m_n(0, 3, char(' ')), not_eof_or_eol1).parse(input)?;
26            lines.push(first_line);
27            input
28        };
29
30        let paragraph_parser = separated_list0(
31            line_ending,
32            preceded(
33                is_paragraph_line_start(),
34                preceded(many_m_n(0, 3, char(' ')), not_eof_or_eol1),
35            ),
36        );
37        let (input, rest_lines) = line_terminated(paragraph_parser).parse(input)?;
38        lines.extend(rest_lines);
39
40        let content = lines.join("\n");
41
42        let (_, content) = crate::parser::inline::inline_many1()
43            .parse(content.as_str())
44            .map_err(|err| err.map_input(|_| input))?;
45
46        Ok((input, content))
47    }
48}
49
50pub(super) fn is_paragraph_line_start<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
51    move |input: &'a str| {
52        peek(not(alt((
53            value((), crate::parser::blocks::heading::heading_v1()),
54            value((), crate::parser::blocks::heading::heading_v2_level()),
55            crate::parser::blocks::thematic_break::thematic_break(),
56            value((), crate::parser::blocks::blockquote::blockquote()),
57            value((), crate::parser::blocks::list::list_item()),
58            value((), crate::parser::blocks::code_block::code_block_fenced()),
59            value((), crate::parser::blocks::html_block::html_block()),
60            value(
61                (),
62                crate::parser::blocks::link_definition::link_definition(),
63            ),
64            value(
65                (),
66                crate::parser::blocks::footnote_definition::footnote_definition(),
67            ),
68            value((), crate::parser::blocks::table::table()),
69            value((), line_terminated(space0)),
70        ))))
71        .parse(input)
72    }
73}
74