wayver's git archive


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

sable-markdown/src/parser/blocks/code_block.rs@main

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    bytes::complete::tag,
5    character::complete::char,
6    combinator::{not, opt, peek, recognize, value},
7    multi::{many_m_n, many0, many1},
8    sequence::preceded,
9};
10
11use crate::{
12    ast::{CodeBlock, CodeBlockKind},
13    parser::util::{line_terminated, not_eof_or_eol0, not_eof_or_eol1},
14};
15
16pub(super) fn code_block<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
17    move |input: &'a str| alt((code_block_indented(), code_block_fenced())).parse(input)
18}
19
20pub(super) fn code_block_indented<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
21    move |input: &'a str| {
22        let line_parser = preceded(
23            alt((value((), many_m_n(4, 4, char(' '))), value((), char('\t')))),
24            line_terminated(not_eof_or_eol0),
25        );
26
27        let (input, lines) = many1(line_parser).parse(input)?;
28        let literal = lines.join("\n");
29
30        let code_block = CodeBlock {
31            kind: CodeBlockKind::Indented,
32            literal,
33        };
34
35        Ok((input, code_block))
36    }
37}
38
39pub(super) fn code_block_fenced<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
40    move |input: &'a str| {
41        let (input, space_prefix) = many_m_n(0, 3, char(' ')).parse(input)?;
42        let prefix_length = space_prefix.len();
43
44        let (input, (fence, info)) = line_terminated((
45            recognize(alt((
46                many_m_n(3, usize::MAX, char('`')),
47                many_m_n(3, usize::MAX, char('~')),
48            ))),
49            opt(recognize(not_eof_or_eol1)),
50        ))
51        .parse(input)?;
52        let ending_fence = || {
53            line_terminated((
54                many_m_n(0, 3, char(' ')),
55                tag(fence),
56                many0(char(fence.chars().next().unwrap())),
57            ))
58        };
59
60        let (input, lines) = many0(preceded(
61            peek(not(ending_fence())),
62            preceded(
63                many_m_n(0, prefix_length, char(' ')),
64                line_terminated(not_eof_or_eol0),
65            ),
66        ))
67        .parse(input)?;
68        let (input, _) = ending_fence().parse(input)?;
69
70        let literal = lines.join("\n");
71        let code_block = CodeBlock {
72            kind: CodeBlockKind::Fenced {
73                info: info.map(ToOwned::to_owned),
74            },
75            literal,
76        };
77
78        Ok((input, code_block))
79    }
80}
81