diff --git a/termsnap-lib/src/colors.rs b/termsnap-lib/src/colors.rs index c1883c8..803d2a2 100644 --- a/termsnap-lib/src/colors.rs +++ b/termsnap-lib/src/colors.rs @@ -1,11 +1,11 @@ use alacritty_terminal::{ term::color::Colors as AlacrittyColors, - vte::ansi::{Color, NamedColor, Rgb}, + vte::ansi::{Color, NamedColor, Rgb as AlacrittyRgb}, }; use std::collections::HashMap; -use crate::Screen; +use crate::{Rgb, Screen}; pub(crate) struct Colors { colors: AlacrittyColors, @@ -13,7 +13,7 @@ pub(crate) struct Colors { impl Colors { pub fn to_rgb(&self, color: Color) -> Rgb { - match color { + let AlacrittyRgb { r, g, b } = match color { Color::Named(named_color) => { self.colors[named_color as usize].expect("all colors should be defined") } @@ -21,7 +21,9 @@ impl Colors { self.colors[usize::from(idx)].expect("all colors should be defined") } Color::Spec(rgb) => rgb, - } + }; + + Rgb { r, g, b } } } @@ -81,7 +83,7 @@ fn fill_cube(colors: &mut AlacrittyColors) { for g in 0..6 { for b in 0..6 { // Override colors 16..232 with the config (if present). - colors[index] = Some(Rgb { + colors[index] = Some(AlacrittyRgb { r: if r == 0 { 0 } else { r * 40 + 55 }, g: if g == 0 { 0 } else { g * 40 + 55 }, b: if b == 0 { 0 } else { b * 40 + 55 }, @@ -101,7 +103,7 @@ fn fill_gray_ramp(colors: &mut AlacrittyColors) { // Build colors. for i in 0..24 { let value = i * 10 + 8; - colors[index] = Some(Rgb { + colors[index] = Some(AlacrittyRgb { r: value, g: value, b: value, @@ -112,7 +114,7 @@ fn fill_gray_ramp(colors: &mut AlacrittyColors) { debug_assert!(index == 256); } -pub(crate) fn most_common_color(colors: &Colors, screen: &Screen) -> Rgb { +pub(crate) fn most_common_color(screen: &Screen) -> Rgb { use std::hash::{Hash, Hasher}; #[derive(PartialEq, Eq, Copy, Clone)] @@ -156,7 +158,7 @@ pub(crate) fn most_common_color(colors: &Colors, screen: &Screen) -> Rgb { let cell = &screen.cells[usize::from(idx)]; let bg = &cell.bg; - *counts.entry(Rgb_(colors.to_rgb(*bg))).or_insert(0) += 1; + *counts.entry(Rgb_(*bg)).or_insert(0) += 1; } counts diff --git a/termsnap-lib/src/lib.rs b/termsnap-lib/src/lib.rs index f45f9fa..9ef0eb6 100644 --- a/termsnap-lib/src/lib.rs +++ b/termsnap-lib/src/lib.rs @@ -1,17 +1,13 @@ #[forbid(unsafe_code)] - use std::fmt::{Display, Write}; use alacritty_terminal::{ term::{ - cell::{Cell, Flags}, + cell::{Cell as AlacrittyCell, Flags}, test::TermSize, Config, Term as AlacrittyTerm, }, - vte::{ - self, - ansi::{Processor, Rgb}, - }, + vte::{self, ansi::Processor}, }; mod colors; @@ -20,6 +16,46 @@ use colors::Colors; const FONT_ASPECT_RATIO: f32 = 0.6; const FONT_ASCENT: f32 = 0.750; +/// A color in the sRGB color space. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Rgb { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Display for Rgb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "#{:02x?}{:02x?}{:02x}", self.r, self.g, self.b) + } +} + +/// The unicode character and style of a single cell in the terminal grid. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Cell { + pub c: char, + pub fg: Rgb, + pub bg: Rgb, + pub bold: bool, + pub italic: bool, + pub underline: bool, + pub strikethrough: bool, +} + +impl Cell { + fn from_alacritty_cell(colors: &Colors, cell: &AlacrittyCell) -> Self { + Cell { + c: cell.c, + fg: colors.to_rgb(cell.fg), + bg: colors.to_rgb(cell.bg), + bold: cell.flags.intersects(Flags::BOLD), + italic: cell.flags.intersects(Flags::ITALIC), + underline: cell.flags.intersects(Flags::ALL_UNDERLINES), + strikethrough: cell.flags.intersects(Flags::STRIKEOUT), + } + } +} + #[derive(PartialEq)] struct TextStyle { fg: Rgb, @@ -31,14 +67,22 @@ struct TextStyle { impl TextStyle { /// private conversion from alacritty Cell to Style - fn from_cell(colors: &Colors, cell: &Cell) -> Self { - TextStyle { - fg: colors.to_rgb(cell.fg), + fn from_cell(cell: &Cell) -> Self { + let Cell { + fg, + bold, + italic, + underline, + strikethrough, + .. + } = *cell; - bold: cell.flags.intersects(Flags::BOLD), - italic: cell.flags.intersects(Flags::ITALIC), - underline: cell.flags.intersects(Flags::ALL_UNDERLINES), - strikethrough: cell.flags.intersects(Flags::STRIKEOUT), + TextStyle { + fg, + bold, + italic, + underline, + strikethrough, } } } @@ -218,9 +262,7 @@ impl Screen { "#, )?; - let colors = Colors::default(); - - let main_bg = colors::most_common_color(&colors, self.screen); + let main_bg = colors::most_common_color(self.screen); fmt_rect( f, 0, @@ -241,7 +283,7 @@ impl Screen { } let cell = &cells[idx]; - let bg = colors.to_rgb(cell.bg); + let bg = cell.bg; if bg == main_bg { continue; @@ -253,7 +295,7 @@ impl Screen { for x1 in x0 + 1..*columns { let idx = self.screen.idx(y0, x1); let cell = &cells[idx]; - if colors.to_rgb(cell.bg) == bg { + if cell.bg == bg { end_x = x1; } else { break; @@ -265,7 +307,7 @@ impl Screen { for x1 in x0 + 1..*columns { let idx = self.screen.idx(y1, x1); let cell = &cells[idx]; - if colors.to_rgb(cell.bg) != bg { + if cell.bg != bg { all = false; break; } @@ -294,13 +336,13 @@ impl Screen { for y in 0..*lines { let idx = self.screen.idx(y, 0); let cell = &cells[idx]; - let mut style = TextStyle::from_cell(&colors, cell); + let mut style = TextStyle::from_cell(cell); let mut start_x = 0; for x in 0..*columns { let idx = self.screen.idx(y, x); let cell = &cells[idx]; - let style_ = TextStyle::from_cell(&colors, cell); + let style_ = TextStyle::from_cell(cell); if style_ != style { if !text_line.is_empty() { @@ -356,6 +398,17 @@ impl Screen { pub fn columns(&self) -> u16 { self.columns } + + /// An iterator over all cells in the terminal grid. This iterates over all columns in the + /// first line from left to right, then the second line, etc. + pub fn cells(&self) -> impl Iterator { + self.cells.iter() + } + + /// Get the cell at the terminal grid position specified by `line` and `column`. + pub fn get(&self, line: u16, column: u16) -> Option<&Cell> { + self.cells.get(self.idx(line, column)) + } } /// A sink for responses sent by the [terminal emulator](Term). The terminal emulator sends @@ -444,6 +497,9 @@ impl Term { /// Get a snapshot of the current terminal screen. pub fn current_screen(&self) -> Screen { + // ideally users can define their own colors + let colors = Colors::default(); + Screen { lines: self.lines, columns: self.columns, @@ -451,7 +507,7 @@ impl Term { .term .grid() .display_iter() - .map(|point_cell| point_cell.cell.clone()) + .map(|point_cell| Cell::from_alacritty_cell(&colors, point_cell.cell)) .collect(), } }