oak_markdown/builder/
mod.rs1use crate::{
2 ast::*,
3 language::MarkdownLanguage,
4 parser::{MarkdownParser, element_type::MarkdownElementType},
5};
6use oak_core::{Builder, BuilderCache, ElementType, GreenNode, OakError, Parser, RedNode, RedTree, SourceText, TextEdit, UniversalElementRole, source::Source};
7
8#[derive(Clone)]
10pub struct MarkdownBuilder<'config> {
11 config: &'config MarkdownLanguage,
13}
14
15#[allow(unused)]
16impl<'config> MarkdownBuilder<'config> {
17 pub fn new(config: &'config MarkdownLanguage) -> Self {
19 Self { config }
20 }
21
22 fn build_root(&self, green_tree: &GreenNode<MarkdownLanguage>, source: &SourceText) -> Result<MarkdownRoot, OakError> {
24 let red_root = RedNode::new(green_tree, 0);
25
26 let mut blocks = Vec::new();
27 for child in red_root.children() {
28 if let RedTree::Node(node) = child {
29 if let Some(block) = self.build_block(node, source) {
30 blocks.push(block)
31 }
32 }
33 }
34
35 Ok(MarkdownRoot { blocks })
36 }
37
38 fn build_block(&self, node: RedNode<MarkdownLanguage>, source: &SourceText) -> Option<Block> {
40 let role = node.kind::<MarkdownElementType>().role();
41 match role {
42 UniversalElementRole::Container => {
43 None
45 }
46 UniversalElementRole::Statement => {
47 None
49 }
50 _ => None,
51 }
52 }
53
54 fn build_inline(&self, node: RedNode<MarkdownLanguage>, source: &SourceText) -> Option<Inline> {
56 None
57 }
58
59 fn build_list_item(&self, node: RedNode<MarkdownLanguage>, source: &SourceText) -> crate::ast::ListItem {
60 let mut content = Vec::new();
61 for child in node.children() {
62 if let RedTree::Node(child_node) = child {
63 if let Some(block) = self.build_block(child_node, source) {
64 content.push(block)
65 }
66 }
67 }
68
69 if content.is_empty() {
71 let text = source.get_text_in(node.span()).to_string();
72 if !text.trim().is_empty() {
73 let display_text = if text.starts_with("- ") || text.starts_with("* ") {
75 text[2..].to_string()
76 }
77 else if text.len() > 3 && text.chars().next().unwrap().is_ascii_digit() && text.contains(". ") {
78 if let Some(pos) = text.find(". ") { text[pos + 2..].to_string() } else { text }
80 }
81 else {
82 text
83 };
84
85 content.push(crate::ast::Block::Paragraph(crate::ast::Paragraph { content: display_text.trim().to_string(), span: node.span() }))
86 }
87 }
88
89 crate::ast::ListItem { content, is_task: false, is_checked: None, span: node.span() }
90 }
91}
92
93impl<'config> Builder<MarkdownLanguage> for MarkdownBuilder<'config> {
94 fn build<'a, S: Source + ?Sized>(&self, source: &S, edits: &[TextEdit], _cache: &'a mut impl BuilderCache<MarkdownLanguage>) -> oak_core::builder::BuildOutput<MarkdownLanguage> {
95 let parser = MarkdownParser::new(self.config);
96 let mut parse_session = oak_core::parser::session::ParseSession::<MarkdownLanguage>::default();
97 let parse_result = parser.parse(source, edits, &mut parse_session);
98
99 match parse_result.result {
100 Ok(green_tree) => {
101 let source_text = SourceText::new(source.get_text_in((0..source.length()).into()).into_owned());
102 match self.build_root(green_tree, &source_text) {
103 Ok(ast_root) => oak_core::OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
104 Err(build_error) => {
105 let mut diagnostics = parse_result.diagnostics;
106 diagnostics.push(build_error.clone());
107 oak_core::OakDiagnostics { result: Err(build_error), diagnostics }
108 }
109 }
110 }
111 Err(parse_error) => oak_core::OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
112 }
113 }
114}