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-tui's richer types (with internal methods).
6#[cfg(feature = "tui")]
7pub use vtcode_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 = highlight_config.map(|cfg| vtcode_tui::TuiSyntaxHighlightingConfig {
70        enabled: cfg.enabled,
71        theme: cfg.theme.clone(),
72        cache_themes: cfg.cache_themes,
73        max_file_size_mb: cfg.max_file_size_mb,
74        enabled_languages: cfg.enabled_languages.clone(),
75        highlight_timeout_ms: cfg.highlight_timeout_ms,
76    });
77    vtcode_tui::ui::markdown::render_markdown_to_lines_with_options(
78        source,
79        base_style,
80        &tui_theme_styles,
81        tui_highlight_cfg.as_ref(),
82        render_options,
83    )
84}
85
86#[cfg(not(feature = "tui"))]
87pub fn render_markdown_to_lines_with_options(
88    source: &str,
89    base_style: Style,
90    _theme_styles: &ThemeStyles,
91    _highlight_config: Option<&SyntaxHighlightingConfig>,
92    _render_options: RenderMarkdownOptions,
93) -> Vec<MarkdownLine> {
94    let mut lines: Vec<MarkdownLine> = source
95        .lines()
96        .map(|line| MarkdownLine {
97            segments: if line.is_empty() {
98                Vec::new()
99            } else {
100                vec![MarkdownSegment {
101                    style: base_style,
102                    text: line.to_string(),
103                    link_target: None,
104                }]
105            },
106        })
107        .collect();
108    if lines.is_empty() {
109        lines.push(MarkdownLine::default());
110    }
111    lines
112}
113
114pub fn render_markdown(source: &str) -> Vec<MarkdownLine> {
115    let styles = theme::active_styles();
116    render_markdown_to_lines(source, Style::default(), &styles, None)
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn facade_renders_markdown() {
125        let lines = render_markdown("# Heading");
126        assert!(!lines.is_empty());
127    }
128}