Skip to main content

vtcode_core/ui/
markdown.rs

1use crate::config::loader::SyntaxHighlightingConfig;
2use crate::ui::theme::{self, ThemeStyles};
3use anstyle::Style;
4
5// When TUI is enabled, use vtcode-ui's richer types (with internal methods).
6#[cfg(feature = "tui")]
7pub use vtcode_ui::tui::ui::markdown::{
8    HighlightedSegment, MarkdownLine, MarkdownSegment, RenderMarkdownOptions,
9    highlight_code_to_ansi, highlight_code_to_segments, highlight_line_for_diff,
10};
11
12// When headless, use the plain data types from commons.
13#[cfg(not(feature = "tui"))]
14pub use vtcode_commons::ui_protocol::{
15    HighlightedSegment, MarkdownLine, MarkdownSegment, RenderMarkdownOptions,
16};
17
18#[cfg(not(feature = "tui"))]
19pub fn highlight_code_to_ansi(code: &str, _language: Option<&str>, _theme: &str) -> String {
20    code.to_string()
21}
22
23#[cfg(not(feature = "tui"))]
24pub fn highlight_code_to_segments(
25    code: &str,
26    _language: Option<&str>,
27    _theme: &str,
28) -> Vec<HighlightedSegment> {
29    vec![HighlightedSegment {
30        style: Style::default(),
31        text: code.to_string(),
32    }]
33}
34
35#[cfg(not(feature = "tui"))]
36pub fn highlight_line_for_diff(
37    line: &str,
38    _language: Option<&str>,
39) -> Option<Vec<(Style, String)>> {
40    Some(vec![(Style::default(), line.to_string())])
41}
42
43// ── Markdown rendering ──────────────────────────────────────────────────────
44
45pub fn render_markdown_to_lines(
46    source: &str,
47    base_style: Style,
48    theme_styles: &ThemeStyles,
49    highlight_config: Option<&SyntaxHighlightingConfig>,
50) -> Vec<MarkdownLine> {
51    render_markdown_to_lines_with_options(
52        source,
53        base_style,
54        theme_styles,
55        highlight_config,
56        RenderMarkdownOptions::default(),
57    )
58}
59
60#[cfg(feature = "tui")]
61pub fn render_markdown_to_lines_with_options(
62    source: &str,
63    base_style: Style,
64    theme_styles: &ThemeStyles,
65    highlight_config: Option<&SyntaxHighlightingConfig>,
66    render_options: RenderMarkdownOptions,
67) -> Vec<MarkdownLine> {
68    let tui_theme_styles = crate::ui::tui_compat::tui_theme_styles_from_core(theme_styles);
69    let tui_highlight_cfg =
70        highlight_config.map(|cfg| vtcode_ui::tui::TuiSyntaxHighlightingConfig {
71            enabled: cfg.enabled,
72            theme: cfg.theme.clone(),
73            cache_themes: cfg.cache_themes,
74            max_file_size_mb: cfg.max_file_size_mb,
75            enabled_languages: cfg.enabled_languages.clone(),
76            highlight_timeout_ms: cfg.highlight_timeout_ms,
77        });
78    vtcode_ui::tui::ui::markdown::render_markdown_to_lines_with_options(
79        source,
80        base_style,
81        &tui_theme_styles,
82        tui_highlight_cfg.as_ref(),
83        render_options,
84    )
85}
86
87#[cfg(not(feature = "tui"))]
88pub fn render_markdown_to_lines_with_options(
89    source: &str,
90    base_style: Style,
91    _theme_styles: &ThemeStyles,
92    _highlight_config: Option<&SyntaxHighlightingConfig>,
93    _render_options: RenderMarkdownOptions,
94) -> Vec<MarkdownLine> {
95    let mut lines: Vec<MarkdownLine> = source
96        .lines()
97        .map(|line| MarkdownLine {
98            segments: if line.is_empty() {
99                Vec::new()
100            } else {
101                vec![MarkdownSegment {
102                    style: base_style,
103                    text: line.to_string(),
104                    link_target: None,
105                }]
106            },
107        })
108        .collect();
109    if lines.is_empty() {
110        lines.push(MarkdownLine::default());
111    }
112    lines
113}
114
115pub fn render_markdown(source: &str) -> Vec<MarkdownLine> {
116    let styles = theme::active_styles();
117    render_markdown_to_lines(source, Style::default(), &styles, None)
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn facade_renders_markdown() {
126        let lines = render_markdown("# Heading");
127        assert!(!lines.is_empty());
128    }
129}