use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::vte::ansi::{Color as AnsiColor, NamedColor};
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::Span;
use ratatui::widgets::Clear;
use crate::agent::GridSnapshot;
use crate::app::App;
pub(super) fn draw_agent_pane(f: &mut Frame, app: &App, area: Rect, _active: bool) {
f.render_widget(Clear, area);
let Some(session) = app.agent.as_ref() else {
f.render_widget(
Span::styled(" agent ", Style::default().fg(Color::DarkGray)),
area,
);
return;
};
if area.width == 0 || area.height == 0 {
return;
}
session.resize(area.width, area.height);
let Some(snap) = session.grid_snapshot() else {
return;
};
let max_rows = (area.height as usize).min(snap.rows);
let max_cols = (area.width as usize).min(snap.cols);
let bg_style = crate::theme::active().ui_background();
let def_fg = bg_style.and_then(|s| s.fg);
let def_bg = bg_style.and_then(|s| s.bg);
let buf = f.buffer_mut();
for cell in &snap.cells {
if cell.row >= max_rows || cell.col >= max_cols {
continue;
}
let x = area.x + cell.col as u16;
let y = area.y + cell.row as u16;
let style = cell_style(cell.fg, cell.bg, cell.flags, def_fg, def_bg);
let target = &mut buf[(x, y)];
let mut s = [0u8; 4];
target.set_symbol(cell.c.encode_utf8(&mut s));
target.set_style(style);
}
}
pub(super) fn place_agent_cursor(f: &mut Frame, snap: &GridSnapshot, area: Rect) {
let max_rows = (area.height as usize).min(snap.rows);
let max_cols = (area.width as usize).min(snap.cols);
if snap.cursor_col >= max_cols || snap.cursor_row >= max_rows {
return;
}
let x = area.x + snap.cursor_col as u16;
let y = area.y + snap.cursor_row as u16;
f.set_cursor_position((x, y));
}
fn cell_style(
fg: AnsiColor,
bg: AnsiColor,
flags: Flags,
def_fg: Option<Color>,
def_bg: Option<Color>,
) -> Style {
let mut fg = map_color(fg);
let mut bg = map_color(bg);
if fg == Color::Reset
&& let Some(c) = def_fg
{
fg = c;
}
if bg == Color::Reset
&& let Some(c) = def_bg
{
bg = c;
}
if flags.contains(Flags::INVERSE) {
std::mem::swap(&mut fg, &mut bg);
}
let mut style = Style::default();
style = style.fg(fg).bg(bg);
let mut modifier = Modifier::empty();
if flags.contains(Flags::BOLD) {
modifier |= Modifier::BOLD;
}
if flags.contains(Flags::ITALIC) {
modifier |= Modifier::ITALIC;
}
if flags.contains(Flags::DIM) {
modifier |= Modifier::DIM;
}
if flags.intersects(Flags::ALL_UNDERLINES) {
modifier |= Modifier::UNDERLINED;
}
if flags.contains(Flags::STRIKEOUT) {
modifier |= Modifier::CROSSED_OUT;
}
if flags.contains(Flags::HIDDEN) {
modifier |= Modifier::HIDDEN;
}
style.add_modifier(modifier)
}
fn map_color(c: AnsiColor) -> Color {
match c {
AnsiColor::Spec(rgb) => Color::Rgb(rgb.r, rgb.g, rgb.b),
AnsiColor::Indexed(n) => Color::Indexed(n),
AnsiColor::Named(named) => map_named(named),
}
}
fn map_named(c: NamedColor) -> Color {
use NamedColor::*;
match c {
Black | DimBlack => Color::Black,
Red | DimRed => Color::Red,
Green | DimGreen => Color::Green,
Yellow | DimYellow => Color::Yellow,
Blue | DimBlue => Color::Blue,
Magenta | DimMagenta => Color::Magenta,
Cyan | DimCyan => Color::Cyan,
White | DimWhite => Color::Gray,
BrightBlack => Color::DarkGray,
BrightRed => Color::LightRed,
BrightGreen => Color::LightGreen,
BrightYellow => Color::LightYellow,
BrightBlue => Color::LightBlue,
BrightMagenta => Color::LightMagenta,
BrightCyan => Color::LightCyan,
BrightWhite => Color::White,
Foreground | BrightForeground | DimForeground | Background | Cursor => Color::Reset,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_base_colors_map_to_ansi() {
assert_eq!(map_named(NamedColor::Red), Color::Red);
assert_eq!(map_named(NamedColor::BrightBlue), Color::LightBlue);
assert_eq!(map_named(NamedColor::White), Color::Gray);
assert_eq!(map_named(NamedColor::BrightWhite), Color::White);
}
#[test]
fn special_slots_defer_to_terminal() {
assert_eq!(map_named(NamedColor::Foreground), Color::Reset);
assert_eq!(map_named(NamedColor::Background), Color::Reset);
}
#[test]
fn indexed_and_rgb_pass_through() {
assert_eq!(map_color(AnsiColor::Indexed(200)), Color::Indexed(200));
assert_eq!(
map_color(AnsiColor::Spec(alacritty_terminal::vte::ansi::Rgb {
r: 1,
g: 2,
b: 3
})),
Color::Rgb(1, 2, 3)
);
}
#[test]
fn inverse_swaps_fg_and_bg() {
let style = cell_style(
AnsiColor::Named(NamedColor::Red),
AnsiColor::Named(NamedColor::Blue),
Flags::INVERSE,
None,
None,
);
assert_eq!(style.fg, Some(Color::Blue));
assert_eq!(style.bg, Some(Color::Red));
}
#[test]
fn bold_italic_flags_map_to_modifiers() {
let style = cell_style(
AnsiColor::Named(NamedColor::Foreground),
AnsiColor::Named(NamedColor::Background),
Flags::BOLD | Flags::ITALIC,
None,
None,
);
assert!(style.add_modifier.contains(Modifier::BOLD));
assert!(style.add_modifier.contains(Modifier::ITALIC));
}
#[test]
fn default_slots_take_theme_colors_when_set() {
let style = cell_style(
AnsiColor::Named(NamedColor::Foreground),
AnsiColor::Named(NamedColor::Background),
Flags::empty(),
Some(Color::Red),
Some(Color::Blue),
);
assert_eq!(style.fg, Some(Color::Red));
assert_eq!(style.bg, Some(Color::Blue));
}
#[test]
fn default_slots_stay_reset_without_theme() {
let style = cell_style(
AnsiColor::Named(NamedColor::Foreground),
AnsiColor::Named(NamedColor::Background),
Flags::empty(),
None,
None,
);
assert_eq!(style.fg, Some(Color::Reset));
assert_eq!(style.bg, Some(Color::Reset));
}
#[test]
fn explicit_ansi_color_ignores_theme_default() {
let style = cell_style(
AnsiColor::Named(NamedColor::Red),
AnsiColor::Named(NamedColor::Background),
Flags::empty(),
Some(Color::Rgb(1, 2, 3)),
Some(Color::Rgb(4, 5, 6)),
);
assert_eq!(style.fg, Some(Color::Red));
assert_eq!(style.bg, Some(Color::Rgb(4, 5, 6)));
}
}