Skip to main content

oak_markdown/builder/
mod.rs

1use crate::{ast::*, language::MarkdownLanguage, parser::MarkdownParser};
2use oak_core::{Builder, BuilderCache, GreenNode, OakError, Parser, RedNode, RedTree, SourceText, TextEdit, source::Source};
3
4/// Markdown 语言的 AST 构建器
5#[derive(Clone)]
6pub struct MarkdownBuilder<'config> {
7    /// 语言配置
8    config: &'config MarkdownLanguage,
9}
10
11impl<'config> MarkdownBuilder<'config> {
12    /// 创建新的 Markdown 构建器
13    pub fn new(config: &'config MarkdownLanguage) -> Self {
14        Self { config }
15    }
16
17    /// 从语法树构建 AST 根节点
18    fn build_root(&self, green_tree: &GreenNode<MarkdownLanguage>, source: &SourceText) -> Result<MarkdownRoot, OakError> {
19        let red_root = RedNode::new(green_tree, 0);
20
21        let mut blocks = Vec::new();
22        for child in red_root.children() {
23            if let RedTree::Node(node) = child {
24                if let Some(block) = self.build_block(node, source) {
25                    blocks.push(block);
26                }
27            }
28        }
29
30        Ok(MarkdownRoot { blocks })
31    }
32
33    /// 构建块级元素
34    fn build_block(&self, node: RedNode<MarkdownLanguage>, source: &SourceText) -> Option<Block> {
35        use crate::kind::MarkdownSyntaxKind::*;
36
37        let kind = node.green.kind;
38        match kind {
39            Heading1 | Heading2 | Heading3 | Heading4 | Heading5 | Heading6 => {
40                let level = match kind {
41                    Heading1 => 1,
42                    Heading2 => 2,
43                    Heading3 => 3,
44                    Heading4 => 4,
45                    Heading5 => 5,
46                    Heading6 => 6,
47                    _ => unreachable!(),
48                };
49                let text = source.get_text_in(node.span());
50                let content = text.trim_start_matches('#').trim_start().to_string();
51                Some(Block::Heading(crate::ast::Heading { level, content, span: node.span() }))
52            }
53            Paragraph => Some(Block::Paragraph(crate::ast::Paragraph { content: source.get_text_in(node.span()).to_string(), span: node.span() })),
54            CodeBlock => {
55                let mut language = None;
56                let mut content = String::new();
57
58                for child in node.children() {
59                    match child {
60                        RedTree::Leaf(leaf) => {
61                            if leaf.kind == CodeLanguage {
62                                language = Some(source.get_text_in(leaf.span).trim().to_string());
63                            }
64                            else if leaf.kind != CodeFence {
65                                content.push_str(&source.get_text_in(leaf.span));
66                            }
67                        }
68                        RedTree::Node(child_node) => {
69                            // 检查子节点是否包含语言标识
70                            for sub_child in child_node.children() {
71                                if let RedTree::Leaf(sub_leaf) = sub_child {
72                                    if sub_leaf.kind == CodeLanguage {
73                                        language = Some(source.get_text_in(sub_leaf.span).trim().to_string());
74                                    }
75                                    else if sub_leaf.kind != CodeFence {
76                                        content.push_str(&source.get_text_in(sub_leaf.span));
77                                    }
78                                }
79                                else if let RedTree::Node(sub_node) = sub_child {
80                                    content.push_str(&source.get_text_in(sub_node.span()));
81                                }
82                            }
83                        }
84                    }
85                }
86
87                Some(Block::CodeBlock(crate::ast::CodeBlock { language, content: content.trim().to_string(), span: node.span() }))
88            }
89            UnorderedList | OrderedList => {
90                let mut items = Vec::new();
91                for child in node.children() {
92                    if let RedTree::Node(child_node) = child {
93                        if child_node.green.kind == ListItem {
94                            items.push(self.build_list_item(child_node, source));
95                        }
96                    }
97                }
98                Some(Block::List(crate::ast::List { is_ordered: kind == OrderedList, items, span: node.span() }))
99            }
100            Blockquote => {
101                let mut content_text = String::new();
102                for child in node.children() {
103                    match child {
104                        RedTree::Leaf(leaf) => {
105                            if leaf.kind != BlockquoteMarker {
106                                content_text.push_str(&source.get_text_in(leaf.span));
107                            }
108                        }
109                        RedTree::Node(child_node) => {
110                            content_text.push_str(&source.get_text_in(child_node.span()));
111                        }
112                    }
113                }
114
115                // 简单的引用处理:将其内容作为段落
116                Some(Block::Blockquote(crate::ast::Blockquote { content: vec![Block::Paragraph(crate::ast::Paragraph { content: content_text.trim().to_string(), span: node.span() })], span: node.span() }))
117            }
118            HorizontalRule => Some(Block::HorizontalRule(crate::ast::HorizontalRule { span: node.span() })),
119            Table => {
120                let text = source.get_text_in(node.span());
121                let lines: Vec<&str> = text.lines().collect();
122                if lines.is_empty() {
123                    return None;
124                }
125
126                let parse_row = |line: &str| -> crate::ast::TableRow {
127                    let cells = line
128                        .split('|')
129                        .filter(|s| !s.trim().is_empty())
130                        .map(|s| crate::ast::TableCell {
131                            content: s.trim().to_string(),
132                            span: node.span(), // 简化处理
133                        })
134                        .collect();
135                    crate::ast::TableRow { cells, span: node.span() }
136                };
137
138                let header = parse_row(lines[0]);
139                let mut rows = Vec::new();
140                for line in lines.iter().skip(1) {
141                    if line.contains("---") {
142                        continue;
143                    }
144                    if line.trim().is_empty() {
145                        continue;
146                    }
147                    rows.push(parse_row(line));
148                }
149
150                Some(Block::Table(crate::ast::Table { header, rows, span: node.span() }))
151            }
152            HtmlTag => {
153                // TODO: 实现 HTML 构建
154                None
155            }
156            _ => None,
157        }
158    }
159
160    fn build_list_item(&self, node: RedNode<MarkdownLanguage>, source: &SourceText) -> crate::ast::ListItem {
161        let mut content = Vec::new();
162        for child in node.children() {
163            if let RedTree::Node(child_node) = child {
164                if let Some(block) = self.build_block(child_node, source) {
165                    content.push(block);
166                }
167            }
168        }
169
170        // 如果没有嵌套块,但有文本内容,将其包装为段落
171        if content.is_empty() {
172            let text = source.get_text_in(node.span()).to_string();
173            if !text.trim().is_empty() {
174                // 简单的清理:移除可能的列表标记前缀
175                let display_text = if text.starts_with("- ") || text.starts_with("* ") {
176                    text[2..].to_string()
177                }
178                else if text.len() > 3 && text.chars().next().unwrap().is_ascii_digit() && text.contains(". ") {
179                    // 处理有序列表标记,如 "1. "
180                    if let Some(pos) = text.find(". ") { text[pos + 2..].to_string() } else { text }
181                }
182                else {
183                    text
184                };
185
186                content.push(crate::ast::Block::Paragraph(crate::ast::Paragraph { content: display_text.trim().to_string(), span: node.span() }));
187            }
188        }
189
190        crate::ast::ListItem { content, is_task: false, is_checked: None, span: node.span() }
191    }
192}
193
194impl<'config> Builder<MarkdownLanguage> for MarkdownBuilder<'config> {
195    fn build<'a, S: Source + ?Sized>(&self, source: &S, edits: &[TextEdit], _cache: &'a mut impl BuilderCache<MarkdownLanguage>) -> oak_core::builder::BuildOutput<MarkdownLanguage> {
196        let parser = MarkdownParser::new(self.config);
197        let mut parse_session = oak_core::parser::session::ParseSession::<MarkdownLanguage>::default();
198        let parse_result = parser.parse(source, edits, &mut parse_session);
199
200        match parse_result.result {
201            Ok(green_tree) => {
202                let source_text = SourceText::new(source.get_text_in((0..source.length()).into()).into_owned());
203                match self.build_root(green_tree, &source_text) {
204                    Ok(ast_root) => oak_core::OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
205                    Err(build_error) => {
206                        let mut diagnostics = parse_result.diagnostics;
207                        diagnostics.push(build_error.clone());
208                        oak_core::OakDiagnostics { result: Err(build_error), diagnostics }
209                    }
210                }
211            }
212            Err(parse_error) => oak_core::OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
213        }
214    }
215}