- 1
//! Hardware terminal graphics protocol detection and image/framebuffer - 2
//! rendering (Kitty, iTerm2, Sixel, Half-block ANSI fallback). - 3
- 4
use ratatui::buffer::Buffer; - 5
use ratatui::layout::Rect; - 6
use ratatui::style::Color; - 7
- 8
#[derive(Debug, Clone, Copy, PartialEq, Eq)] - 9
pub enum GraphicsProtocol { - 10
Kitty, - 11
ITerm2, - 12
Sixel, - 13
HalfBlockAnsi, - 14
} - 15
- 16
impl GraphicsProtocol { - 17
/// Autodetect terminal graphics support from environment variables. - 18
pub fn detect() -> Self { - 19
// Ghostty, Kitty, WezTerm support Kitty graphics protocol natively - 20
if let Ok(term) = std::env::var("TERM") - 21
&& (term.contains("kitty") || term.contains("ghostty") || term.contains("wezterm")) - 22
{ - 23
return GraphicsProtocol::Kitty; - 24
} - 25
if std::env::var("KITTY_WINDOW_ID").is_ok() - 26
|| std::env::var("GHOSTTY_RESOURCES_DIR").is_ok() - 27
{ - 28
return GraphicsProtocol::Kitty; - 29
} - 30
// iTerm2 or VSCode integrated terminal - 31
if let Ok(program) = std::env::var("TERM_PROGRAM") { - 32
if program == "iTerm.app" || program == "vscode" { - 33
return GraphicsProtocol::ITerm2; - 34
} - 35
if program == "WezTerm" { - 36
return GraphicsProtocol::Kitty; - 37
} - 38
} - 39
// Fallback: 24-bit TrueColor Half-block ANSI - 40
GraphicsProtocol::HalfBlockAnsi - 41
} - 42
} - 43
- 44
/// A rendered visual artifact cell for terminal previewing. - 45
#[derive(Debug, Clone)] - 46
pub struct HalfBlockCell { - 47
pub char_glyph: char, - 48
pub fg: Color, - 49
pub bg: Color, - 50
} - 51
- 52
/// Scaled half-block grid for rendering images on any terminal without protocols. - 53
#[derive(Debug, Clone)] - 54
pub struct HalfBlockImage { - 55
pub width: u16, - 56
pub height: u16, - 57
pub cells: Vec<HalfBlockCell>, - 58
} - 59
- 60
impl HalfBlockImage { - 61
/// Generate a placeholder image buffer of the given dimensions. - 62
/// Used when no real preview image is available yet from the launch server. - 63
pub fn placeholder(width: u16, height: u16) -> Self { - 64
let w = width.max(10) as usize; - 65
let h = height.max(4) as usize; - 66
let mut cells = Vec::with_capacity(w * h); - 67
- 68
for y in 0..h { - 69
for x in 0..w { - 70
// Header bar - 71
if y == 0 { - 72
cells.push(HalfBlockCell { - 73
char_glyph: '▀', - 74
fg: Color::Rgb(0xdf, 0x79, 0x5f), // Terracotta - 75
bg: Color::Rgb(0x1c, 0x1c, 0x19), - 76
}); - 77
} else if x == 0 || x == w - 1 || y == h - 1 { - 78
cells.push(HalfBlockCell { - 79
char_glyph: '█', - 80
fg: Color::Rgb(0x34, 0x34, 0x2f), - 81
bg: Color::Rgb(0x17, 0x17, 0x14), - 82
}); - 83
} else if (2..=4).contains(&y) && x >= 3 && x <= w.saturating_sub(4) { - 84
// Sparkline wave - 85
let wave_val = ((x as f32 * 0.4).sin() * 2.0) as i32; - 86
let target_y = 3 + wave_val; - 87
if (y as i32) == target_y { - 88
cells.push(HalfBlockCell { - 89
char_glyph: '▄', - 90
fg: Color::Rgb(0x73, 0xa9, 0x82), // Sage green - 91
bg: Color::Rgb(0x1c, 0x1c, 0x19), - 92
}); - 93
} else { - 94
cells.push(HalfBlockCell { - 95
char_glyph: ' ', - 96
fg: Color::Rgb(0xee, 0xea, 0xe2), - 97
bg: Color::Rgb(0x1c, 0x1c, 0x19), - 98
}); - 99
} - 100
} else { - 101
cells.push(HalfBlockCell { - 102
char_glyph: ' ', - 103
fg: Color::Rgb(0xee, 0xea, 0xe2), - 104
bg: Color::Rgb(0x1c, 0x1c, 0x19), - 105
}); - 106
} - 107
} - 108
} - 109
- 110
Self { - 111
width: w as u16, - 112
height: h as u16, - 113
cells, - 114
} - 115
} - 116
- 117
/// Render onto a ratatui buffer - 118
pub fn render(&self, area: Rect, buf: &mut Buffer) { - 119
let draw_w = (area.width as usize).min(self.width as usize); - 120
let draw_h = (area.height as usize).min(self.height as usize); - 121
- 122
for y in 0..draw_h { - 123
for x in 0..draw_w { - 124
let idx = y * (self.width as usize) + x; - 125
if let Some(cell) = self.cells.get(idx) - 126
&& let Some(cell_ref) = buf.cell_mut((area.x + x as u16, area.y + y as u16)) - 127
{ - 128
cell_ref.set_char(cell.char_glyph); - 129
cell_ref.set_fg(cell.fg); - 130
cell_ref.set_bg(cell.bg); - 131
} - 132
} - 133
} - 134
} - 135
} - 136
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.