Fix colors with comprehensive tests

This commit is contained in:
Dheepak Krishnamurthy 2021-02-16 03:20:47 -07:00
parent 43670fad4d
commit 169ee6e6e5
2 changed files with 243 additions and 181 deletions

View file

@ -235,7 +235,7 @@ impl TTApp {
.year(self.calendar_year) .year(self.calendar_year)
.date_style(dates_with_styles) .date_style(dates_with_styles)
.months_per_row(self.config.uda_calendar_months_per_row); .months_per_row(self.config.uda_calendar_months_per_row);
c.title_background_color = self.config.uda_style_calendar_title.bg; c.title_background_color = self.config.uda_style_calendar_title.bg.unwrap_or(Color::Black);
f.render_widget(c, rects[0]); f.render_widget(c, rects[0]);
} }
@ -457,9 +457,7 @@ impl TTApp {
for (i, context) in contexts.iter().enumerate() { for (i, context) in contexts.iter().enumerate() {
let mut style = Style::default(); let mut style = Style::default();
if &self.contexts[i].active == "yes" { if &self.contexts[i].active == "yes" {
style = style style = self.config.uda_style_context_active;
.fg(self.config.uda_style_context_active.fg)
.bg(self.config.uda_style_context_active.bg)
} }
rows.push(Row::StyledData(context.iter(), style)); rows.push(Row::StyledData(context.iter(), style));
if i == self.context_table_state.selected().unwrap_or_default() { if i == self.context_table_state.selected().unwrap_or_default() {
@ -569,23 +567,31 @@ impl TTApp {
for tag_name in virtual_tag_names_in_precedence.iter().rev() { for tag_name in virtual_tag_names_in_precedence.iter().rev() {
if tag_name == "uda." || tag_name == "priority" { if tag_name == "uda." || tag_name == "priority" {
if let Some(p) = task.priority() { if let Some(p) = task.priority() {
let c = self let s = self
.config .config
.color .color
.get(&format!("color.uda.priority.{}", p)) .get(&format!("color.uda.priority.{}", p))
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
style = config::blend(style, c); style = style.patch(s);
}
} else if tag_name == "tag." {
if let Some(tags) = task.tags() {
for t in tags {
let color_tag_name = format!("color.tag.{}", t);
let s = self.config.color.get(&color_tag_name).cloned().unwrap_or_default();
style = style.patch(s);
}
} }
} else if tag_name == "project." { } else if tag_name == "project." {
if let Some(p) = task.project() { if let Some(p) = task.project() {
let c = self let s = self
.config .config
.color .color
.get(&format!("color.project.{}", p)) .get(&format!("color.project.{}", p))
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
style = config::blend(style, c); style = style.patch(s);
} }
} else if task } else if task
.tags() .tags()
@ -593,8 +599,8 @@ impl TTApp {
.contains(&tag_name.to_string().replace(".", "").to_uppercase()) .contains(&tag_name.to_string().replace(".", "").to_uppercase())
{ {
let color_tag_name = format!("color.{}", tag_name); let color_tag_name = format!("color.{}", tag_name);
let c = self.config.color.get(&color_tag_name).cloned().unwrap_or_default(); let s = self.config.color.get(&color_tag_name).cloned().unwrap_or_default();
style = config::blend(style, c); style = style.patch(s);
} }
} }
@ -1781,9 +1787,9 @@ mod tests {
let style = app.style_for_task(&task); let style = app.style_for_task(&task);
dbg!(style); dbg!(style);
assert!(style == Style::default().fg(Color::Green)); assert_eq!(style, Style::default().fg(Color::Indexed(2)));
let task = app.task_by_id(2).unwrap(); let task = app.task_by_id(11).unwrap();
dbg!(task.tags().unwrap()); dbg!(task.tags().unwrap());
let style = app.style_for_task(&task); let style = app.style_for_task(&task);
dbg!(style); dbg!(style);

View file

@ -4,78 +4,6 @@ use std::process::Command;
use std::str; use std::str;
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
pub fn blend(style: Style, c: TColor) -> Style {
let mut style = style;
if c.fg != Color::Reset {
style = style.fg(c.fg);
}
if c.bg != Color::Reset {
style = style.bg(c.bg);
}
for m in c.modifiers {
style = style.add_modifier(m);
}
style
}
#[derive(Debug, Clone)]
pub struct TColor {
pub fg: Color,
pub bg: Color,
pub modifiers: Vec<Modifier>,
}
impl Default for TColor {
fn default() -> Self {
TColor::default()
}
}
impl TColor {
pub fn default() -> Self {
Self {
fg: Color::Reset,
bg: Color::Reset,
modifiers: vec![],
}
}
pub fn new(fg: Color, bg: Color, modifiers: Vec<Modifier>) -> Self {
Self { fg, bg, modifiers }
}
pub fn upgrade(&mut self) {
if self.modifiers.contains(&Modifier::BOLD) {
self.fg = match self.fg {
Color::Red => Color::LightRed,
Color::Green => Color::LightGreen,
Color::Yellow => Color::LightYellow,
Color::Blue => Color::LightBlue,
Color::Magenta => Color::LightMagenta,
Color::Cyan => Color::LightCyan,
Color::White => Color::Indexed(7),
Color::Black => Color::Indexed(0),
x => x,
};
self.bg = match self.bg {
Color::Red => Color::LightRed,
Color::Green => Color::LightGreen,
Color::Yellow => Color::LightYellow,
Color::Blue => Color::LightBlue,
Color::Magenta => Color::LightMagenta,
Color::Cyan => Color::LightCyan,
Color::White => Color::Indexed(7),
Color::Black => Color::Indexed(0),
x => x,
};
}
}
}
trait TaskWarriorBool { trait TaskWarriorBool {
fn get_bool(&self) -> Option<bool>; fn get_bool(&self) -> Option<bool>;
} }
@ -107,7 +35,7 @@ impl TaskWarriorBool for str {
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub enabled: bool, pub enabled: bool,
pub color: HashMap<String, TColor>, pub color: HashMap<String, Style>,
pub filter: String, pub filter: String,
pub obfuscate: bool, pub obfuscate: bool,
pub print_empty_columns: bool, pub print_empty_columns: bool,
@ -121,8 +49,8 @@ pub struct Config {
pub uda_selection_dim: bool, pub uda_selection_dim: bool,
pub uda_selection_blink: bool, pub uda_selection_blink: bool,
pub uda_calendar_months_per_row: usize, pub uda_calendar_months_per_row: usize,
pub uda_style_context_active: TColor, pub uda_style_context_active: Style,
pub uda_style_calendar_title: TColor, pub uda_style_calendar_title: Style,
} }
impl Config { impl Config {
@ -144,10 +72,8 @@ impl Config {
uda_selection_dim: Self::get_uda_selection_dim(), uda_selection_dim: Self::get_uda_selection_dim(),
uda_selection_blink: Self::get_uda_selection_blink(), uda_selection_blink: Self::get_uda_selection_blink(),
uda_calendar_months_per_row: Self::get_uda_months_per_row(), uda_calendar_months_per_row: Self::get_uda_months_per_row(),
uda_style_calendar_title: Self::get_uda_style("calendar.title") uda_style_calendar_title: Self::get_uda_style("calendar.title").unwrap_or_default(),
.unwrap_or_else(|| TColor::new(Color::Reset, Color::Reset, vec![])), uda_style_context_active: Self::get_uda_style("context.active").unwrap_or_default(),
uda_style_context_active: Self::get_uda_style("context.active")
.unwrap_or_else(|| TColor::new(Color::Reset, Color::Reset, vec![])),
}) })
} }
@ -155,7 +81,7 @@ impl Config {
HashMap::new() HashMap::new()
} }
fn get_uda_style(config: &str) -> Option<TColor> { fn get_uda_style(config: &str) -> Option<Style> {
let c = format!("uda.taskwarrior-tui.style.{}", config); let c = format!("uda.taskwarrior-tui.style.{}", config);
let s = Self::get_config(&c); let s = Self::get_config(&c);
if s.is_empty() { if s.is_empty() {
@ -165,7 +91,7 @@ impl Config {
} }
} }
fn get_color_collection() -> Result<HashMap<String, TColor>, Box<dyn Error>> { fn get_color_collection() -> Result<HashMap<String, Style>, Box<dyn Error>> {
let mut color_collection = HashMap::new(); let mut color_collection = HashMap::new();
let output = Command::new("task").arg("rc.color=off").arg("show").output()?; let output = Command::new("task").arg("rc.color=off").arg("show").output()?;
@ -187,11 +113,11 @@ impl Config {
Ok(color_collection) Ok(color_collection)
} }
fn get_tcolor(line: &str) -> TColor { pub fn get_tcolor(line: &str) -> Style {
let (foreground, background) = line.split_at(line.to_lowercase().find("on ").unwrap_or_else(|| line.len())); let (foreground, background) = line.split_at(line.to_lowercase().find("on ").unwrap_or_else(|| line.len()));
let (mut foreground, mut background) = (String::from(foreground), String::from(background)); let (mut foreground, mut background) = (String::from(foreground), String::from(background));
background = background.replace("on ", ""); background = background.replace("on ", "");
let mut modifiers = vec![]; let mut modifiers = Modifier::empty();
if foreground.contains("bright") { if foreground.contains("bright") {
foreground = foreground.replace("bright ", ""); foreground = foreground.replace("bright ", "");
background = background.replace("bright ", ""); background = background.replace("bright ", "");
@ -200,130 +126,136 @@ impl Config {
foreground = foreground.replace("grey", "gray"); foreground = foreground.replace("grey", "gray");
background = background.replace("grey", "gray"); background = background.replace("grey", "gray");
if foreground.contains("underline") { if foreground.contains("underline") {
modifiers.push(Modifier::UNDERLINED); modifiers |= Modifier::UNDERLINED;
} }
let foreground = foreground.replace("underline ", ""); let foreground = foreground.replace("underline ", "");
// TODO: use bold, bright boolean flags
if foreground.contains("bold") { if foreground.contains("bold") {
modifiers.push(Modifier::BOLD); modifiers |= Modifier::BOLD;
} }
let foreground = foreground.replace("bold ", ""); // let foreground = foreground.replace("bold ", "");
if foreground.contains("inverse") { if foreground.contains("inverse") {
modifiers.push(Modifier::REVERSED); modifiers |= Modifier::REVERSED;
} }
let foreground = foreground.replace("inverse ", ""); let foreground = foreground.replace("inverse ", "");
TColor { let mut style = Style::default();
fg: Self::get_color_foreground(foreground.as_str(), Color::Reset), if let Some(fg) = Self::get_color_foreground(foreground.as_str()) {
bg: Self::get_color_background(background.as_str(), Color::Reset), style = style.fg(fg);
modifiers,
} }
if let Some(bg) = Self::get_color_background(background.as_str()) {
style = style.bg(bg);
}
style = style.add_modifier(modifiers);
style
} }
fn get_color_foreground(s: &str, default: Color) -> Color {
fn get_color_foreground(s: &str) -> Option<Color> {
let s = s.trim_start(); let s = s.trim_start();
let s = s.trim_end(); let s = s.trim_end();
if s.contains("color") { if s.contains("color") {
let s = s.trim_start_matches("bright ");
let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default(); let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default();
Color::Indexed(c) Some(Color::Indexed(c))
} else if s.contains("gray") { } else if s.contains("gray") {
let s = s.trim_start_matches("bright ");
let c = 232 + s.trim_start_matches("gray").parse::<u8>().unwrap_or_default(); let c = 232 + s.trim_start_matches("gray").parse::<u8>().unwrap_or_default();
Color::Indexed(c) Some(Color::Indexed(c))
} else if s.contains("rgb") { } else if s.contains("rgb") {
let s = s.trim_start_matches("bright ");
let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8;
let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8;
let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8;
let c = 16 + red * 36 + green * 6 + blue; let c = 16 + red * 36 + green * 6 + blue;
Color::Indexed(c) Some(Color::Indexed(c))
} else if s == "bright red" { } else if s == "bold black" {
Color::LightRed Some(Color::Indexed(8))
} else if s == "bright green" { } else if s == "bold red" {
Color::LightGreen Some(Color::Indexed(9))
} else if s == "bright yellow" { } else if s == "bold green" {
Color::LightYellow Some(Color::Indexed(10))
} else if s == "bright blue" { } else if s == "bold yellow" {
Color::LightBlue Some(Color::Indexed(11))
} else if s == "bright magenta" { } else if s == "bold blue" {
Color::LightMagenta Some(Color::Indexed(12))
} else if s == "bright cyan" { } else if s == "bold magenta" {
Color::LightCyan Some(Color::Indexed(13))
} else if s == "bright white" { } else if s == "bold cyan" {
Color::Indexed(7) Some(Color::Indexed(14))
} else if s == "bright black" { } else if s == "bold white" {
Color::Indexed(0) Some(Color::Indexed(15))
} else if s.contains("red") { } else if s == "black" {
Color::Red Some(Color::Indexed(0))
} else if s.contains("green") { } else if s == "red" {
Color::Green Some(Color::Indexed(1))
} else if s.contains("yellow") { } else if s == "green" {
Color::Yellow Some(Color::Indexed(2))
} else if s.contains("blue") { } else if s == "yellow" {
Color::Blue Some(Color::Indexed(3))
} else if s.contains("magenta") { } else if s == "blue" {
Color::Magenta Some(Color::Indexed(4))
} else if s.contains("cyan") { } else if s == "magenta" {
Color::Cyan Some(Color::Indexed(5))
} else if s.contains("white") { } else if s == "cyan" {
Color::White Some(Color::Indexed(6))
} else if s.contains("black") { } else if s == "white" {
Color::Black Some(Color::Indexed(7))
} else { } else {
default None
} }
} }
fn get_color_background(s: &str, default: Color) -> Color { fn get_color_background(s: &str) -> Option<Color> {
let s = s.trim_start(); let s = s.trim_start();
let s = s.trim_end(); let s = s.trim_end();
if s.contains("color") { if s.contains("bright color") {
let s = s.trim_start_matches("bright "); let s = s.trim_start_matches("bright ");
let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default(); let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default();
Color::Indexed(c.wrapping_shl(8)) Some(Color::Indexed(c.wrapping_shl(8)))
} else if s.contains("color") {
let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default();
Some(Color::Indexed(c))
} else if s.contains("gray") { } else if s.contains("gray") {
let s = s.trim_start_matches("bright "); let s = s.trim_start_matches("bright ");
let c = 232 + s.trim_start_matches("gray").parse::<u8>().unwrap_or_default(); let c = 232 + s.trim_start_matches("gray").parse::<u8>().unwrap_or_default();
Color::Indexed(c.wrapping_shl(8)) Some(Color::Indexed(c.wrapping_shl(8)))
} else if s.contains("rgb") { } else if s.contains("rgb") {
let s = s.trim_start_matches("bright "); let s = s.trim_start_matches("bright ");
let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8;
let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8;
let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8;
let c = 16 + red * 36 + green * 6 + blue; let c = 16 + red * 36 + green * 6 + blue;
Color::Indexed(c.wrapping_shl(8)) Some(Color::Indexed(c.wrapping_shl(8)))
} else if s == "bright red" {
Color::LightRed
} else if s == "bright green" {
Color::LightGreen
} else if s == "bright yellow" {
Color::LightYellow
} else if s == "bright blue" {
Color::LightBlue
} else if s == "bright magenta" {
Color::LightMagenta
} else if s == "bright cyan" {
Color::LightCyan
} else if s == "bright white" {
Color::White
} else if s == "bright black" { } else if s == "bright black" {
Color::Black Some(Color::Indexed(8))
} else if s.contains("red") { } else if s == "bright red" {
Color::Red Some(Color::Indexed(9))
} else if s.contains("green") { } else if s == "bright green" {
Color::Green Some(Color::Indexed(10))
} else if s.contains("yellow") { } else if s == "bright yellow" {
Color::Yellow Some(Color::Indexed(11))
} else if s.contains("blue") { } else if s == "bright blue" {
Color::Blue Some(Color::Indexed(12))
} else if s.contains("magenta") { } else if s == "bright magenta" {
Color::Magenta Some(Color::Indexed(13))
} else if s.contains("cyan") { } else if s == "bright cyan" {
Color::Cyan Some(Color::Indexed(14))
} else if s.contains("white") { } else if s == "bright white" {
Color::Indexed(7) Some(Color::Indexed(15))
} else if s.contains("black") { } else if s == "black" {
Color::Indexed(0) Some(Color::Indexed(0))
} else if s == "red" {
Some(Color::Indexed(1))
} else if s == "green" {
Some(Color::Indexed(2))
} else if s == "yellow" {
Some(Color::Indexed(3))
} else if s == "blue" {
Some(Color::Indexed(4))
} else if s == "magenta" {
Some(Color::Indexed(5))
} else if s == "cyan" {
Some(Color::Indexed(6))
} else if s == "white" {
Some(Color::Indexed(7))
} else { } else {
default None
} }
} }
@ -418,9 +350,133 @@ impl Config {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::config::Config; use super::*;
#[test] #[test]
fn test_colors() { fn test_colors() {
let tc = Config::default(); let tc = Config::default();
let c = Config::get_tcolor("red on blue");
assert_eq!(c.fg.unwrap(), Color::Indexed(1));
assert_eq!(c.bg.unwrap(), Color::Indexed(4));
let c = Config::get_tcolor("bold red");
assert_eq!(c.fg.unwrap(), Color::Indexed(9));
assert!(c.bg.is_none());
assert_eq!(c.add_modifier & Modifier::BOLD, Modifier::BOLD);
let c = Config::get_tcolor("white on red");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert_eq!(c.bg.unwrap(), Color::Indexed(1));
let c = Config::get_tcolor("blue");
assert_eq!(c.fg.unwrap(), Color::Indexed(4));
assert!(c.bg.is_none());
let c = Config::get_tcolor("black on bright green");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(10));
let c = Config::get_tcolor("magenta");
assert_eq!(c.fg.unwrap(), Color::Indexed(5));
assert!(c.bg.is_none());
let c = Config::get_tcolor("white on green");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert_eq!(c.bg.unwrap(), Color::Indexed(2));
let c = Config::get_tcolor("black on white");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(7));
let c = Config::get_tcolor("black on bright white");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(15));
let c = Config::get_tcolor("bold white");
assert_eq!(c.fg.unwrap(), Color::Indexed(15));
assert!(c.bg.is_none());
let c = Config::get_tcolor("white");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert!(c.bg.is_none());
let c = Config::get_tcolor("bold yellow");
assert_eq!(c.fg.unwrap(), Color::Indexed(11));
assert!(c.bg.is_none());
let c = Config::get_tcolor("green");
assert_eq!(c.fg.unwrap(), Color::Indexed(2));
assert!(c.bg.is_none());
let c = Config::get_tcolor("yellow");
assert_eq!(c.fg.unwrap(), Color::Indexed(3));
assert!(c.bg.is_none());
let c = Config::get_tcolor("red");
assert_eq!(c.fg.unwrap(), Color::Indexed(1));
assert!(c.bg.is_none());
let c = Config::get_tcolor("bold red");
assert_eq!(c.fg.unwrap(), Color::Indexed(9));
assert!(c.bg.is_none());
let c = Config::get_tcolor("on green");
assert!(c.fg.is_none());
assert_eq!(c.bg.unwrap(), Color::Indexed(2));
let c = Config::get_tcolor("on red");
assert!(c.fg.is_none());
assert_eq!(c.bg.unwrap(), Color::Indexed(1));
let c = Config::get_tcolor("on yellow");
assert!(c.fg.is_none());
assert_eq!(c.bg.unwrap(), Color::Indexed(3));
let c = Config::get_tcolor("black on red");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(1));
let c = Config::get_tcolor("black on yellow");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(3));
let c = Config::get_tcolor("black on green");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(2));
let c = Config::get_tcolor("white on black");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert_eq!(c.bg.unwrap(), Color::Indexed(0));
let c = Config::get_tcolor("black on green");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(2));
let c = Config::get_tcolor("white on red");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert_eq!(c.bg.unwrap(), Color::Indexed(1));
let c = Config::get_tcolor("bold white on red");
assert_eq!(c.fg.unwrap(), Color::Indexed(15));
assert_eq!(c.bg.unwrap(), Color::Indexed(1));
let c = Config::get_tcolor("black on bright yellow");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(11));
let c = Config::get_tcolor("black on bright red");
assert_eq!(c.fg.unwrap(), Color::Indexed(0));
assert_eq!(c.bg.unwrap(), Color::Indexed(9));
let c = Config::get_tcolor("bold white on bright blue");
assert_eq!(c.fg.unwrap(), Color::Indexed(15));
assert_eq!(c.bg.unwrap(), Color::Indexed(12));
let c = Config::get_tcolor("white on bright black");
assert_eq!(c.fg.unwrap(), Color::Indexed(7));
assert_eq!(c.bg.unwrap(), Color::Indexed(8));
let c = Config::get_tcolor("bold blue");
assert_eq!(c.fg.unwrap(), Color::Indexed(12));
assert!(c.bg.is_none());
} }
} }