From 3b4ed096d0abd72e076e144b42cfb4551daaff53 Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Sat, 7 Jan 2023 09:34:15 -0800 Subject: [PATCH] formatter: add support for setting background color --- CHANGELOG.md | 3 ++ src/formatter.rs | 76 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2007424..5cfaf1a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The `ui.relative-timestamps` option now also affects `jj op log`. +* Background colors are now supported. You can set e.g. + `color.error = { bg = "red" }` in your `~/.jjconfig.toml`. + ### Fixed bugs * When sharing the working copy with a Git repo, we used to forget to export diff --git a/src/formatter.rs b/src/formatter.rs index f69bebdfa..c5e06e2b1 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use std::{fmt, io}; use crossterm::queue; -use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor}; +use crossterm::style::{Attribute, Color, SetAttribute, SetBackgroundColor, SetForegroundColor}; use itertools::Itertools; // Lets the caller label strings and translates the labels to colors @@ -153,11 +153,13 @@ impl Formatter for PlainTextFormatter { #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Style { pub fg_color: Option, + pub bg_color: Option, } impl Style { fn merge(&mut self, other: &Style) { self.fg_color = other.fg_color.or(self.fg_color); + self.bg_color = other.bg_color.or(self.bg_color); } } @@ -236,10 +238,18 @@ impl ColorFormatter { } else if !is_bright(&new_style.fg_color) && is_bright(&self.current_style.fg_color) { queue!(self.output, SetAttribute(Attribute::Reset))?; } - queue!( - self.output, - SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset)) - )?; + if new_style.fg_color != self.current_style.fg_color { + queue!( + self.output, + SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset)) + )?; + } + if new_style.bg_color != self.current_style.bg_color { + queue!( + self.output, + SetBackgroundColor(new_style.bg_color.unwrap_or(Color::Reset)) + )?; + } self.current_style = new_style; } Ok(()) @@ -272,6 +282,7 @@ fn rules_from_config(config: &config::Config) -> HashMap, Style> { config::ValueKind::String(color_name) => { let style = Style { fg_color: color_for_name(&color_name), + bg_color: None, }; result.insert(labels, style); } @@ -282,6 +293,11 @@ fn rules_from_config(config: &config::Config) -> HashMap, Style> { style.fg_color = color_for_name(color_name); } } + if let Some(value) = style_table.get("bg") { + if let config::ValueKind::String(color_name) = &value.kind { + style.bg_color = color_for_name(color_name); + } + } result.insert(labels, style); } _ => {} @@ -435,10 +451,13 @@ mod tests { #[test] fn test_color_formatter_attributes() { - // Test that each attribute of the style can be set. + // Test that each attribute of the style can be set and that they can be + // combined in a single rule or by using multiple rules. let config = config_from_string( r#" colors.red_fg = { fg = "red" } + colors.blue_bg = { bg = "blue" } + colors.multiple = { fg = "green", bg = "yellow" } "#, ); let mut output: Vec = vec![]; @@ -446,7 +465,50 @@ mod tests { formatter.add_label("red_fg").unwrap(); formatter.write_str(" fg only ").unwrap(); formatter.remove_label().unwrap(); - insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" fg only "); + formatter.write_str("\n").unwrap(); + formatter.add_label("blue_bg").unwrap(); + formatter.write_str(" bg only ").unwrap(); + formatter.remove_label().unwrap(); + formatter.write_str("\n").unwrap(); + formatter.add_label("multiple").unwrap(); + formatter.write_str(" single rule ").unwrap(); + formatter.remove_label().unwrap(); + formatter.write_str("\n").unwrap(); + formatter.add_label("red_fg").unwrap(); + formatter.add_label("blue_bg").unwrap(); + formatter.write_str(" two rules ").unwrap(); + formatter.remove_label().unwrap(); + formatter.remove_label().unwrap(); + formatter.write_str("\n").unwrap(); + insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###" +  fg only  +  bg only  +  single rule  +  two rules  + "###); + } + + #[test] + fn test_color_formatter_bold_reset() { + // Test that we don't lose other attributes when we reset the bold attribute. + // TODO: Actually use bold instead of bright when we support that + let config = config_from_string( + r#" + colors.not_bold = { fg = "red", bg = "blue" } + colors.bold_font = { fg = "bright red" } + "#, + ); + let mut output: Vec = vec![]; + let mut formatter = ColorFormatter::for_config(&mut output, &config); + formatter.add_label("not_bold").unwrap(); + formatter.write_str(" not bold ").unwrap(); + formatter.add_label("bold_font").unwrap(); + formatter.write_str(" bold ").unwrap(); + formatter.remove_label().unwrap(); + formatter.write_str(" not bold again ").unwrap(); + formatter.remove_label().unwrap(); + // TODO: This loses the blue background when we reset the bold attribute. + insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" not bold  bold  not bold again "); } #[test]