From 5578109c6387123b96e8c24b0ce51ad81b301a55 Mon Sep 17 00:00:00 2001 From: Elio Date: Mon, 25 Mar 2024 05:59:35 -0700 Subject: [PATCH 1/7] Add background colors and theme support --- src/display/inline.rs | 6 +- src/display/side_by_side.rs | 47 +++------------ src/display/style.rs | 117 +++++++++++++++++++++++------------- src/main.rs | 1 + src/options.rs | 7 +++ src/theme.rs | 104 ++++++++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 86 deletions(-) create mode 100644 src/theme.rs diff --git a/src/display/inline.rs b/src/display/inline.rs index dc34248d5e..58bf453d5a 100644 --- a/src/display/inline.rs +++ b/src/display/inline.rs @@ -27,17 +27,15 @@ pub(crate) fn print( apply_colors( lhs_src, Side::Left, - display_options.syntax_highlight, + display_options, file_format, - display_options.background_color, lhs_positions, ), apply_colors( rhs_src, Side::Right, - display_options.syntax_highlight, + display_options, file_format, - display_options.background_color, rhs_positions, ), ) diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index 0b32e1fa6d..1561c4b3aa 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -15,7 +15,7 @@ use crate::{ display::hunks::{matched_lines_indexes_for_hunk, Hunk}, display::style::{ self, apply_colors, apply_line_number_color, color_positions, novel_style, replace_tabs, - split_and_apply, BackgroundColor, + split_and_apply, }, hash::DftHashMap, lines::format_line_num, @@ -237,8 +237,7 @@ pub(crate) fn lines_with_novel( /// Calculate positions of highlights on both sides. This includes /// both syntax highlighting and added/removed content highlighting. fn highlight_positions( - background: BackgroundColor, - syntax_highlight: bool, + display_options: &DisplayOptions, file_format: &FileFormat, lhs_mps: &[MatchedPos], rhs_mps: &[MatchedPos], @@ -246,13 +245,7 @@ fn highlight_positions( DftHashMap>, DftHashMap>, ) { - let lhs_positions = color_positions( - Side::Left, - background, - syntax_highlight, - file_format, - lhs_mps, - ); + let lhs_positions = color_positions(Side::Left, display_options, file_format, lhs_mps); // Preallocate the hashmap assuming the average line will have 2 items on it. let mut lhs_styles: DftHashMap> = DftHashMap::default(); @@ -261,13 +254,7 @@ fn highlight_positions( styles.push((span, style)); } - let rhs_positions = color_positions( - Side::Right, - background, - syntax_highlight, - file_format, - rhs_mps, - ); + let rhs_positions = color_positions(Side::Right, display_options, file_format, rhs_mps); let mut rhs_styles: DftHashMap> = DftHashMap::default(); for (span, style) in rhs_positions { @@ -315,22 +302,8 @@ pub(crate) fn print( ) { let (lhs_colored_lines, rhs_colored_lines) = if display_options.use_color { ( - apply_colors( - lhs_src, - Side::Left, - display_options.syntax_highlight, - file_format, - display_options.background_color, - lhs_mps, - ), - apply_colors( - rhs_src, - Side::Right, - display_options.syntax_highlight, - file_format, - display_options.background_color, - rhs_mps, - ), + apply_colors(lhs_src, Side::Left, display_options, file_format, lhs_mps), + apply_colors(rhs_src, Side::Right, display_options, file_format, rhs_mps), ) } else { ( @@ -381,13 +354,7 @@ pub(crate) fn print( // TODO: this is largely duplicating the `apply_colors` logic. let (lhs_highlights, rhs_highlights) = if display_options.use_color { - highlight_positions( - display_options.background_color, - display_options.syntax_highlight, - file_format, - lhs_mps, - rhs_mps, - ) + highlight_positions(display_options, file_format, lhs_mps, rhs_mps) } else { (DftHashMap::default(), DftHashMap::default()) }; diff --git a/src/display/style.rs b/src/display/style.rs index e402af14d7..5f7cf0fd4f 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -297,6 +297,7 @@ fn style_lines(lines: &[&str], styles: &[(SingleLineSpan, Style)]) -> Vec Style { if background.is_dark() { match side { @@ -313,8 +314,7 @@ pub(crate) fn novel_style(style: Style, side: Side, background: BackgroundColor) pub(crate) fn color_positions( side: Side, - background: BackgroundColor, - syntax_highlight: bool, + display_options: &DisplayOptions, file_format: &FileFormat, positions: &[MatchedPos], ) -> Vec<(SingleLineSpan, Style)> { @@ -323,52 +323,80 @@ pub(crate) fn color_positions( let mut style = Style::new(); match pos.kind { MatchKind::UnchangedToken { highlight, .. } | MatchKind::Ignored { highlight } => { - if syntax_highlight { - if let TokenKind::Atom(atom_kind) = highlight { - match atom_kind { + if display_options.syntax_highlight { + match highlight { + TokenKind::Delimiter => { + style = *display_options.theme.style("delimiter", false, side) + } + TokenKind::Atom(atom_kind) => match atom_kind { + AtomKind::Normal => { + style = *display_options.theme.style("normal", false, side); + } AtomKind::String(StringKind::StringLiteral) => { - style = if background.is_dark() { - style.bright_magenta() - } else { - style.magenta() - }; + style = *display_options.theme.style("string_literal", false, side); + } + AtomKind::String(StringKind::Text) => { + style = *display_options.theme.style("text", false, side); + } + AtomKind::Type => { + style = *display_options.theme.style("type", false, side); } - AtomKind::String(StringKind::Text) => {} AtomKind::Comment => { - style = style.italic(); - style = if background.is_dark() { - style.bright_blue() - } else { - style.blue() - }; + style = *display_options.theme.style("comment", false, side); } - AtomKind::Keyword | AtomKind::Type => { - style = style.bold(); + AtomKind::Keyword => { + style = *display_options.theme.style("keyword", false, side); } - AtomKind::TreeSitterError => style = style.purple(), - AtomKind::Normal => {} - } + AtomKind::TreeSitterError => { + style = + *display_options + .theme + .style("tree_sitter_error", false, side); + } + }, } } } MatchKind::Novel { highlight, .. } => { - style = novel_style(style, side, background); - if syntax_highlight - && matches!( - highlight, - TokenKind::Delimiter - | TokenKind::Atom(AtomKind::Keyword) - | TokenKind::Atom(AtomKind::Type) - ) - { - style = style.bold(); - } - if matches!(highlight, TokenKind::Atom(AtomKind::Comment)) { - style = style.italic(); + style = *display_options.theme.default_style(true, side); + + // TODO: determine if we want to only show these when syntax highlighting is on + if display_options.syntax_highlight { + match highlight { + TokenKind::Delimiter => { + style = *display_options.theme.style("delimiter", true, side) + } + TokenKind::Atom(AtomKind::Normal) => { + style = *display_options.theme.style("normal", true, side) + } + TokenKind::Atom(AtomKind::String(StringKind::StringLiteral)) => { + style = *display_options.theme.style("string_literal", true, side) + } + TokenKind::Atom(AtomKind::String(StringKind::Text)) => { + style = *display_options.theme.style("text", true, side) + } + TokenKind::Atom(AtomKind::Type) => { + style = *display_options.theme.style("type", true, side) + } + TokenKind::Atom(AtomKind::Comment) => { + style = *display_options.theme.style("comment", true, side) + } + TokenKind::Atom(AtomKind::Keyword) => { + style = *display_options.theme.style("keyword", true, side) + } + TokenKind::Atom(AtomKind::TreeSitterError) => { + style = *display_options.theme.style("tree_sitter_error", true, side) + } + } } + + // if matches!(highlight, TokenKind::Atom(AtomKind::Comment)) { + // style = *display_options.theme.style("comment", true, side); + // } } MatchKind::NovelWord { highlight } => { - style = novel_style(style, side, background).bold(); + // style = novel_style(style, side, display_options.background_color).bold(); + style = *display_options.theme.default_style(true, side); // Underline novel words inside comments in code, but // don't apply it to every single line in plaintext. @@ -376,13 +404,19 @@ pub(crate) fn color_positions( style = style.underline(); } - if syntax_highlight && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) { + if display_options.syntax_highlight + && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) + { style = style.italic(); } } MatchKind::NovelLinePart { highlight, .. } => { - style = novel_style(style, side, background); - if syntax_highlight && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) { + // style = novel_style_bg(style, side, display_options.background_color); + style = *display_options.theme.default_style(true, side); + + if display_options.syntax_highlight + && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) + { style = style.italic(); } } @@ -395,12 +429,11 @@ pub(crate) fn color_positions( pub(crate) fn apply_colors( s: &str, side: Side, - syntax_highlight: bool, + display_options: &DisplayOptions, file_format: &FileFormat, - background: BackgroundColor, positions: &[MatchedPos], ) -> Vec { - let styles = color_positions(side, background, syntax_highlight, file_format, positions); + let styles = color_positions(side, display_options, file_format, positions); let lines = s.lines().collect::>(); style_lines(&lines, &styles) } diff --git a/src/main.rs b/src/main.rs index 9f58470d54..5fc212c5d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,7 @@ mod lines; mod options; mod parse; mod summary; +mod theme; mod version; mod words; diff --git a/src/options.rs b/src/options.rs index eefc1c93d9..9c43d829b9 100644 --- a/src/options.rs +++ b/src/options.rs @@ -16,6 +16,7 @@ use crate::{ display::style::BackgroundColor, exit_codes::EXIT_BAD_ARGUMENTS, parse::guess_language::{language_override_from_name, LanguageOverride}, + theme::Theme, version::VERSION, }; @@ -39,6 +40,7 @@ pub(crate) enum ColorOutput { #[derive(Debug, Clone)] pub(crate) struct DisplayOptions { + pub(crate) theme: Theme, pub(crate) background_color: BackgroundColor, pub(crate) use_color: bool, pub(crate) display_mode: DisplayMode, @@ -53,6 +55,7 @@ pub(crate) struct DisplayOptions { impl Default for DisplayOptions { fn default() -> Self { Self { + theme: Theme::default(), background_color: BackgroundColor::Dark, use_color: false, display_mode: DisplayMode::SideBySide, @@ -741,6 +744,8 @@ pub(crate) fn parse_args() -> Mode { } } + // create theme styles + // TODO: document these different ways of calling difftastic. let (display_path, lhs_path, rhs_path, lhs_permissions, rhs_permissions, renamed) = match &args [..] @@ -793,6 +798,7 @@ pub(crate) fn parse_args() -> Mode { } [path] => { let display_options = DisplayOptions { + theme: Theme::default(), background_color, use_color, print_unchanged, @@ -830,6 +836,7 @@ pub(crate) fn parse_args() -> Mode { }; let display_options = DisplayOptions { + theme: Theme::default(), background_color, use_color, print_unchanged, diff --git a/src/theme.rs b/src/theme.rs new file mode 100644 index 0000000000..d5f1a916f7 --- /dev/null +++ b/src/theme.rs @@ -0,0 +1,104 @@ +use crate::constants::Side; +use owo_colors::{Style, XtermColors}; +use std::collections::HashMap; + +type StyleMap = HashMap; + +/// Theme objects to allow customization of colors and styles +/// +/// Themes represent a dark or light theme individually. We decide whether to +/// use light or dark at theme load time. +#[derive(Debug, Clone)] +pub(crate) struct Theme { + pub(crate) base_style: Style, + pub(crate) novel_style_left: Style, + pub(crate) novel_style_right: Style, + pub(crate) styles: StyleMap, +} + +impl Theme { + pub(crate) fn default_style(&self, novel: bool, side: Side) -> &Style { + match novel { + true => match side { + Side::Left => &self.novel_style_left, + Side::Right => &self.novel_style_right, + }, + false => &self.base_style, + } + } + + /// try to match __ + /// try to match _ + /// try to match + /// if none of these match, fallback to defaults + pub(crate) fn style(&self, name: &str, novel: bool, side: Side) -> &Style { + if let Some(full_style) = self.styles.get(&format!( + "{}{}_{}", + name, + match novel { + true => "_novel", + false => "", + }, + match side { + Side::Left => "left", + Side::Right => "right", + } + )) { + return full_style; + } + + if let Some(side_less_style) = self.styles.get(&format!( + "{}{}", + name, + match novel { + true => "_novel", + false => "", + }, + )) { + return side_less_style; + } + + if let Some(bare_style) = self.styles.get(name) { + return bare_style; + } + + self.default_style(novel, side) + } +} + +fn insert_style_combos(styles: &mut StyleMap, name: &str, style: Style) { + styles.insert( + format!("{}_novel_left", name), + style.on_color(XtermColors::from(224)), + ); + styles.insert( + format!("{}_novel_right", name), + style.on_color(XtermColors::from(194)), + ); + styles.insert(name.to_owned(), style); +} + +impl Default for Theme { + fn default() -> Self { + let novel_style_left = Style::new().on_color(XtermColors::from(224)); + let novel_style_right = Style::new().on_color(XtermColors::from(194)); + + let mut styles = HashMap::new(); + insert_style_combos(&mut styles, "normal", Style::new()); + insert_style_combos(&mut styles, "string_literal", Style::new().bright_blue()); + insert_style_combos(&mut styles, "text", Style::new().bright_blue()); + insert_style_combos(&mut styles, "comment", Style::new().dimmed().italic()); + insert_style_combos(&mut styles, "keyword", Style::new().magenta()); + insert_style_combos(&mut styles, "type", Style::new().blue()); + insert_style_combos(&mut styles, "delimiter", Style::new().magenta()); + + styles.insert("tree_sitter_error".to_string(), Style::new().purple()); + + Theme { + base_style: Style::new(), + novel_style_left, + novel_style_right, + styles, + } + } +} From 8d1dc0cabc10d6b6821d34828d62e47711fd9d91 Mon Sep 17 00:00:00 2001 From: Elio Date: Mon, 25 Mar 2024 06:30:08 -0700 Subject: [PATCH 2/7] Remove novel_style function NOTE: There appears to be a bug in testing. Lines in the left hand side are being colored green in some cases. --- src/display/side_by_side.rs | 5 ++--- src/display/style.rs | 31 ++----------------------------- src/theme.rs | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index 1561c4b3aa..7bc09edfe8 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -14,8 +14,7 @@ use crate::{ display::context::all_matched_lines_filled, display::hunks::{matched_lines_indexes_for_hunk, Hunk}, display::style::{ - self, apply_colors, apply_line_number_color, color_positions, novel_style, replace_tabs, - split_and_apply, + self, apply_colors, apply_line_number_color, color_positions, replace_tabs, split_and_apply, }, hash::DftHashMap, lines::format_line_num, @@ -92,7 +91,7 @@ fn display_single_column( let mut style = Style::new(); if display_options.use_color { - style = novel_style(Style::new(), side, display_options.background_color); + style = *display_options.theme.lineno_style(false, side); } for (i, line) in src_lines.iter().enumerate() { diff --git a/src/display/style.rs b/src/display/style.rs index 5f7cf0fd4f..c2659036d9 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -297,21 +297,6 @@ fn style_lines(lines: &[&str], styles: &[(SingleLineSpan, Style)]) -> Vec Style { - if background.is_dark() { - match side { - Side::Left => style.bright_red(), - Side::Right => style.bright_green(), - } - } else { - match side { - Side::Left => style.red(), - Side::Right => style.green(), - } - } -} - pub(crate) fn color_positions( side: Side, display_options: &DisplayOptions, @@ -484,22 +469,10 @@ pub(crate) fn apply_line_number_color( display_options: &DisplayOptions, ) -> String { if display_options.use_color { - let mut style = Style::new(); - // The goal here is to choose a style for line numbers that is // visually distinct from content. - if is_novel { - // For changed lines, show the line number as red/green - // and bold. This works well for syntactic diffs, where - // most content is not bold. - style = novel_style(style, side, display_options.background_color).bold(); - } else { - // For unchanged lines, dim the line numbers so it's - // clearly separate from the content. - style = style.dimmed() - } - - s.style(style).to_string() + s.style(*display_options.theme.lineno_style(is_novel, side)) + .to_string() } else { s.to_string() } diff --git a/src/theme.rs b/src/theme.rs index d5f1a916f7..2b05dd1318 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -13,6 +13,9 @@ pub(crate) struct Theme { pub(crate) base_style: Style, pub(crate) novel_style_left: Style, pub(crate) novel_style_right: Style, + pub(crate) lineno_style_base: Style, + pub(crate) lineno_style_left: Style, + pub(crate) lineno_style_right: Style, pub(crate) styles: StyleMap, } @@ -27,6 +30,16 @@ impl Theme { } } + pub(crate) fn lineno_style(&self, novel: bool, side: Side) -> &Style { + match novel { + true => match side { + Side::Left => &self.lineno_style_left, + Side::Right => &self.lineno_style_right, + }, + false => &self.lineno_style_base, + } + } + /// try to match __ /// try to match _ /// try to match @@ -98,6 +111,14 @@ impl Default for Theme { base_style: Style::new(), novel_style_left, novel_style_right, + // For unchanged lines, dim the line numbers so it's + // clearly separate from the content. + lineno_style_base: Style::new().dimmed(), + // For changed lines, show the line number as red/green + // and bold. This works well for syntactic diffs, where + // most content is not bold. + lineno_style_left: Style::new().red().bold(), + lineno_style_right: Style::new().green().bold(), styles, } } From fe52be57e9acd8ead2f5a6897e133b3e746fd3a5 Mon Sep 17 00:00:00 2001 From: Elio Date: Thu, 28 Mar 2024 18:03:41 -0700 Subject: [PATCH 3/7] Add style_by_type and line background coloring NOTE: This version does not compile due to a conflict between some remaining owo_colors types and some yansi types. --- Cargo.lock | 9 +- Cargo.toml | 1 + src/display/side_by_side.rs | 43 +++++++- src/display/style.rs | 138 +++++-------------------- src/theme.rs | 200 ++++++++++++++++++++++++++++++------ 5 files changed, 245 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3b8baad51..bd626c05b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,7 @@ dependencies = [ "unicode-width", "version_check", "wu-diff", + "yansi 1.0.1", ] [[package]] @@ -739,7 +740,7 @@ dependencies = [ "ctor", "diff", "output_vt100", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -1238,6 +1239,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index 2876008558..262ff73bbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ humansize = "2.1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" line-numbers = "0.3.0" +yansi = "1.0.1" [dev-dependencies] # assert_cmd 2.0.6 requires rust 1.60 diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index 7bc09edfe8..095640210b 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -8,6 +8,7 @@ use std::{ use line_numbers::LineNumber; use line_numbers::SingleLineSpan; use owo_colors::{OwoColorize, Style}; +use yansi::Paint; use crate::{ constants::Side, @@ -432,6 +433,15 @@ pub(crate) fn print( match rhs_line_num { Some(rhs_line_num) => { let rhs_line = &rhs_colored_lines[rhs_line_num.as_usize()]; + + let rhs_line = if rhs_lines_with_novel.contains(rhs_line_num) { + // TODO: replace the argument to on_fixed with the color from the theme + Paint::on_fixed(&rhs_line, 194).wrap().to_string() + } else { + rhs_line.to_string() + }; + + // TODO: add line bg color here if same_lines { print!("{}{}", display_rhs_line_num, rhs_line); } else { @@ -452,6 +462,15 @@ pub(crate) fn print( match lhs_line_num { Some(lhs_line_num) => { let lhs_line = &lhs_colored_lines[lhs_line_num.as_usize()]; + + let lhs_line = if lhs_lines_with_novel.contains(lhs_line_num) { + // TODO: replace the argument to on_fixed with the color from the theme + Paint::on_fixed(&lhs_line, 224).wrap().to_string() + } else { + lhs_line.to_string() + }; + + // TODO: add line bg color here if same_lines { print!("{}{}", display_lhs_line_num, lhs_line); } else { @@ -484,7 +503,7 @@ pub(crate) fn print( rhs_highlights.get(rhs_line_num).unwrap_or(&vec![]), Side::Right, ), - None => vec!["".into()], + None => vec![" ".repeat(source_dims.content_width)], }; for (i, (lhs_line, rhs_line)) in zip_pad_shorter(&lhs_line, &rhs_line) @@ -493,7 +512,8 @@ pub(crate) fn print( { let lhs_line = lhs_line.unwrap_or_else(|| " ".repeat(source_dims.content_width)); - let rhs_line = rhs_line.unwrap_or_else(|| "".into()); + let rhs_line = + rhs_line.unwrap_or_else(|| " ".repeat(source_dims.content_width)); let lhs_num: String = if i == 0 { display_lhs_line_num.clone() } else { @@ -535,7 +555,24 @@ pub(crate) fn print( s }; - println!("{}{}{}{}{}", lhs_num, lhs_line, SPACER, rhs_num, rhs_line); + println!( + "{}{}{}{}{}", + lhs_num, + match lhs_line_num { + Some(line_num) if lhs_lines_with_novel.contains(line_num) => + // TODO: replace the argument to on_fixed with the color from the theme + Paint::on_fixed(&lhs_line, 224).wrap().to_string(), + _ => lhs_line, + }, + SPACER, + rhs_num, + match rhs_line_num { + Some(line_num) if rhs_lines_with_novel.contains(line_num) => + // TODO: replace the argument to on_fixed with the color from the theme + Paint::on_fixed(&rhs_line, 194).wrap().to_string(), + _ => rhs_line, + } + ); } } diff --git a/src/display/style.rs b/src/display/style.rs index c2659036d9..4ade782a6d 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -150,9 +150,9 @@ pub(crate) fn split_and_apply( let mut parts = String::with_capacity(part.len() + pad); parts.push_str(&part); - if matches!(side, Side::Left) { - parts.push_str(&" ".repeat(pad)); - } + // if matches!(side, Side::Left) { + parts.push_str(&" ".repeat(pad)); + // } parts }) .collect(); @@ -215,8 +215,10 @@ pub(crate) fn split_and_apply( res.push_str(span_s); } - if matches!(side, Side::Left) { - res.push_str(&" ".repeat(pad)); + match side { + Side::Left => res.push_str(&" ".repeat(pad)), + // TODO: replace the magic number 8 with the actual value, probably lineno length plus a gutter + Side::Right => res.push_str(&" ".repeat(pad - 8)), } styled_parts.push(res); @@ -228,7 +230,7 @@ pub(crate) fn split_and_apply( /// Return a copy of `line` with styles applied to all the spans /// specified. -fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)]) -> String { +fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &Style) -> String { let line_bytes = byte_len(line); let mut styled_line = String::with_capacity(line.len()); let mut i = 0; @@ -244,7 +246,9 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)]) -> String { // Unstyled text before the next span. if i < start_col { - styled_line.push_str(substring_by_byte(line, i, start_col)); + let span_s = substring_by_byte(line, i, start_col); + // styled_line.push_str(span_s); + styled_line.push_str(&span_s.style(*default_style).to_string()); } // Apply style to the substring in this span. @@ -256,7 +260,8 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)]) -> String { // Unstyled text after the last span. if i < line_bytes { let span_s = substring_by_byte(line, i, line_bytes); - styled_line.push_str(span_s); + // styled_line.push_str(span_s); + styled_line.push_str(&span_s.style(*default_style).to_string()); } styled_line } @@ -280,7 +285,11 @@ fn group_by_line( /// styled strings, including trailing newlines. /// /// Tolerant against lines in `s` being shorter than the spans. -fn style_lines(lines: &[&str], styles: &[(SingleLineSpan, Style)]) -> Vec { +fn style_lines( + lines: &[&str], + styles: &[(SingleLineSpan, Style)], + default_style: &Style, +) -> Vec { let mut ranges_by_line = group_by_line(styles); let mut styled_lines = Vec::with_capacity(lines.len()); @@ -290,7 +299,8 @@ fn style_lines(lines: &[&str], styles: &[(SingleLineSpan, Style)]) -> Vec(&(i as u32).into()) .unwrap_or_default(); - styled_line.push_str(&apply_line(line, &ranges)); + styled_line.push_str(&apply_line(line, &ranges, default_style)); + // TODO: apply background color to line here or in the apply_line function styled_line.push('\n'); styled_lines.push(styled_line); } @@ -305,107 +315,7 @@ pub(crate) fn color_positions( ) -> Vec<(SingleLineSpan, Style)> { let mut styles = vec![]; for pos in positions { - let mut style = Style::new(); - match pos.kind { - MatchKind::UnchangedToken { highlight, .. } | MatchKind::Ignored { highlight } => { - if display_options.syntax_highlight { - match highlight { - TokenKind::Delimiter => { - style = *display_options.theme.style("delimiter", false, side) - } - TokenKind::Atom(atom_kind) => match atom_kind { - AtomKind::Normal => { - style = *display_options.theme.style("normal", false, side); - } - AtomKind::String(StringKind::StringLiteral) => { - style = *display_options.theme.style("string_literal", false, side); - } - AtomKind::String(StringKind::Text) => { - style = *display_options.theme.style("text", false, side); - } - AtomKind::Type => { - style = *display_options.theme.style("type", false, side); - } - AtomKind::Comment => { - style = *display_options.theme.style("comment", false, side); - } - AtomKind::Keyword => { - style = *display_options.theme.style("keyword", false, side); - } - AtomKind::TreeSitterError => { - style = - *display_options - .theme - .style("tree_sitter_error", false, side); - } - }, - } - } - } - MatchKind::Novel { highlight, .. } => { - style = *display_options.theme.default_style(true, side); - - // TODO: determine if we want to only show these when syntax highlighting is on - if display_options.syntax_highlight { - match highlight { - TokenKind::Delimiter => { - style = *display_options.theme.style("delimiter", true, side) - } - TokenKind::Atom(AtomKind::Normal) => { - style = *display_options.theme.style("normal", true, side) - } - TokenKind::Atom(AtomKind::String(StringKind::StringLiteral)) => { - style = *display_options.theme.style("string_literal", true, side) - } - TokenKind::Atom(AtomKind::String(StringKind::Text)) => { - style = *display_options.theme.style("text", true, side) - } - TokenKind::Atom(AtomKind::Type) => { - style = *display_options.theme.style("type", true, side) - } - TokenKind::Atom(AtomKind::Comment) => { - style = *display_options.theme.style("comment", true, side) - } - TokenKind::Atom(AtomKind::Keyword) => { - style = *display_options.theme.style("keyword", true, side) - } - TokenKind::Atom(AtomKind::TreeSitterError) => { - style = *display_options.theme.style("tree_sitter_error", true, side) - } - } - } - - // if matches!(highlight, TokenKind::Atom(AtomKind::Comment)) { - // style = *display_options.theme.style("comment", true, side); - // } - } - MatchKind::NovelWord { highlight } => { - // style = novel_style(style, side, display_options.background_color).bold(); - style = *display_options.theme.default_style(true, side); - - // Underline novel words inside comments in code, but - // don't apply it to every single line in plaintext. - if matches!(file_format, FileFormat::SupportedLanguage(_)) { - style = style.underline(); - } - - if display_options.syntax_highlight - && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) - { - style = style.italic(); - } - } - MatchKind::NovelLinePart { highlight, .. } => { - // style = novel_style_bg(style, side, display_options.background_color); - style = *display_options.theme.default_style(true, side); - - if display_options.syntax_highlight - && matches!(highlight, TokenKind::Atom(AtomKind::Comment)) - { - style = style.italic(); - } - } - }; + let style = *display_options.theme.style_by_type(&pos.kind, side); styles.push((pos.pos, style)); } styles @@ -420,7 +330,11 @@ pub(crate) fn apply_colors( ) -> Vec { let styles = color_positions(side, display_options, file_format, positions); let lines = s.lines().collect::>(); - style_lines(&lines, &styles) + style_lines( + &lines, + &styles, + display_options.theme.default_style(true, side), + ) } fn apply_header_color( diff --git a/src/theme.rs b/src/theme.rs index 2b05dd1318..e07e300944 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,5 +1,9 @@ -use crate::constants::Side; -use owo_colors::{Style, XtermColors}; +use yansi::{Color, Style}; + +use crate::{ + constants::Side, + parse::syntax::{AtomKind, MatchKind, StringKind, TokenKind}, +}; use std::collections::HashMap; type StyleMap = HashMap; @@ -60,60 +64,196 @@ impl Theme { return full_style; } - if let Some(side_less_style) = self.styles.get(&format!( - "{}{}", - name, - match novel { - true => "_novel", - false => "", - }, - )) { + // if let Some(side_less_style) = self.styles.get(&format!( + // "{}{}", + // name, + // match novel { + // true => "_novel", + // false => "", + // }, + // )) { + // return side_less_style; + // } + + if let Some(bare_style) = self.styles.get(name) { + return bare_style; + } else { + &self.base_style + } + + // self.default_style(novel, side) + } + + pub(crate) fn style_by_type(&self, kind: &MatchKind, side: Side) -> &Style { + // TODO: take syntax coloring preference into account as well as file type + // // Underline novel words inside comments in code, but + // // don't apply it to every single line in plaintext. + // if matches!(file_format, FileFormat::SupportedLanguage(_)) { + // style = style.underline(); + // } + + // translate the status to strings + let (status, token) = match kind { + MatchKind::UnchangedToken { highlight, .. } => ("unchanged", highlight), + MatchKind::Ignored { highlight } => ("ignored", highlight), + MatchKind::Novel { highlight } => ("novel", highlight), + MatchKind::NovelLinePart { highlight, .. } => ("novel_line_part", highlight), + MatchKind::NovelWord { highlight } => ("novel_word", highlight), + }; + + // translate the token kinds to strings + let token_kind = match token { + TokenKind::Delimiter => "delimiter", + TokenKind::Atom(AtomKind::Normal) => "normal", + TokenKind::Atom(AtomKind::String(StringKind::StringLiteral)) => "string_literal", + TokenKind::Atom(AtomKind::String(StringKind::Text)) => "text", + TokenKind::Atom(AtomKind::Type) => "type", + TokenKind::Atom(AtomKind::Comment) => "comment", + TokenKind::Atom(AtomKind::Keyword) => "keyword", + TokenKind::Atom(AtomKind::TreeSitterError) => "tree_sitter_error", + }; + + // translate the side to its corresponding name + let side_name = match side { + Side::Left => "left", + Side::Right => "right", + }; + + // attempt to return the most specific style first + if let Some(full_style) = self + .styles + .get(&format!("{}_{}_{}", token_kind, status, side_name)) + { + return full_style; + } + + // fallback to novel if no more specific status is available + if matches!( + kind, + MatchKind::Novel { .. } | MatchKind::NovelLinePart { .. } | MatchKind::NovelWord { .. } + ) { + if let Some(full_style) = self + .styles + .get(&format!("{}_novel_{}", token_kind, side_name)) + { + return full_style; + } + } + + // fallback to non-novel with side name + if matches!( + kind, + MatchKind::UnchangedToken { .. } | MatchKind::Ignored { .. } + ) { + if let Some(full_style) = self.styles.get(&format!("{}_{}", token_kind, side_name)) { + return full_style; + } + } + + // fallback to side-less style + if let Some(side_less_style) = self.styles.get(&format!("{}_{}", token_kind, status,)) { return side_less_style; } - if let Some(bare_style) = self.styles.get(name) { + // fallback to the bare style for that token kind or return the base style + if let Some(bare_style) = self.styles.get(token_kind) { return bare_style; + } else { + &self.base_style } - self.default_style(novel, side) + // TODO: do we want to return the default style or is the base above enough? + // self.default_style(novel, side) } } -fn insert_style_combos(styles: &mut StyleMap, name: &str, style: Style) { +fn insert_style_combos( + styles: &mut StyleMap, + name: &str, + style: Style, + lhs_novel_color: yansi::Color, + rhs_novel_color: yansi::Color, +) { styles.insert( format!("{}_novel_left", name), - style.on_color(XtermColors::from(224)), + style.clone().bg(lhs_novel_color), ); styles.insert( format!("{}_novel_right", name), - style.on_color(XtermColors::from(194)), + style.clone().bg(rhs_novel_color), ); styles.insert(name.to_owned(), style); } impl Default for Theme { + /// Setup the base color theme + /// + /// We'll allow setting up user provided, custom color themes later. fn default() -> Self { - let novel_style_left = Style::new().on_color(XtermColors::from(224)); - let novel_style_right = Style::new().on_color(XtermColors::from(194)); - + let lhs_novel_color = Color::BrightRed; + let rhs_novel_color = Color::BrightGreen; + // let novel_style_left = Style::new().on_color(XtermColors::from(224)); + // let novel_style_right = Style::new().on_color(XtermColors::from(194)); let mut styles = HashMap::new(); - insert_style_combos(&mut styles, "normal", Style::new()); - insert_style_combos(&mut styles, "string_literal", Style::new().bright_blue()); - insert_style_combos(&mut styles, "text", Style::new().bright_blue()); - insert_style_combos(&mut styles, "comment", Style::new().dimmed().italic()); - insert_style_combos(&mut styles, "keyword", Style::new().magenta()); - insert_style_combos(&mut styles, "type", Style::new().blue()); - insert_style_combos(&mut styles, "delimiter", Style::new().magenta()); + insert_style_combos( + &mut styles, + "normal", + yansi::Style::default(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "string_literal", + Style::new().bright_blue(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "text", + Style::new().bright_blue(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "comment", + Style::new().italic(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "keyword", + Style::new().magenta(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "type", + Style::new().blue(), + lhs_novel_color, + rhs_novel_color, + ); + insert_style_combos( + &mut styles, + "delimiter", + Style::new().magenta(), + lhs_novel_color, + rhs_novel_color, + ); - styles.insert("tree_sitter_error".to_string(), Style::new().purple()); + styles.insert("tree_sitter_error".to_string(), Style::new().magenta()); Theme { - base_style: Style::new(), - novel_style_left, - novel_style_right, + base_style: yansi::Style::default(), + novel_style_left: Style::new().bg(lhs_novel_color), + novel_style_right: Style::new().bg(rhs_novel_color), // For unchanged lines, dim the line numbers so it's // clearly separate from the content. - lineno_style_base: Style::new().dimmed(), + lineno_style_base: Style::new().dim(), // For changed lines, show the line number as red/green // and bold. This works well for syntactic diffs, where // most content is not bold. From a8171bb3d3c8b8a5f6a62d1bc556140c8622d322 Mon Sep 17 00:00:00 2001 From: Elio Date: Thu, 28 Mar 2024 18:36:55 -0700 Subject: [PATCH 4/7] Remove owo-colors and update method calls NOTE: Colors need adjusting. Sub-line differences seem to have the same background color as the rest of the line. --- Cargo.lock | 7 ------- Cargo.toml | 1 - src/display/side_by_side.rs | 13 ++++++------- src/display/style.rs | 26 +++++++++++--------------- src/main.rs | 2 +- src/theme.rs | 3 +++ 6 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd626c05b6..d502710161 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,7 +274,6 @@ dependencies = [ "line-numbers", "log", "mimalloc", - "owo-colors", "predicates", "pretty_assertions", "pretty_env_logger", @@ -662,12 +661,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "parking_lot" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 262ff73bbe..da751421dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ radix-heap = "0.4.2" # ignore 0.4.19 requires scoped_threads, which was added in rust 1.63. ignore = ">= 0.4, < 0.4.19" const_format = "0.2.22" -owo-colors = "3.5.0" wu-diff = "0.1.2" rayon = "1.7.0" diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index 095640210b..904fce403b 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -7,8 +7,7 @@ use std::{ use line_numbers::LineNumber; use line_numbers::SingleLineSpan; -use owo_colors::{OwoColorize, Style}; -use yansi::Paint; +use yansi::{Paint, Style}; use crate::{ constants::Side, @@ -52,7 +51,7 @@ fn format_missing_line_num( let mut style = Style::new(); if use_color { - style = style.dimmed(); + style = style.dim(); } let num_digits = prev_num.display().len(); @@ -61,7 +60,7 @@ fn format_missing_line_num( (if after_end { " " } else { "." }).repeat(num_digits), width = column_width - 1 ) - .style(style) + .paint(style) .to_string() } @@ -99,7 +98,7 @@ fn display_single_column( let mut formatted_line = String::with_capacity(line.len()); formatted_line.push_str( &format_line_num_padded((i as u32).into(), column_width) - .style(style) + .paint(style) .to_string(), ); formatted_line.push_str(line); @@ -618,7 +617,7 @@ mod tests { assert_eq!( format_missing_line_num(0.into(), &source_dims, Side::Left, true), - ". ".dimmed().to_string() + ". ".dim().to_string() ); assert_eq!( format_missing_line_num(0.into(), &source_dims, Side::Left, false), @@ -638,7 +637,7 @@ mod tests { assert_eq!( format_missing_line_num(1.into(), &source_dims, Side::Left, true), - " ".dimmed().to_string() + " ".dim().to_string() ); assert_eq!( format_missing_line_num(1.into(), &source_dims, Side::Left, false), diff --git a/src/display/style.rs b/src/display/style.rs index 4ade782a6d..02f665e7c7 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -4,17 +4,13 @@ use std::cmp::{max, min}; use line_numbers::LineNumber; use line_numbers::SingleLineSpan; -use owo_colors::{OwoColorize, Style}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use yansi::Paint; +use yansi::Style; -use crate::parse::syntax::StringKind; use crate::{ - constants::Side, - hash::DftHashMap, - lines::byte_len, - options::DisplayOptions, - parse::syntax::{AtomKind, MatchKind, MatchedPos, TokenKind}, - summary::FileFormat, + constants::Side, hash::DftHashMap, lines::byte_len, options::DisplayOptions, + parse::syntax::MatchedPos, summary::FileFormat, }; #[derive(Clone, Copy, Debug)] @@ -193,7 +189,7 @@ pub(crate) fn split_and_apply( min(byte_len(line_part), end_col - part_start), tab_width, ); - res.push_str(&span_s.style(*style).to_string()); + res.push_str(&span_s.paint(*style).to_string()); } prev_style_end = end_col; } @@ -248,12 +244,12 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &St if i < start_col { let span_s = substring_by_byte(line, i, start_col); // styled_line.push_str(span_s); - styled_line.push_str(&span_s.style(*default_style).to_string()); + styled_line.push_str(&span_s.paint(*default_style).to_string()); } // Apply style to the substring in this span. let span_s = substring_by_byte(line, start_col, min(line_bytes, end_col)); - styled_line.push_str(&span_s.style(*style).to_string()); + styled_line.push_str(&span_s.paint(*style).to_string()); i = end_col; } @@ -261,7 +257,7 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &St if i < line_bytes { let span_s = substring_by_byte(line, i, line_bytes); // styled_line.push_str(span_s); - styled_line.push_str(&span_s.style(*default_style).to_string()); + styled_line.push_str(&span_s.paint(*default_style).to_string()); } styled_line } @@ -385,7 +381,7 @@ pub(crate) fn apply_line_number_color( if display_options.use_color { // The goal here is to choose a style for line numbers that is // visually distinct from content. - s.style(*display_options.theme.lineno_style(is_novel, side)) + s.paint(*display_options.theme.lineno_style(is_novel, side)) .to_string() } else { s.to_string() @@ -415,14 +411,14 @@ pub(crate) fn header( let mut trailer = format!(" --- {}{}", divider, file_format); if display_options.use_color { - trailer = trailer.dimmed().to_string(); + trailer = trailer.dim().to_string(); } match extra_info { Some(extra_info) if hunk_num == 1 => { let mut extra_info = extra_info.clone(); if display_options.use_color { - extra_info = extra_info.dimmed().to_string(); + extra_info = extra_info.dim().to_string(); } format!("{}{}\n{}", display_path_pretty, trailer, extra_info) diff --git a/src/main.rs b/src/main.rs index 5fc212c5d5..afd1454099 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ use log::info; use mimalloc::MiMalloc; use options::FilePermissions; use options::USAGE; +use yansi::Paint; use crate::conflicts::apply_conflict_markers; use crate::conflicts::START_LHS_MARKER; @@ -76,7 +77,6 @@ use std::path::Path; use std::{env, thread}; use humansize::{format_size, BINARY}; -use owo_colors::OwoColorize; use rayon::prelude::*; use strum::IntoEnumIterator; use typed_arena::Arena; diff --git a/src/theme.rs b/src/theme.rs index e07e300944..4e92f726da 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -195,6 +195,8 @@ impl Default for Theme { // let novel_style_left = Style::new().on_color(XtermColors::from(224)); // let novel_style_right = Style::new().on_color(XtermColors::from(194)); let mut styles = HashMap::new(); + + // insert standard stiles insert_style_combos( &mut styles, "normal", @@ -245,6 +247,7 @@ impl Default for Theme { rhs_novel_color, ); + // insert custom styles styles.insert("tree_sitter_error".to_string(), Style::new().magenta()); Theme { From 35c56dc428d105e392b104efd5b1dc5bdce78fcb Mon Sep 17 00:00:00 2001 From: Elio Date: Thu, 28 Mar 2024 19:23:38 -0700 Subject: [PATCH 5/7] Update default color theme Use color theme for default background colors. --- src/display/side_by_side.rs | 18 +++++++--- src/display/style.rs | 6 ++-- src/theme.rs | 71 ++++++++++++++----------------------- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index 904fce403b..c9f99d8883 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -435,7 +435,9 @@ pub(crate) fn print( let rhs_line = if rhs_lines_with_novel.contains(rhs_line_num) { // TODO: replace the argument to on_fixed with the color from the theme - Paint::on_fixed(&rhs_line, 194).wrap().to_string() + Paint::bg(&rhs_line, display_options.theme.novel_bg_right) + .wrap() + .to_string() } else { rhs_line.to_string() }; @@ -464,7 +466,9 @@ pub(crate) fn print( let lhs_line = if lhs_lines_with_novel.contains(lhs_line_num) { // TODO: replace the argument to on_fixed with the color from the theme - Paint::on_fixed(&lhs_line, 224).wrap().to_string() + Paint::bg(&lhs_line, display_options.theme.novel_bg_left) + .wrap() + .to_string() } else { lhs_line.to_string() }; @@ -512,7 +516,7 @@ pub(crate) fn print( let lhs_line = lhs_line.unwrap_or_else(|| " ".repeat(source_dims.content_width)); let rhs_line = - rhs_line.unwrap_or_else(|| " ".repeat(source_dims.content_width)); + rhs_line.unwrap_or_else(|| " ".repeat(source_dims.content_width - 8)); let lhs_num: String = if i == 0 { display_lhs_line_num.clone() } else { @@ -560,7 +564,9 @@ pub(crate) fn print( match lhs_line_num { Some(line_num) if lhs_lines_with_novel.contains(line_num) => // TODO: replace the argument to on_fixed with the color from the theme - Paint::on_fixed(&lhs_line, 224).wrap().to_string(), + Paint::bg(&lhs_line, display_options.theme.novel_bg_left) + .wrap() + .to_string(), _ => lhs_line, }, SPACER, @@ -568,7 +574,9 @@ pub(crate) fn print( match rhs_line_num { Some(line_num) if rhs_lines_with_novel.contains(line_num) => // TODO: replace the argument to on_fixed with the color from the theme - Paint::on_fixed(&rhs_line, 194).wrap().to_string(), + Paint::bg(&rhs_line, display_options.theme.novel_bg_right) + .wrap() + .to_string(), _ => rhs_line, } ); diff --git a/src/display/style.rs b/src/display/style.rs index 02f665e7c7..06e1630d78 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -146,9 +146,9 @@ pub(crate) fn split_and_apply( let mut parts = String::with_capacity(part.len() + pad); parts.push_str(&part); - // if matches!(side, Side::Left) { - parts.push_str(&" ".repeat(pad)); - // } + if matches!(side, Side::Left) { + parts.push_str(&" ".repeat(pad)); + } parts }) .collect(); diff --git a/src/theme.rs b/src/theme.rs index 4e92f726da..9adbc8371a 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,4 +1,4 @@ -use yansi::{Color, Style}; +use yansi::{Color, Paint, Style}; use crate::{ constants::Side, @@ -14,6 +14,10 @@ type StyleMap = HashMap; /// use light or dark at theme load time. #[derive(Debug, Clone)] pub(crate) struct Theme { + /// the default background color for deleted lines + pub(crate) novel_bg_left: Color, + /// the default background color for added lines + pub(crate) novel_bg_right: Color, pub(crate) base_style: Style, pub(crate) novel_style_left: Style, pub(crate) novel_style_right: Style, @@ -44,46 +48,15 @@ impl Theme { } } + /// This returns a style from the defined theme for a given kind, novelty, and side. + /// + /// Alternately, it attempts to fallback to an appropriate color so users can sparsely + /// define themes. + /// /// try to match __ /// try to match _ /// try to match /// if none of these match, fallback to defaults - pub(crate) fn style(&self, name: &str, novel: bool, side: Side) -> &Style { - if let Some(full_style) = self.styles.get(&format!( - "{}{}_{}", - name, - match novel { - true => "_novel", - false => "", - }, - match side { - Side::Left => "left", - Side::Right => "right", - } - )) { - return full_style; - } - - // if let Some(side_less_style) = self.styles.get(&format!( - // "{}{}", - // name, - // match novel { - // true => "_novel", - // false => "", - // }, - // )) { - // return side_less_style; - // } - - if let Some(bare_style) = self.styles.get(name) { - return bare_style; - } else { - &self.base_style - } - - // self.default_style(novel, side) - } - pub(crate) fn style_by_type(&self, kind: &MatchKind, side: Side) -> &Style { // TODO: take syntax coloring preference into account as well as file type // // Underline novel words inside comments in code, but @@ -190,13 +163,16 @@ impl Default for Theme { /// /// We'll allow setting up user provided, custom color themes later. fn default() -> Self { - let lhs_novel_color = Color::BrightRed; - let rhs_novel_color = Color::BrightGreen; - // let novel_style_left = Style::new().on_color(XtermColors::from(224)); - // let novel_style_right = Style::new().on_color(XtermColors::from(194)); + // default background colors + let novel_bg_left = Color::Fixed(224); + let novel_bg_right = Color::Fixed(194); + // default background highlight for sub-line differences + let lhs_novel_color = Color::Fixed(217); + let rhs_novel_color = Color::Fixed(157); + let mut styles = HashMap::new(); - // insert standard stiles + // insert standard styles insert_style_combos( &mut styles, "normal", @@ -221,14 +197,14 @@ impl Default for Theme { insert_style_combos( &mut styles, "comment", - Style::new().italic(), + Style::new().fixed(248).italic(), lhs_novel_color, rhs_novel_color, ); insert_style_combos( &mut styles, "keyword", - Style::new().magenta(), + Style::new().bright_blue(), lhs_novel_color, rhs_novel_color, ); @@ -242,15 +218,20 @@ impl Default for Theme { insert_style_combos( &mut styles, "delimiter", - Style::new().magenta(), + Style::new().yellow(), lhs_novel_color, rhs_novel_color, ); // insert custom styles styles.insert("tree_sitter_error".to_string(), Style::new().magenta()); + styles + .insert("comment".to_string(), Style::new().fixed(248)) + .italic(); Theme { + novel_bg_left, + novel_bg_right, base_style: yansi::Style::default(), novel_style_left: Style::new().bg(lhs_novel_color), novel_style_right: Style::new().bg(rhs_novel_color), From fcf6704193249a3c19301549460ab1cf2ee82475 Mon Sep 17 00:00:00 2001 From: Elio Date: Thu, 28 Mar 2024 19:40:48 -0700 Subject: [PATCH 6/7] Fix an excess blank line bug --- src/display/side_by_side.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/display/side_by_side.rs b/src/display/side_by_side.rs index c9f99d8883..ae464d972b 100644 --- a/src/display/side_by_side.rs +++ b/src/display/side_by_side.rs @@ -506,7 +506,8 @@ pub(crate) fn print( rhs_highlights.get(rhs_line_num).unwrap_or(&vec![]), Side::Right, ), - None => vec![" ".repeat(source_dims.content_width)], + // NOTE: do not pad the below string, it will cause excess blank lines + None => vec!["".into()], }; for (i, (lhs_line, rhs_line)) in zip_pad_shorter(&lhs_line, &rhs_line) From 8c2d42935bd38fb4ba5de01dbff79d40a28eb3a0 Mon Sep 17 00:00:00 2001 From: Elio Date: Thu, 28 Mar 2024 20:23:22 -0700 Subject: [PATCH 7/7] Fix inline style display --- src/display/inline.rs | 20 ++++++++++++++++++-- src/display/style.rs | 22 ++++++---------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/display/inline.rs b/src/display/inline.rs index 58bf453d5a..d0b793b6b1 100644 --- a/src/display/inline.rs +++ b/src/display/inline.rs @@ -1,5 +1,7 @@ //! Inline, or "unified" diff display. +use yansi::Paint; + use crate::{ constants::Side, display::context::{calculate_after_context, calculate_before_context, opposite_positions}, @@ -110,7 +112,14 @@ pub(crate) fn print( Side::Left, display_options, ), - lhs_colored_lines[lhs_line.as_usize()] + // wrap bg color here + // TODO: might need to extend lines so background color shows up on right side + Paint::bg( + &lhs_colored_lines[lhs_line.as_usize()], + display_options.theme.novel_bg_left + ) + .wrap() + .to_string() ); } } @@ -124,7 +133,14 @@ pub(crate) fn print( Side::Right, display_options, ), - rhs_colored_lines[rhs_line.as_usize()] + // wrap bg color here + // TODO: might need to extend lines so background color shows up on right side + Paint::bg( + &rhs_colored_lines[rhs_line.as_usize()], + display_options.theme.novel_bg_right + ) + .wrap() + .to_string() ); } } diff --git a/src/display/style.rs b/src/display/style.rs index 06e1630d78..40a5425394 100644 --- a/src/display/style.rs +++ b/src/display/style.rs @@ -226,7 +226,7 @@ pub(crate) fn split_and_apply( /// Return a copy of `line` with styles applied to all the spans /// specified. -fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &Style) -> String { +fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)]) -> String { let line_bytes = byte_len(line); let mut styled_line = String::with_capacity(line.len()); let mut i = 0; @@ -243,8 +243,7 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &St // Unstyled text before the next span. if i < start_col { let span_s = substring_by_byte(line, i, start_col); - // styled_line.push_str(span_s); - styled_line.push_str(&span_s.paint(*default_style).to_string()); + styled_line.push_str(span_s); } // Apply style to the substring in this span. @@ -256,8 +255,7 @@ fn apply_line(line: &str, styles: &[(SingleLineSpan, Style)], default_style: &St // Unstyled text after the last span. if i < line_bytes { let span_s = substring_by_byte(line, i, line_bytes); - // styled_line.push_str(span_s); - styled_line.push_str(&span_s.paint(*default_style).to_string()); + styled_line.push_str(span_s); } styled_line } @@ -281,11 +279,7 @@ fn group_by_line( /// styled strings, including trailing newlines. /// /// Tolerant against lines in `s` being shorter than the spans. -fn style_lines( - lines: &[&str], - styles: &[(SingleLineSpan, Style)], - default_style: &Style, -) -> Vec { +fn style_lines(lines: &[&str], styles: &[(SingleLineSpan, Style)]) -> Vec { let mut ranges_by_line = group_by_line(styles); let mut styled_lines = Vec::with_capacity(lines.len()); @@ -295,7 +289,7 @@ fn style_lines( .remove::(&(i as u32).into()) .unwrap_or_default(); - styled_line.push_str(&apply_line(line, &ranges, default_style)); + styled_line.push_str(&apply_line(line, &ranges)); // TODO: apply background color to line here or in the apply_line function styled_line.push('\n'); styled_lines.push(styled_line); @@ -326,11 +320,7 @@ pub(crate) fn apply_colors( ) -> Vec { let styles = color_positions(side, display_options, file_format, positions); let lines = s.lines().collect::>(); - style_lines( - &lines, - &styles, - display_options.theme.default_style(true, side), - ) + style_lines(&lines, &styles) } fn apply_header_color(