use std::fmt::Write;
use owo_colors::OwoColorize;
use crate::DiffTheme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticColor {
Deleted,
DeletedHighlight,
Inserted,
InsertedHighlight,
Moved,
MovedHighlight,
DeletedKey,
InsertedKey,
Key,
DeletedStructure,
InsertedStructure,
Structure,
DeletedComment,
InsertedComment,
Comment,
DeletedString,
InsertedString,
String,
DeletedNumber,
InsertedNumber,
Number,
DeletedBoolean,
InsertedBoolean,
Boolean,
DeletedNull,
InsertedNull,
Null,
Whitespace,
Unchanged,
}
pub trait ColorBackend {
fn write_styled<W: Write>(
&self,
w: &mut W,
text: &str,
color: SemanticColor,
) -> std::fmt::Result;
fn write_prefix<W: Write>(
&self,
w: &mut W,
prefix: char,
color: SemanticColor,
) -> std::fmt::Result {
self.write_styled(w, &prefix.to_string(), color)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PlainBackend;
impl ColorBackend for PlainBackend {
fn write_styled<W: Write>(
&self,
w: &mut W,
text: &str,
_color: SemanticColor,
) -> std::fmt::Result {
write!(w, "{}", text)
}
}
#[derive(Debug, Clone)]
pub struct AnsiBackend {
theme: DiffTheme,
}
impl AnsiBackend {
pub const fn new(theme: DiffTheme) -> Self {
Self { theme }
}
pub fn with_default_theme() -> Self {
Self::new(DiffTheme::default())
}
}
impl Default for AnsiBackend {
fn default() -> Self {
Self::with_default_theme()
}
}
impl ColorBackend for AnsiBackend {
fn write_styled<W: Write>(
&self,
w: &mut W,
text: &str,
color: SemanticColor,
) -> std::fmt::Result {
let (fg, bg) = match color {
SemanticColor::Deleted => {
(self.theme.deleted, self.theme.desaturated_deleted_line_bg())
}
SemanticColor::DeletedHighlight => (
self.theme.deleted,
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::Inserted => (
self.theme.inserted,
self.theme.desaturated_inserted_line_bg(),
),
SemanticColor::InsertedHighlight => (
self.theme.inserted,
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Moved => (self.theme.moved, self.theme.desaturated_moved_line_bg()),
SemanticColor::MovedHighlight => (
self.theme.moved,
self.theme.desaturated_moved_highlight_bg(),
),
SemanticColor::DeletedKey => (
self.theme.deleted_highlight_key(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedKey => (
self.theme.inserted_highlight_key(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Key => (self.theme.key, None),
SemanticColor::DeletedStructure => (
self.theme.deleted_structure(),
self.theme.desaturated_deleted_line_bg(),
),
SemanticColor::InsertedStructure => (
self.theme.inserted_structure(),
self.theme.desaturated_inserted_line_bg(),
),
SemanticColor::Structure => (self.theme.structure, None),
SemanticColor::DeletedComment => (
self.theme.deleted_highlight_comment(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedComment => (
self.theme.inserted_highlight_comment(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Comment => (self.theme.comment, None),
SemanticColor::DeletedString => (
self.theme.deleted_highlight_string(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedString => (
self.theme.inserted_highlight_string(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::String => (self.theme.string, None),
SemanticColor::DeletedNumber => (
self.theme.deleted_highlight_number(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedNumber => (
self.theme.inserted_highlight_number(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Number => (self.theme.number, None),
SemanticColor::DeletedBoolean => (
self.theme.deleted_highlight_boolean(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedBoolean => (
self.theme.inserted_highlight_boolean(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Boolean => (self.theme.boolean, None),
SemanticColor::DeletedNull => (
self.theme.deleted_highlight_null(),
self.theme.desaturated_deleted_highlight_bg(),
),
SemanticColor::InsertedNull => (
self.theme.inserted_highlight_null(),
self.theme.desaturated_inserted_highlight_bg(),
),
SemanticColor::Null => (self.theme.null, None),
SemanticColor::Whitespace => (self.theme.comment, None),
SemanticColor::Unchanged => (self.theme.unchanged, None),
};
if let Some(bg) = bg {
write!(w, "{}", text.color(fg).on_color(bg))
} else {
write!(w, "{}", text.color(fg))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plain_backend() {
let backend = PlainBackend;
let mut out = String::new();
backend
.write_styled(&mut out, "hello", SemanticColor::Deleted)
.unwrap();
assert_eq!(out, "hello");
out.clear();
backend
.write_styled(&mut out, "world", SemanticColor::Inserted)
.unwrap();
assert_eq!(out, "world");
}
#[test]
fn test_ansi_backend() {
let backend = AnsiBackend::default();
let mut out = String::new();
backend
.write_styled(&mut out, "deleted", SemanticColor::Deleted)
.unwrap();
assert!(out.contains("\x1b["));
assert!(out.contains("deleted"));
}
#[test]
fn test_prefix() {
let backend = PlainBackend;
let mut out = String::new();
backend
.write_prefix(&mut out, '-', SemanticColor::Deleted)
.unwrap();
assert_eq!(out, "-");
}
}