markdown_it/plugins/cmark/block/
fence.rs1use crate::common::utils::unescape_all;
7use crate::parser::block::{BlockRule, BlockState};
8use crate::parser::extset::MarkdownItExt;
9use crate::{MarkdownIt, Node, NodeValue, Renderer};
10
11#[derive(Debug)]
12pub struct CodeFence {
13 pub info: String,
14 pub marker: char,
15 pub marker_len: usize,
16 pub content: String,
17 pub lang_prefix: &'static str,
18}
19
20impl NodeValue for CodeFence {
21 fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
22 let info = unescape_all(&self.info);
23 let mut split = info.split_whitespace();
24 let lang_name = split.next().unwrap_or("");
25 let mut attrs = node.attrs.clone();
26 let class;
27
28 if !lang_name.is_empty() {
29 class = format!("{}{}", self.lang_prefix, lang_name);
30 attrs.push(("class", class));
31 }
32
33 fmt.cr();
34 fmt.open("pre", &[]);
35 fmt.open("code", &attrs);
36 fmt.text(&self.content);
37 fmt.close("code");
38 fmt.close("pre");
39 fmt.cr();
40 }
41}
42
43#[derive(Debug, Clone, Copy)]
44struct FenceSettings(&'static str);
45impl MarkdownItExt for FenceSettings {}
46
47impl Default for FenceSettings {
48 fn default() -> Self {
49 Self("language-")
50 }
51}
52
53pub fn add(md: &mut MarkdownIt) {
54 md.block.add_rule::<FenceScanner>();
55}
56
57pub fn set_lang_prefix(md: &mut MarkdownIt, lang_prefix: &'static str) {
58 md.ext.insert(FenceSettings(lang_prefix));
59}
60
61#[doc(hidden)]
62pub struct FenceScanner;
63
64impl FenceScanner {
65 fn get_header<'a>(state: &'a mut BlockState) -> Option<(char, usize, &'a str)> {
66
67 if state.line_indent(state.line) >= state.md.max_indent { return None; }
68
69 let line = state.get_line(state.line);
70 let mut chars = line.chars();
71
72 let marker = chars.next()?;
73 if marker != '~' && marker != '`' { return None; }
74
75 let mut len = 1;
77 while Some(marker) == chars.next() { len += 1; }
78
79 if len < 3 { return None; }
80
81 let params = &line[len..];
82
83 if marker == '`' && params.contains(marker) { return None; }
84
85 Some((marker, len, params))
86 }
87}
88
89impl BlockRule for FenceScanner {
90 fn check(state: &mut BlockState) -> Option<()> {
91 Self::get_header(state).map(|_| ())
92 }
93
94 fn run(state: &mut BlockState) -> Option<(Node, usize)> {
95 let (marker, len, params) = Self::get_header(state)?;
96 let params = params.to_owned();
97
98 let mut next_line = state.line;
99 let mut have_end_marker = false;
100
101 'outer: loop {
103 next_line += 1;
104 if next_line >= state.line_max {
105 break;
108 }
109
110 let line = state.get_line(next_line);
111
112 if !line.is_empty() && state.line_indent(next_line) < 0 {
113 break;
117 }
118
119 let mut chars = line.chars().peekable();
120
121 if Some(marker) != chars.next() { continue; }
122
123 if state.line_indent(next_line) >= state.md.max_indent {
124 continue;
125 }
126
127 let mut len_end = 1;
129 while Some(&marker) == chars.peek() {
130 chars.next();
131 len_end += 1;
132 }
133
134 if len_end < len { continue; }
136
137 loop {
139 match chars.next() {
140 Some(' ' | '\t') => {},
141 Some(_) => continue 'outer,
142 None => {
143 have_end_marker = true;
144 break 'outer;
145 }
146 }
147 }
148 }
149
150 let indent = state.line_offsets[state.line].indent_nonspace;
152 let (content, _) = state.get_lines(state.line + 1, next_line, indent as usize, true);
153
154 let lang_prefix = state.md.ext.get::<FenceSettings>().copied().unwrap_or_default().0;
155 let node = Node::new(CodeFence {
156 info: params,
157 marker,
158 marker_len: len,
159 content,
160 lang_prefix,
161 });
162 Some((node, next_line - state.line + if have_end_marker { 1 } else { 0 }))
163 }
164}