1use crate::syntax::SyntaxHighlighter;
2use termimad::MadSkin;
3
4pub struct MarkdownRenderer {
5 skin: MadSkin,
6 highlighter: SyntaxHighlighter,
7 #[allow(dead_code)]
8 width: usize,
9}
10
11impl Default for MarkdownRenderer {
12 fn default() -> Self {
13 Self::new()
14 }
15}
16
17impl MarkdownRenderer {
18 pub fn new() -> Self {
19 let width = crossterm::terminal::size()
20 .map(|(cols, _)| cols as usize)
21 .unwrap_or(80);
22
23 let highlighter =
24 SyntaxHighlighter::new().expect("Failed to initialize syntax highlighter");
25
26 Self {
27 skin: MadSkin::default(),
28 highlighter,
29 width,
30 }
31 }
32
33 pub fn render(&self, markdown: &str) -> String {
34 let processed = self.process_code_blocks(markdown);
36 self.skin.inline(&processed).to_string()
37 }
38
39 fn process_code_blocks(&self, markdown: &str) -> String {
41 let mut result = String::new();
42 let mut lines = markdown.lines().peekable();
43
44 while let Some(line) = lines.next() {
45 if line.starts_with("```") {
46 let lang = line.strip_prefix("```").unwrap_or("").trim();
48
49 let mut code_content = String::new();
51 while let Some(next_line) = lines.peek() {
52 if next_line.starts_with("```") {
53 lines.next(); break;
55 }
56 code_content.push_str(next_line);
57 code_content.push('\n');
58 lines.next();
59 }
60
61 match self.highlighter.highlight_to_ansi(&code_content, lang) {
63 Ok(highlighted) => {
64 result.push_str(&highlighted);
65 }
66 Err(_) => {
67 result.push_str(&code_content);
69 }
70 }
71 } else {
72 result.push_str(line);
73 result.push('\n');
74 }
75 }
76
77 result
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_basic_rendering() {
87 let renderer = MarkdownRenderer::new();
88 let markdown = "# Hello\n**bold** and *italic*";
89 let rendered = renderer.render(markdown);
90 assert!(!rendered.is_empty());
91 }
92
93 #[test]
94 fn test_code_block() {
95 let renderer = MarkdownRenderer::new();
96 let markdown = "```rust\nfn main() {}\n```";
97 let rendered = renderer.render(markdown);
98 assert!(!rendered.is_empty());
99 }
100
101 #[test]
102 fn test_list() {
103 let renderer = MarkdownRenderer::new();
104 let markdown = "- item 1\n- item 2";
105 let rendered = renderer.render(markdown);
106 assert!(!rendered.is_empty());
107 }
108
109 #[test]
110 fn test_link() {
111 let renderer = MarkdownRenderer::new();
112 let markdown = "[link](https://example.com)";
113 let rendered = renderer.render(markdown);
114 assert!(!rendered.is_empty());
115 }
116}