use crossterm::{
queue,
style::{
Attribute, Color as CrosstermColor, SetAttribute, SetBackgroundColor, SetForegroundColor,
},
};
use std::io::Write;
use super::super::{cell::Modifier, diff, Buffer, Cell};
use super::types::Terminal;
use crate::style::Color;
use crate::utils::unicode::char_width;
use crate::Result;
impl<W: Write> Terminal<W> {
pub(crate) fn draw_changes(
&mut self,
changes: Vec<diff::Change>,
buffer: &Buffer,
) -> Result<()> {
let mut state = super::types::RenderState::default();
for change in changes {
if !change.cell.is_continuation() {
let hyperlink_url = change
.cell
.hyperlink_id
.and_then(|id| buffer.get_hyperlink(id));
let escape_sequence = change
.cell
.sequence_id
.and_then(|id| buffer.get_sequence(id));
self.draw_cell_stateful(
change.x,
change.y,
&change.cell,
hyperlink_url,
escape_sequence,
&mut state,
)?;
}
self.current.set(change.x, change.y, change.cell);
}
if state.hyperlink_id.is_some() {
self.write_hyperlink_end()?;
}
if state.fg.is_some() || state.bg.is_some() || !state.modifier.is_empty() {
queue!(self.writer, SetAttribute(Attribute::Reset))?;
}
self.writer.flush()?;
Ok(())
}
pub(crate) fn draw_cell_stateful(
&mut self,
x: u16,
y: u16,
cell: &Cell,
hyperlink_url: Option<&str>,
escape_sequence: Option<&str>,
state: &mut super::types::RenderState,
) -> Result<()> {
use crossterm::cursor::MoveTo;
use crossterm::style::Print;
if state.cursor != Some((x, y)) {
queue!(self.writer, MoveTo(x, y))?;
}
if let Some(seq) = escape_sequence {
if state.hyperlink_id.is_some() {
self.write_hyperlink_end()?;
state.hyperlink_id = None;
}
if state.fg.is_some() || state.bg.is_some() || !state.modifier.is_empty() {
queue!(self.writer, SetAttribute(Attribute::Reset))?;
state.fg = None;
state.bg = None;
state.modifier = Modifier::empty();
}
write!(self.writer, "{}", seq)?;
state.cursor = None;
return Ok(());
}
let new_hyperlink_id = cell.hyperlink_id;
if new_hyperlink_id != state.hyperlink_id {
if state.hyperlink_id.is_some() {
self.write_hyperlink_end()?;
}
if let Some(url) = hyperlink_url {
self.write_hyperlink_start(url)?;
}
state.hyperlink_id = new_hyperlink_id;
}
if cell.fg != state.fg {
if let Some(fg) = cell.fg {
queue!(self.writer, SetForegroundColor(to_crossterm_color(fg)))?;
} else if state.fg.is_some() {
queue!(self.writer, SetForegroundColor(CrosstermColor::Reset))?;
}
state.fg = cell.fg;
}
if cell.bg != state.bg {
if let Some(bg) = cell.bg {
queue!(self.writer, SetBackgroundColor(to_crossterm_color(bg)))?;
} else if state.bg.is_some() {
queue!(self.writer, SetBackgroundColor(CrosstermColor::Reset))?;
}
state.bg = cell.bg;
}
if cell.modifier != state.modifier {
if !state.modifier.is_empty() && cell.modifier != state.modifier {
queue!(self.writer, SetAttribute(Attribute::Reset))?;
if let Some(fg) = cell.fg {
queue!(self.writer, SetForegroundColor(to_crossterm_color(fg)))?;
}
if let Some(bg) = cell.bg {
queue!(self.writer, SetBackgroundColor(to_crossterm_color(bg)))?;
}
}
if cell.modifier.contains(Modifier::BOLD) {
queue!(self.writer, SetAttribute(Attribute::Bold))?;
}
if cell.modifier.contains(Modifier::ITALIC) {
queue!(self.writer, SetAttribute(Attribute::Italic))?;
}
if cell.modifier.contains(Modifier::UNDERLINE) {
queue!(self.writer, SetAttribute(Attribute::Underlined))?;
}
if cell.modifier.contains(Modifier::DIM) {
queue!(self.writer, SetAttribute(Attribute::Dim))?;
}
if cell.modifier.contains(Modifier::CROSSED_OUT) {
queue!(self.writer, SetAttribute(Attribute::CrossedOut))?;
}
if cell.modifier.contains(Modifier::REVERSE) {
queue!(self.writer, SetAttribute(Attribute::Reverse))?;
}
state.modifier = cell.modifier;
}
queue!(self.writer, Print(cell.symbol))?;
let width = char_width(cell.symbol) as u16;
state.cursor = Some((x.saturating_add(width), y));
Ok(())
}
pub(crate) fn write_hyperlink_start(&mut self, url: &str) -> Result<()> {
write!(self.writer, "\x1b]8;;{}\x1b\\", url)?;
Ok(())
}
pub(crate) fn write_hyperlink_end(&mut self) -> Result<()> {
write!(self.writer, "\x1b]8;;\x1b\\")?;
Ok(())
}
}
fn to_crossterm_color(color: Color) -> CrosstermColor {
CrosstermColor::Rgb {
r: color.r,
g: color.g,
b: color.b,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Color;
#[test]
fn test_to_crossterm_color_rgb() {
let color = Color {
r: 255,
g: 128,
b: 0,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 255);
assert_eq!(g, 128);
assert_eq!(b, 0);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_black() {
let color = Color {
r: 0,
g: 0,
b: 0,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 0);
assert_eq!(g, 0);
assert_eq!(b, 0);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_white() {
let color = Color {
r: 255,
g: 255,
b: 255,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 255);
assert_eq!(g, 255);
assert_eq!(b, 255);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_gray() {
let color = Color {
r: 128,
g: 128,
b: 128,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 128);
assert_eq!(g, 128);
assert_eq!(b, 128);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_red() {
let color = Color {
r: 255,
g: 0,
b: 0,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 255);
assert_eq!(g, 0);
assert_eq!(b, 0);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_green() {
let color = Color {
r: 0,
g: 255,
b: 0,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 0);
assert_eq!(g, 255);
assert_eq!(b, 0);
}
_ => panic!("Expected Rgb color"),
}
}
#[test]
fn test_to_crossterm_color_blue() {
let color = Color {
r: 0,
g: 0,
b: 255,
a: 255,
};
let crossterm_color = to_crossterm_color(color);
match crossterm_color {
CrosstermColor::Rgb { r, g, b } => {
assert_eq!(r, 0);
assert_eq!(g, 0);
assert_eq!(b, 255);
}
_ => panic!("Expected Rgb color"),
}
}
}