markdown_it_footnote/
inline.rs1use markdown_it::{
21 parser::inline::{InlineRule, InlineState},
22 MarkdownIt, Node, NodeValue,
23};
24
25use crate::{definitions::FootnoteDefinition, FootnoteMap};
26
27pub fn add(md: &mut MarkdownIt) {
29 md.inline.add_rule::<InlineFootnoteScanner>();
31}
32
33#[derive(Debug)]
34pub struct InlineFootnote;
35impl NodeValue for InlineFootnote {
36 fn render(&self, node: &Node, fmt: &mut dyn markdown_it::Renderer) {
37 fmt.contents(&node.children);
39 }
40}
41
42struct InlineFootnoteScanner;
44
45impl InlineRule for InlineFootnoteScanner {
46 const MARKER: char = '^';
47
48 fn check(state: &mut InlineState) -> Option<usize> {
49 let mut chars = state.src[state.pos..state.pos_max].chars();
50
51 let Some('^') = chars.next() else { return None; };
53 let Some('[') = chars.next() else { return None; };
54
55 let content_start = state.pos + 2;
56
57 match parse_footnote(state, content_start) {
58 Some(content_end) => Some(content_end + 1 - state.pos),
59 None => None,
60 }
61 }
62
63 fn run(state: &mut InlineState) -> Option<(Node, usize)> {
64 let mut chars = state.src[state.pos..state.pos_max].chars();
65
66 let Some('^') = chars.next() else { return None; };
68 let Some('[') = chars.next() else { return None; };
69
70 let content_start = state.pos + 2;
71
72 match parse_footnote(state, content_start) {
73 Some(content_end) => {
74 let foot_map = state.root_ext.get_or_insert_default::<FootnoteMap>();
75 let (def_id, ref_id) = foot_map.add_inline_def();
76
77 let current_node = std::mem::replace(
79 &mut state.node,
80 Node::new(FootnoteDefinition {
81 label: None,
82 def_id: Some(def_id),
83 inline: true,
84 }),
85 );
86
87 let start = state.pos;
89 let max = state.pos_max;
90 state.pos = content_start;
91 state.pos_max = content_end;
92 state.md.inline.tokenize(state);
93 state.pos = start;
94 state.pos_max = max;
95
96 let def_node = std::mem::replace(&mut state.node, current_node);
98
99 let ref_node = Node::new(crate::references::FootnoteReference {
100 label: None,
101 ref_id,
102 def_id,
103 });
104
105 let mut outer_node = Node::new(InlineFootnote);
107 outer_node.children = vec![def_node, ref_node];
108
109 Some((outer_node, content_end + 1 - state.pos))
110 }
111 None => None,
112 }
113 }
114}
115
116fn parse_footnote(state: &mut InlineState, start: usize) -> Option<usize> {
119 let old_pos = state.pos;
120 let mut label_end = None;
121 state.pos = start + 1;
122 let mut found = false;
123 while let Some(ch) = state.src[state.pos..state.pos_max].chars().next() {
124 if ch == ']' {
125 found = true;
126 break;
127 }
128 state.md.inline.skip_token(state);
129 }
130
131 if found {
132 label_end = Some(state.pos);
133 }
134
135 state.pos = old_pos;
137
138 label_end
139}