markdown_that/plugins/
sourcepos.rs

1//! Add source mapping to resulting HTML, looks like this: `<stuff data-sourcepos="1:1-2:3">`.
2//! ```rust
3//! let md = &mut markdown_that::MarkdownThat::new();
4//! markdown_that::plugins::cmark::add(md);
5//! markdown_that::plugins::sourcepos::add(md);
6//!
7//! let html = md.parse("# hello").render();
8//! assert_eq!(html.trim(), r#"<h1 data-sourcepos="1:1-1:7">hello</h1>"#);
9//! ```
10use crate::common::sourcemap::SourceWithLineStarts;
11use crate::parser::block::builtin::BlockParserRule;
12use crate::parser::core::{CoreRule, Root};
13use crate::parser::inline::builtin::InlineParserRule;
14use crate::{MarkdownThat, Node};
15
16pub fn add(md: &mut MarkdownThat) {
17    md.add_rule::<SyntaxPosRule>()
18        .after::<BlockParserRule>()
19        .after::<InlineParserRule>();
20}
21
22#[doc(hidden)]
23pub struct SyntaxPosRule;
24impl CoreRule for SyntaxPosRule {
25    fn run(root: &mut Node, _: &MarkdownThat) {
26        let source = root.cast::<Root>().unwrap().content.as_str();
27        let mapping = SourceWithLineStarts::new(source);
28
29        root.walk_mut(|node, _| {
30            if let Some(map) = node.srcmap {
31                let ((startline, startcol), (endline, endcol)) = map.get_positions(&mapping);
32                node.attrs.push((
33                    "data-sourcepos",
34                    format!("{}:{}-{}:{}", startline, startcol, endline, endcol),
35                ));
36            }
37        });
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    #[test]
44    fn header_test() {
45        // same as doctest, keep in sync!
46        // used for code coverage and quicker rust-analyzer hints
47        let md = &mut crate::MarkdownThat::new();
48        crate::plugins::cmark::add(md);
49        crate::plugins::sourcepos::add(md);
50
51        let html = md.parse("# hello").render();
52        assert_eq!(html.trim(), r#"<h1 data-sourcepos="1:1-1:7">hello</h1>"#);
53    }
54}