Skip to main content

limit_cli/
render.rs

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        // Pre-process code blocks with syntax highlighting
35        let processed = self.process_code_blocks(markdown);
36        self.skin.inline(&processed).to_string()
37    }
38
39    /// Process code blocks and apply syntax highlighting
40    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                // Start of code block
47                let lang = line.strip_prefix("```").unwrap_or("").trim();
48
49                // Read code content
50                let mut code_content = String::new();
51                while let Some(next_line) = lines.peek() {
52                    if next_line.starts_with("```") {
53                        lines.next(); // Consume the closing ```
54                        break;
55                    }
56                    code_content.push_str(next_line);
57                    code_content.push('\n');
58                    lines.next();
59                }
60
61                // Apply syntax highlighting
62                match self.highlighter.highlight_to_ansi(&code_content, lang) {
63                    Ok(highlighted) => {
64                        result.push_str(&highlighted);
65                    }
66                    Err(_) => {
67                        // Fallback to code without highlighting
68                        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}