177 lines
5.0 KiB
Rust
177 lines
5.0 KiB
Rust
use alacritty_terminal::{
|
||
term::color::Colors,
|
||
vte::ansi::{Color, NamedColor, Rgb},
|
||
};
|
||
use gpui::{Hsla, Rgba};
|
||
|
||
use crate::themes::TerminalTheme;
|
||
|
||
/// The colors a terminal paints with: its theme, under any a program set by OSC 4, 10, 11 or 12.
|
||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||
pub struct Ink {
|
||
pub ansi: [Hsla; 16],
|
||
pub fg: Hsla,
|
||
pub bg: Hsla,
|
||
pub cursor: Hsla,
|
||
pub cursor_text: Hsla,
|
||
}
|
||
|
||
impl Ink {
|
||
pub fn new(theme: &TerminalTheme, set: &Colors) -> Self {
|
||
let named = |name: NamedColor| set[name].map(hsla);
|
||
let mut ansi = theme.ansi;
|
||
for (index, color) in ansi.iter_mut().enumerate() {
|
||
if let Some(spec) = set[index] {
|
||
*color = hsla(spec);
|
||
}
|
||
}
|
||
Self {
|
||
ansi,
|
||
fg: named(NamedColor::Foreground).unwrap_or(theme.foreground),
|
||
bg: named(NamedColor::Background).unwrap_or(theme.background),
|
||
cursor: named(NamedColor::Cursor).unwrap_or(theme.cursor),
|
||
cursor_text: theme.cursor_text,
|
||
}
|
||
}
|
||
|
||
pub fn resolve(&self, color: Color, set: &Colors) -> Hsla {
|
||
match color {
|
||
Color::Spec(spec) => hsla(spec),
|
||
Color::Indexed(index) => self.indexed(index.into(), set),
|
||
Color::Named(name) => self.named(name),
|
||
}
|
||
}
|
||
|
||
fn named(&self, name: NamedColor) -> Hsla {
|
||
match name {
|
||
NamedColor::Foreground | NamedColor::BrightForeground => self.fg,
|
||
NamedColor::Background => self.bg,
|
||
NamedColor::Cursor => self.cursor,
|
||
NamedColor::DimForeground => self.fg.opacity(DIM),
|
||
dim if (NamedColor::DimBlack as usize..=NamedColor::DimWhite as usize)
|
||
.contains(&(dim as usize)) =>
|
||
{
|
||
self.named(dim.to_bright()).opacity(DIM)
|
||
}
|
||
ansi => self.ansi[ansi as usize],
|
||
}
|
||
}
|
||
|
||
fn indexed(&self, index: usize, set: &Colors) -> Hsla {
|
||
if let Some(spec) = set[index] {
|
||
return hsla(spec);
|
||
}
|
||
match index {
|
||
0..16 => self.ansi[index],
|
||
16..232 => {
|
||
let step = |level: usize| if level == 0 { 0 } else { 55 + 40 * level as u8 };
|
||
let cube = index - 16;
|
||
hsla(Rgb {
|
||
r: step(cube / 36),
|
||
g: step(cube / 6 % 6),
|
||
b: step(cube % 6),
|
||
})
|
||
}
|
||
_ => {
|
||
let gray = 8 + 10 * (index - 232) as u8;
|
||
hsla(Rgb {
|
||
r: gray,
|
||
g: gray,
|
||
b: gray,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Color `index` as an answer to a program's query: 0–255, then 256 text, 257 ground, 258 cursor.
|
||
pub fn query(&self, index: usize, set: &Colors) -> Rgb {
|
||
let color = match index {
|
||
256 => self.fg,
|
||
257 => self.bg,
|
||
258 => self.cursor,
|
||
index => self.indexed(index.min(255), set),
|
||
};
|
||
let rgba = color.to_rgb();
|
||
let byte = |channel: f32| (channel * 255.0).round() as u8;
|
||
Rgb {
|
||
r: byte(rgba.r),
|
||
g: byte(rgba.g),
|
||
b: byte(rgba.b),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// How much of the text color dim text keeps.
|
||
pub const DIM: f32 = 0.66;
|
||
|
||
fn hsla(spec: Rgb) -> Hsla {
|
||
let channel = |value: u8| f32::from(value) / 255.0;
|
||
Rgba {
|
||
r: channel(spec.r),
|
||
g: channel(spec.g),
|
||
b: channel(spec.b),
|
||
a: 1.0,
|
||
}
|
||
.into()
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use crate::themes;
|
||
|
||
fn ink(set: &Colors) -> Ink {
|
||
Ink::new(themes::find("catppuccin-mocha").expect("mocha"), set)
|
||
}
|
||
|
||
#[test]
|
||
fn indexed_colors_follow_the_xterm_cube_and_ramp() {
|
||
let set = Colors::default();
|
||
let ink = ink(&set);
|
||
assert_eq!(ink.query(196, &set), Rgb { r: 255, g: 0, b: 0 });
|
||
assert_eq!(ink.query(16, &set), Rgb { r: 0, g: 0, b: 0 });
|
||
assert_eq!(
|
||
ink.query(244, &set),
|
||
Rgb {
|
||
r: 128,
|
||
g: 128,
|
||
b: 128
|
||
}
|
||
);
|
||
assert_eq!(
|
||
ink.query(1, &set),
|
||
Rgb {
|
||
r: 0xf3,
|
||
g: 0x8b,
|
||
b: 0xa8
|
||
},
|
||
"the theme's red"
|
||
);
|
||
assert_eq!(
|
||
ink.query(257, &set),
|
||
Rgb {
|
||
r: 0x1e,
|
||
g: 0x1e,
|
||
b: 0x2e
|
||
}
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn a_color_the_program_set_wins() {
|
||
let mut set = Colors::default();
|
||
set[1] = Some(Rgb { r: 1, g: 2, b: 3 });
|
||
set[NamedColor::Background] = Some(Rgb { r: 9, g: 9, b: 9 });
|
||
let ink = ink(&set);
|
||
assert_eq!(
|
||
ink.resolve(Color::Named(NamedColor::Red), &set),
|
||
hsla(Rgb { r: 1, g: 2, b: 3 })
|
||
);
|
||
assert_eq!(ink.bg, hsla(Rgb { r: 9, g: 9, b: 9 }));
|
||
assert_eq!(
|
||
ink.resolve(Color::Named(NamedColor::DimRed), &set),
|
||
ink.ansi[1].opacity(DIM)
|
||
);
|
||
}
|
||
}
|