Skip to main content

vtcode_diff/
adapters.rs

1//! Feature-gated ANSI and Ratatui renderer adapters.
2
3#[cfg(feature = "ansi")]
4mod ansi_adapter {
5    use crate::DiffRow;
6    use anstyle::{Reset, Style};
7
8    /// Caller-supplied foreground styles for ANSI output.
9    #[derive(Debug, Clone, Copy, Default)]
10    pub struct AnsiDiffPalette {
11        /// Hunk and omission style.
12        pub header: Style,
13        /// Context style.
14        pub context: Style,
15        /// Addition style.
16        pub addition: Style,
17        /// Deletion style.
18        pub deletion: Style,
19        /// Extra intraline emphasis.
20        pub emphasis: Style,
21    }
22
23    /// Renders semantic rows as ANSI lines.
24    #[must_use]
25    pub fn render_ansi_rows(rows: &[DiffRow], palette: AnsiDiffPalette, color: bool) -> Vec<String> {
26        rows.iter()
27            .map(|row| {
28                let mut output = String::new();
29                output.push(row.marker);
30                output.push(' ');
31                if let Some(cell) = &row.left {
32                    render_cell(&mut output, cell, palette, color);
33                }
34                if let Some(cell) = &row.right {
35                    output.push_str(" │ ");
36                    render_cell(&mut output, cell, palette, color);
37                }
38                output
39            })
40            .collect()
41    }
42
43    fn render_cell(output: &mut String, cell: &crate::DiffCell, palette: AnsiDiffPalette, color: bool) {
44        let style = match cell.marker {
45            '+' => palette.addition,
46            '-' => palette.deletion,
47            '@' | '…' => palette.header,
48            _ => palette.context,
49        };
50        for segment in &cell.segments {
51            if color {
52                let selected = if segment.emphasized { palette.emphasis } else { style };
53                output.push_str(&selected.render().to_string());
54                output.push_str(&segment.text);
55                output.push_str(&Reset.render().to_string());
56            } else {
57                output.push_str(&segment.text);
58            }
59        }
60    }
61}
62
63#[cfg(feature = "ansi")]
64pub use ansi_adapter::{AnsiDiffPalette, render_ansi_rows};
65
66#[cfg(feature = "ratatui")]
67mod ratatui_adapter {
68    use crate::DiffRow;
69    use ratatui::text::{Line, Span};
70
71    /// Converts semantic rows to unstyled Ratatui lines for caller styling.
72    #[must_use]
73    pub fn to_ratatui_lines(rows: &[DiffRow]) -> Vec<Line<'static>> {
74        rows.iter()
75            .map(|row| {
76                let mut spans = vec![Span::raw(format!("{} ", row.marker))];
77                if let Some(cell) = &row.left {
78                    spans.extend(cell.segments.iter().map(|segment| Span::raw(segment.text.clone())));
79                }
80                if let Some(cell) = &row.right {
81                    spans.push(Span::raw(" │ "));
82                    spans.extend(cell.segments.iter().map(|segment| Span::raw(segment.text.clone())));
83                }
84                Line::from(spans)
85            })
86            .collect()
87    }
88}
89
90#[cfg(feature = "ratatui")]
91pub use ratatui_adapter::to_ratatui_lines;