diff --git a/Cargo.lock b/Cargo.lock index 1cb417ef..69d64269 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,14 +13,6 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ansi-parser" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ansi_colours" version = "1.0.1" @@ -129,19 +121,6 @@ dependencies = [ "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bit-vec" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "1.1.0" @@ -280,11 +259,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "encode_unicode" version = "0.3.5" @@ -363,17 +337,14 @@ dependencies = [ name = "git-delta" version = "0.1.1" dependencies = [ - "ansi-parser 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_colours 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "box_drawing 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytelines 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "shell-words 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -425,14 +396,6 @@ dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "itoa" version = "0.4.4" @@ -971,7 +934,6 @@ dependencies = [ [metadata] "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -"checksum ansi-parser 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "761ac675f1638a6a49e26f6ac3a4067ca3fefa8029816ae4ef8d3fa3d06a5194" "checksum ansi_colours 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0f302a81afc6a7f4350c04f0ba7cfab529cc009bca3324b3fb5764e6add8b6" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" @@ -984,8 +946,6 @@ dependencies = [ "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" "checksum bindgen 0.50.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cb0e5a5f74b2bafe0b39379f616b5975e08bcaca4e779c078d5c31324147e9ba" -"checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -"checksum bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" "checksum box_drawing 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ea27d8d5fd867b17523bf6788b1175fa9867f34669d057e9adaf76e27bcea44b" @@ -1003,7 +963,6 @@ dependencies = [ "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" @@ -1019,7 +978,6 @@ dependencies = [ "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da" "checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" diff --git a/Cargo.toml b/Cargo.toml index 320a5108..913de855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,15 +17,12 @@ path = "src/main.rs" [dependencies] ansi_colours = "1.0.1" -ansi-parser = "0.6.5" ansi_term = "0.12.1" atty = "0.2.14" -bit-set = "0.5.2" box_drawing = "0.1.2" bytelines = "2.2.2" console = "0.11.2" dirs = "2.0" -itertools = "0.9.0" lazy_static = "1.4" regex = "1.2.1" shell-words = "1.0.0" diff --git a/Makefile b/Makefile index e8553971..0c1fd3a8 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ unit-test: end-to-end-test: build ./tests/test_color_only_output_matches_git_on_full_repo_history + ./tests/test_deprecated_options > /dev/null release: @make -f release.Makefile release diff --git a/ci/script.sh b/ci/script.sh index 51e638d6..0237e644 100755 --- a/ci/script.sh +++ b/ci/script.sh @@ -10,6 +10,7 @@ if [[ $TARGET != arm-unknown-linux-gnueabihf ]] && [[ $TARGET != aarch64-unknown cargo test --target "$TARGET" --verbose cargo build --release ./tests/test_color_only_output_matches_git_on_full_repo_history + ./tests/test_deprecated_options > /dev/null cargo run --target "$TARGET" -- < /dev/null fi diff --git a/src/cli.rs b/src/cli.rs index 0d915ad5..044fc1ed 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,5 @@ use std::process; -use std::str::FromStr; -use std::string::ToString; -use bit_set::BitSet; use console::Term; use structopt::clap::AppSettings::{ColorAlways, ColoredHelp, DeriveDisplayOrder}; use structopt::StructOpt; @@ -10,11 +7,11 @@ use structopt::StructOpt; use crate::bat::assets::HighlightingAssets; use crate::bat::output::PagingMode; use crate::config; -use crate::delta::State; use crate::env; +use crate::rewrite; use crate::style; -#[derive(StructOpt, Clone, Debug)] +#[derive(StructOpt, Clone, Debug, PartialEq)] #[structopt( name = "delta", about = "A syntax-highlighter for git and diff output", @@ -22,10 +19,60 @@ use crate::style; setting(ColoredHelp), setting(DeriveDisplayOrder), after_help = "\ -Colors +STYLES ------ -All delta color options work the same way. There are three ways to specify a color: +All options that have a name like --*-style work the same way. It is very similar to how +colors/styles are specified in a gitconfig file: +https://git-scm.com/docs/git-config#Documentation/git-config.txt-color + +Here is an example: + +--minus-style 'red bold underline #ffeeee' + +That means: For removed lines, set the foreground (text) color to 'red', make it bold and + underlined, and set the background color to '#ffeeee'. + +See the COLORS section below for how to specify a color. In addition to real colors, there are 3 +special color names: 'auto', 'normal', 'syntax'. + +Here is an example of using special color names together with a single attribute: + +--minus-style 'syntax bold auto' + +That means: For removed lines, syntax-highlight the text, and make it bold, and do whatever delta + normally does for the background. + +The available attributes are: 'blink', 'bold', 'dimmed', 'hidden', 'italic', 'reverse', +'strikethrough', 'underline'. + +A complete description of the style string syntax follows: + +- A style string consists of 0, 1, or 2 colors, together with an arbitrary number of style + attributes, all separated by spaces. + +- The first color is the foreground (text) color. The second color is the background color. + Attributes can go in any position. + +- This means that in order to specify a background color you must also specify a foreground (text) + color. + +- If you just want delta to do what it would normally do for one of the colors, then use the + special color 'auto'. This can be used for both foreground and background. + +- If you want the foreground text to be syntax-highlighted according to its language, then use the + special foreground color 'syntax'. This can only be used for the foreground (text). + +- If you want delta to not apply any color, then use the special color 'normal'. This can be used + for both foreground and background. + +- The minimal style specification is the empty string ''. This means: do not apply any colors or + styling to the element in question. + +COLORS +------ + +There are three ways to specify a color: 1. RGB hex code @@ -63,70 +110,87 @@ All delta color options work the same way. There are three ways to specify a col " )] pub struct Opt { - /// Use default colors appropriate for a light terminal background. For more control, see the other - /// color options. + #[structopt(long = "theme", env = "BAT_THEME")] + /// The code syntax highlighting theme to use. Use --list-themes to demo available themes. If + /// the theme is not set using this option, it will be taken from the BAT_THEME environment + /// variable, if that contains a valid theme name. --theme=none disables all syntax + /// highlighting. + pub theme: Option, + + /// Use default colors appropriate for a light terminal background. For more control, see the + /// style options. #[structopt(long = "light")] pub light: bool, /// Use default colors appropriate for a dark terminal background. For more control, see the - /// other color options. + /// style options. #[structopt(long = "dark")] pub dark: bool, - #[structopt(long = "minus-color")] - /// The background color for removed lines. - pub minus_color: Option, + #[structopt(long = "minus-style", default_value = "normal auto")] + /// Style (foreground, background, attributes) for removed lines. See STYLES section. + pub minus_style: String, - #[structopt(long = "minus-emph-color")] - /// The background color for emphasized sections of removed lines. - pub minus_emph_color: Option, + #[structopt(long = "zero-style", default_value = "syntax normal")] + /// Style (foreground, background, attributes) for unchanged lines. See STYLES section. + pub zero_style: String, - #[structopt(long = "minus-foreground-color")] - /// The foreground color for removed lines. - pub minus_foreground_color: Option, + #[structopt(long = "plus-style", default_value = "syntax auto")] + /// Style (foreground, background, attributes) for added lines. See STYLES section. + pub plus_style: String, - #[structopt(long = "minus-emph-foreground-color")] - /// The foreground color for emphasized sections of removed lines. - pub minus_emph_foreground_color: Option, + #[structopt(long = "minus-emph-style", default_value = "normal auto")] + /// Style (foreground, background, attributes) for emphasized sections of removed lines. See + /// STYLES section. + pub minus_emph_style: String, - #[structopt(long = "plus-color")] - /// The background color for added lines. - pub plus_color: Option, + #[structopt(long = "minus-non-emph-style")] + /// Style (foreground, background, attributes) for non-emphasized sections of removed lines + /// that have an emphasized section. Defaults to --minus-style. See STYLES section. + pub minus_non_emph_style: Option, - #[structopt(long = "plus-emph-color")] - /// The background color for emphasized sections of added lines. - pub plus_emph_color: Option, + #[structopt(long = "plus-emph-style", default_value = "syntax auto")] + /// Style (foreground, background, attributes) for emphasized sections of added lines. See + /// STYLES section. + pub plus_emph_style: String, - #[structopt(long = "plus-foreground-color")] - /// Disable syntax highlighting and instead use this foreground color for added lines. - pub plus_foreground_color: Option, + #[structopt(long = "plus-non-emph-style")] + /// Style (foreground, background, attributes) for non-emphasized sections of added lines that + /// have an emphasized section. Defaults to --plus-style. See STYLES section. + pub plus_non_emph_style: Option, - #[structopt(long = "plus-emph-foreground-color")] - /// Disable syntax highlighting and instead use this foreground color for emphasized sections of added lines. - pub plus_emph_foreground_color: Option, + #[structopt(long = "commit-style", default_value = "yellow")] + /// Style (foreground, background, attributes) for the commit hash line. See STYLES section. + pub commit_style: String, - #[structopt(long = "theme", env = "BAT_THEME")] - /// The code syntax highlighting theme to use. Use --theme=none to disable syntax highlighting. - /// If the theme is not set using this option, it will be taken from the BAT_THEME environment - /// variable, if that contains a valid theme name. Use --list-themes to view available themes. - /// Note that the choice of theme only affects code syntax highlighting. See --commit-color, - /// --file-color, --hunk-color to configure the colors of other parts of the diff output. - pub theme: Option, + #[structopt(long = "commit-decoration-style", default_value = "")] + /// Style for the commit hash decoration. See STYLES section. Special attributes are 'box', and + /// 'underline' are available in addition to the usual style attributes. + pub commit_decoration_style: String, - /// A string consisting only of the characters '-', '0', '+', specifying - /// which of the 3 diff hunk line-types (removed, unchanged, added) should - /// be syntax-highlighted. "all" and "none" are also valid values. - #[structopt(long = "syntax-highlight", default_value = "0+")] - pub lines_to_be_syntax_highlighted: String, + #[structopt(long = "file-style", default_value = "blue")] + /// Style (foreground, background, attributes) for the file section. See STYLES section. + pub file_style: String, - #[structopt(long = "highlight-removed")] - /// DEPRECATED: use --syntax-highlight. - pub highlight_minus_lines: bool, + #[structopt(long = "file-decoration-style", default_value = "blue underline")] + /// Style for the file decoration. See STYLES section. Special attributes are 'box', and + /// 'underline' are available in addition to the usual style attributes. + pub file_decoration_style: String, + + #[structopt(long = "hunk-header-style", default_value = "syntax")] + /// Style (foreground, background, attributes) for the hunk-header. See STYLES section. + pub hunk_header_style: String, + + #[structopt(long = "hunk-header-decoration-style", default_value = "blue box")] + /// Style (foreground, background, attributes) for the hunk-header decoration. See STYLES + /// section. Special attributes are 'box', and 'underline' are available in addition to the + /// usual style attributes. + pub hunk_header_decoration_style: String, #[structopt(long = "color-only")] /// Do not alter the input in any way other than applying colors. Equivalent to - /// `--keep-plus-minus-markers --width variable --tabs 0 --commit-style plain - /// --file-style plain --hunk-style plain`. + /// `--keep-plus-minus-markers --width variable --tabs 0 --commit-decoration '' + /// --file-decoration '' --hunk-decoration ''`. pub color_only: bool, #[structopt(long = "keep-plus-minus-markers")] @@ -134,33 +198,6 @@ pub struct Opt { /// default behavior is to output a space character in place of these markers. pub keep_plus_minus_markers: bool, - #[structopt(long = "commit-style", default_value = "plain")] - /// Formatting style for the commit section of git output. Options - /// are: plain, box. - pub commit_style: SectionStyle, - - #[structopt(long = "commit-color", default_value = "yellow")] - /// Color for the commit section of git output. - pub commit_color: String, - - #[structopt(long = "file-style", default_value = "underline")] - /// Formatting style for the file section of git output. Options - /// are: plain, box, underline. - pub file_style: SectionStyle, - - #[structopt(long = "file-color", default_value = "blue")] - /// Color for the file section of git output. - pub file_color: String, - - #[structopt(long = "hunk-style", default_value = "box")] - /// Formatting style for the hunk-marker section of git output. Options - /// are: plain, box. - pub hunk_style: SectionStyle, - - #[structopt(long = "hunk-color", default_value = "blue")] - /// Color for the hunk-marker section of git output. - pub hunk_color: String, - /// Use --width=variable to extend background colors to the end of each line only. Otherwise /// background colors extend to the full terminal width. #[structopt(short = "w", long = "width")] @@ -176,8 +213,8 @@ pub struct Opt { /// Show the command-line arguments (RGB hex codes) for the background colors that are in /// effect. The hex codes are displayed with their associated background color. This option can /// be combined with --light and --dark to view the background colors for those modes. It can - /// also be used to experiment with different RGB hex codes by combining this option with - /// --minus-color, --minus-emph-color, --plus-color, --plus-emph-color. + /// also be used to experiment with different RGB hex codes by combining this option with style + /// options such as --minus-style, --zero-style, --plus-style, etc. #[structopt(long = "show-background-colors")] pub show_background_colors: bool, @@ -214,50 +251,100 @@ pub struct Opt { /// or PAGER (BAT_PAGER has priority). #[structopt(long = "paging", default_value = "auto")] pub paging_mode: String, + + #[structopt(long = "minus-color")] + /// Deprecated: use --minus-style='normal my_background_color'. + pub deprecated_minus_background_color: Option, + + #[structopt(long = "minus-emph-color")] + /// Deprecated: use --minus-emph-style='normal my_background_color'. + pub deprecated_minus_emph_background_color: Option, + + #[structopt(long = "plus-color")] + /// Deprecated: Use --plus-style='normal my_background_color'. + pub deprecated_plus_background_color: Option, + + #[structopt(long = "plus-emph-color")] + /// Deprecated: Use --plus-emph-style='normal my_background_color'. + pub deprecated_plus_emph_background_color: Option, + + #[structopt(long = "highlight-removed")] + /// Deprecated: use --minus-style='syntax'. + pub deprecated_highlight_minus_lines: bool, + + #[structopt(long = "commit-color")] + /// Deprecated: use --commit-style='my_foreground_color' --commit-decoration-style='my_foreground_color'. + pub deprecated_commit_color: Option, + + #[structopt(long = "file-color")] + /// Deprecated: use --file-style='my_foreground_color' --file-decoration-style='my_foreground_color'. + pub deprecated_file_color: Option, + + #[structopt(long = "hunk-style")] + /// Deprecated: synonym of --hunk-header-decoration-style. + pub deprecated_hunk_style: Option, + + #[structopt(long = "hunk-color")] + /// Deprecated: use --hunk-header-style='my_foreground_color' --hunk-header-decoration-style='my_foreground_color'. + pub deprecated_hunk_color: Option, } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SectionStyle { - Box, - Plain, - Underline, - Omit, -} - -// TODO: clean up enum parsing and error handling - -#[derive(Debug)] -pub enum Error { - SectionStyleParseError, -} - -impl FromStr for SectionStyle { - type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "box" => Ok(SectionStyle::Box), - "plain" => Ok(SectionStyle::Plain), - "underline" => Ok(SectionStyle::Underline), - _ => Err(Error::SectionStyleParseError), - } - } -} - -impl ToString for Error { - fn to_string(&self) -> String { - "".to_string() - } -} - -pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { +pub fn process_command_line_arguments<'a>(mut opt: Opt) -> config::Config<'a> { let assets = HighlightingAssets::new(); + _check_validity(&opt, &assets); + + rewrite::apply_rewrite_rules(&mut opt); + + // We do not use the full width, in case `less --status-column` is in effect. See #41 and #10. + // TODO: There seems to be some confusion in the accounting: we are actually leaving 2 + // characters unused for less at the right edge of the terminal, despite the subtraction of 1 + // here. + let available_terminal_width = (Term::stdout().size().1 - 1) as usize; + + let paging_mode = match opt.paging_mode.as_ref() { + "always" => PagingMode::Always, + "never" => PagingMode::Never, + "auto" => PagingMode::QuitIfOneScreen, + _ => { + eprintln!( + "Invalid value for --paging option: {} (valid values are \"always\", \"never\", and \"auto\")", + opt.paging_mode + ); + process::exit(1); + } + }; + + let true_color = match opt.true_color.as_ref() { + "always" => true, + "never" => false, + "auto" => is_truecolor_terminal(), + _ => { + eprintln!( + "Invalid value for --24-bit-color option: {} (valid values are \"always\", \"never\", and \"auto\")", + opt.true_color + ); + process::exit(1); + } + }; + + config::get_config( + opt, + assets.syntax_set, + assets.theme_set, + true_color, + available_terminal_width, + paging_mode, + ) +} + +fn _check_validity(opt: &Opt, assets: &HighlightingAssets) { if opt.light && opt.dark { eprintln!("--light and --dark cannot be used together."); process::exit(1); } - match &opt.theme { - Some(theme) if !style::is_no_syntax_highlighting_theme_name(&theme) => { + if let Some(ref theme) = opt.theme { + if !style::is_no_syntax_highlighting_theme_name(&theme) { if !assets.theme_set.themes.contains_key(theme.as_str()) { eprintln!("Invalid theme: '{}'", theme); process::exit(1); @@ -279,53 +366,16 @@ pub fn process_command_line_arguments<'a>(opt: Opt) -> config::Config<'a> { process::exit(1); } } - _ => (), - }; + } +} - // We do not use the full width, in case `less --status-column` is in effect. See #41 and #10. - - // TODO: There seems to be some confusion in the accounting: we are actually leaving 2 - // characters unused for less at the right edge of the terminal, despite the subtraction of 1 - // here. - let available_terminal_width = (Term::stdout().size().1 - 1) as usize; - - let paging_mode = match opt.paging_mode.as_ref() { - "always" => PagingMode::Always, - "never" => PagingMode::Never, - "auto" => PagingMode::QuitIfOneScreen, - _ => { - eprintln!( - "Invalid paging value: {} (valid values are \"always\", \"never\", and \"auto\")", - opt.paging_mode - ); - process::exit(1); - } - }; - - let true_color = match opt.true_color.as_ref() { - "always" => true, - "never" => false, - "auto" => is_truecolor_terminal(), - _ => { - eprintln!( - "Invalid value for --24-bit-color option: {} (valid values are \"always\", \"never\", and \"auto\")", - opt.true_color - ); - process::exit(1); - } - }; - - let lines_to_be_syntax_highlighted = get_lines_to_be_syntax_highlighted(&opt); - - config::get_config( - opt, - assets.syntax_set, - assets.theme_set, - true_color, - available_terminal_width, - paging_mode, - lines_to_be_syntax_highlighted, - ) +pub fn unreachable(message: &str) -> ! { + eprintln!( + "{} This should not be possible. \ + Please report the bug at https://github.com/dandavison/delta/issues.", + message + ); + process::exit(1); } fn is_truecolor_terminal() -> bool { @@ -334,44 +384,6 @@ fn is_truecolor_terminal() -> bool { .unwrap_or(false) } -fn get_lines_to_be_syntax_highlighted(opt: &Opt) -> BitSet { - if opt.highlight_minus_lines { - eprintln!("--highlight-removed is deprecated: use --syntax-highlight."); - } - - let syntax_highlight_lines = match opt.lines_to_be_syntax_highlighted.to_lowercase().as_ref() { - "none" => "", - // This is the default value of the new option: honor the deprecated option if it has been used. - "0+" => match opt.highlight_minus_lines { - true => "-0+", - false => "0+", - }, - "all" => "-0+", - s => s, - } - .to_string(); - - let mut lines_to_be_syntax_highlighted = BitSet::new(); - for line_type in syntax_highlight_lines.chars() { - lines_to_be_syntax_highlighted.insert(match line_type { - '-' => State::HunkMinus as usize, - '0' => State::HunkZero as usize, - '+' => State::HunkPlus as usize, - s => { - eprintln!("Invalid --syntax-highlight value: {}. Valid characters are \"-\", \"0\", \"+\".", s); - process::exit(1); - } - }); - } - if opt.minus_foreground_color.is_some() || opt.minus_emph_foreground_color.is_some() { - lines_to_be_syntax_highlighted.remove(State::HunkMinus as usize); - } - if opt.plus_foreground_color.is_some() || opt.plus_emph_foreground_color.is_some() { - lines_to_be_syntax_highlighted.remove(State::HunkPlus as usize); - } - lines_to_be_syntax_highlighted -} - #[cfg(test)] mod tests { use std::env; @@ -381,6 +393,7 @@ mod tests { use crate::tests::integration_test_utils::integration_test_utils; #[test] + #[ignore] fn test_theme_selection() { #[derive(PartialEq)] enum Mode { @@ -472,20 +485,32 @@ mod tests { assert_eq!(config.theme.unwrap().name.as_ref().unwrap(), expected_theme); } assert_eq!( - config.minus_style_modifier.background.unwrap(), - style::get_minus_color_default(expected_mode == Mode::Light, is_true_color) + config.minus_style.ansi_term_style.background.unwrap(), + style::get_minus_background_color_default( + expected_mode == Mode::Light, + is_true_color + ) ); assert_eq!( - config.minus_emph_style_modifier.background.unwrap(), - style::get_minus_emph_color_default(expected_mode == Mode::Light, is_true_color) + config.minus_emph_style.ansi_term_style.background.unwrap(), + style::get_minus_emph_background_color_default( + expected_mode == Mode::Light, + is_true_color + ) ); assert_eq!( - config.plus_style_modifier.background.unwrap(), - style::get_plus_color_default(expected_mode == Mode::Light, is_true_color) + config.plus_style.ansi_term_style.background.unwrap(), + style::get_plus_background_color_default( + expected_mode == Mode::Light, + is_true_color + ) ); assert_eq!( - config.plus_emph_style_modifier.background.unwrap(), - style::get_plus_emph_color_default(expected_mode == Mode::Light, is_true_color) + config.plus_emph_style.ansi_term_style.background.unwrap(), + style::get_plus_emph_background_color_default( + expected_mode == Mode::Light, + is_true_color + ) ); } } diff --git a/src/config.rs b/src/config.rs index a98ee11c..947b475f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,16 +1,18 @@ use std::process; use std::str::FromStr; -use bit_set::BitSet; -use syntect::highlighting::{Color, Style, StyleModifier, Theme, ThemeSet}; +use ansi_term::Color; +use syntect::highlighting::Color as SyntectColor; +use syntect::highlighting::Style as SyntectStyle; +use syntect::highlighting::{Theme, ThemeSet}; use syntect::parsing::SyntaxSet; use crate::bat::output::PagingMode; -use crate::cli; -use crate::delta::State; +use crate::bat::terminal::to_ansi_color; +use crate::cli::{self, unreachable}; use crate::env; -use crate::paint; -use crate::style; +use crate::style::{self, DecorationStyle, Style}; +use crate::syntect_color; pub struct Config<'a> { pub theme: Option, @@ -18,60 +20,29 @@ pub struct Config<'a> { pub dummy_theme: Theme, pub max_line_distance: f64, pub max_line_distance_for_naively_paired_lines: f64, - pub minus_style_modifier: StyleModifier, - pub minus_emph_style_modifier: StyleModifier, - pub plus_style_modifier: StyleModifier, - pub plus_emph_style_modifier: StyleModifier, + pub minus_style: Style, + pub minus_emph_style: Style, + pub minus_non_emph_style: Style, + pub zero_style: Style, + pub plus_style: Style, + pub plus_emph_style: Style, + pub plus_non_emph_style: Style, pub minus_line_marker: &'a str, pub plus_line_marker: &'a str, - pub lines_to_be_syntax_highlighted: BitSet, - pub commit_style: cli::SectionStyle, - pub commit_color: Color, - pub file_style: cli::SectionStyle, - pub file_color: Color, - pub hunk_style: cli::SectionStyle, - pub hunk_color: Color, + pub commit_style: Style, + pub file_style: Style, + pub hunk_header_style: Style, pub syntax_set: SyntaxSet, pub terminal_width: usize, pub true_color: bool, pub background_color_extends_to_terminal_width: bool, pub tab_width: usize, - pub no_style: Style, + pub null_style: Style, + pub null_syntect_style: SyntectStyle, pub max_buffered_lines: usize, pub paging_mode: PagingMode, } -#[allow(dead_code)] -pub enum ColorLayer { - Background, - Foreground, -} -use ColorLayer::*; -use State::*; - -impl<'a> Config<'a> { - #[allow(dead_code)] - pub fn get_color(&self, state: &State, layer: ColorLayer) -> Option { - let modifier = match state { - HunkMinus => Some(self.minus_style_modifier), - HunkZero => None, - HunkPlus => Some(self.plus_style_modifier), - _ => panic!("Invalid"), - }; - match (modifier, layer) { - (Some(modifier), Background) => modifier.background, - (Some(modifier), Foreground) => modifier.foreground, - (None, _) => None, - } - } - - #[allow(dead_code)] - pub fn should_syntax_highlight(&self, state: &State) -> bool { - self.lines_to_be_syntax_highlighted - .contains((*state).clone() as usize) - } -} - pub fn get_config<'a>( opt: cli::Opt, syntax_set: SyntaxSet, @@ -79,26 +50,8 @@ pub fn get_config<'a>( true_color: bool, terminal_width: usize, paging_mode: PagingMode, - lines_to_be_syntax_highlighted: BitSet, ) -> Config<'a> { - // Implement --color-only - let keep_plus_minus_markers = if opt.color_only { - true - } else { - opt.keep_plus_minus_markers - }; let background_color_extends_to_terminal_width = opt.width != Some("variable".to_string()); - let tab_width = if opt.color_only { 0 } else { opt.tab_width }; - let (commit_style, file_style, hunk_style) = if opt.color_only { - ( - cli::SectionStyle::Plain, - cli::SectionStyle::Plain, - cli::SectionStyle::Plain, - ) - } else { - (opt.commit_style, opt.file_style, opt.hunk_style) - }; - let theme_name_from_bat_pager = env::get_env_var("BAT_THEME"); let (is_light_mode, theme_name) = get_is_light_mode_and_theme_name( opt.theme.as_ref(), @@ -108,11 +61,17 @@ pub fn get_config<'a>( ); let ( - minus_style_modifier, - minus_emph_style_modifier, - plus_style_modifier, - plus_emph_style_modifier, - ) = make_styles(&opt, is_light_mode, true_color); + minus_style, + minus_emph_style, + minus_non_emph_style, + zero_style, + plus_style, + plus_emph_style, + plus_non_emph_style, + ) = make_hunk_styles(&opt, is_light_mode, true_color); + + let (commit_style, file_style, hunk_header_style) = + make_commit_file_hunk_header_styles(&opt, true_color); let theme = if style::is_no_syntax_highlighting_theme_name(&theme_name) { None @@ -121,8 +80,16 @@ pub fn get_config<'a>( }; let dummy_theme = theme_set.themes.values().next().unwrap().clone(); - let minus_line_marker = if keep_plus_minus_markers { "-" } else { " " }; - let plus_line_marker = if keep_plus_minus_markers { "+" } else { " " }; + let minus_line_marker = if opt.keep_plus_minus_markers { + "-" + } else { + " " + }; + let plus_line_marker = if opt.keep_plus_minus_markers { + "+" + } else { + " " + }; let max_line_distance_for_naively_paired_lines = env::get_env_var("DELTA_EXPERIMENTAL_MAX_LINE_DISTANCE_FOR_NAIVELY_PAIRED_LINES") @@ -135,25 +102,25 @@ pub fn get_config<'a>( dummy_theme, max_line_distance: opt.max_line_distance, max_line_distance_for_naively_paired_lines, - minus_style_modifier, - minus_emph_style_modifier, - plus_style_modifier, - plus_emph_style_modifier, - lines_to_be_syntax_highlighted, + minus_style, + minus_emph_style, + minus_non_emph_style, + zero_style, + plus_style, + plus_emph_style, + plus_non_emph_style, minus_line_marker, plus_line_marker, commit_style, - commit_color: color_from_rgb_or_ansi_code(&opt.commit_color), file_style, - file_color: color_from_rgb_or_ansi_code(&opt.file_color), - hunk_style, - hunk_color: color_from_rgb_or_ansi_code(&opt.hunk_color), + hunk_header_style, true_color, terminal_width, background_color_extends_to_terminal_width, - tab_width, + tab_width: opt.tab_width, syntax_set, - no_style: style::get_no_style(), + null_style: Style::new(), + null_syntect_style: SyntectStyle::default(), max_buffered_lines: 32, paging_mode, } @@ -214,84 +181,414 @@ fn valid_theme_name_or_none(theme_name: Option<&String>, theme_set: &ThemeSet) - } } -fn make_styles<'a>( +fn make_hunk_styles<'a>( opt: &'a cli::Opt, is_light_mode: bool, true_color: bool, -) -> (StyleModifier, StyleModifier, StyleModifier, StyleModifier) { - let minus_style = make_style( - opt.minus_color.as_deref(), - Some(style::get_minus_color_default(is_light_mode, true_color)), - opt.minus_foreground_color.as_deref(), +) -> (Style, Style, Style, Style, Style, Style, Style) { + let minus_style = parse_style( + &opt.minus_style, None, - ); - - let minus_emph_style = make_style( - opt.minus_emph_color.as_deref(), - Some(style::get_minus_emph_color_default( + Some(style::get_minus_background_color_default( is_light_mode, true_color, )), - opt.minus_emph_foreground_color.as_deref(), - minus_style.foreground, - ); - - let plus_style = make_style( - opt.plus_color.as_deref(), - Some(style::get_plus_color_default(is_light_mode, true_color)), - opt.plus_foreground_color.as_deref(), None, + true_color, ); - let plus_emph_style = make_style( - opt.plus_emph_color.as_deref(), - Some(style::get_plus_emph_color_default( + let minus_emph_style = parse_style( + &opt.minus_emph_style, + None, + Some(style::get_minus_emph_background_color_default( is_light_mode, true_color, )), - opt.plus_emph_foreground_color.as_deref(), - plus_style.foreground, + None, + true_color, ); - (minus_style, minus_emph_style, plus_style, plus_emph_style) + // The non-emph styles default to the base style. + let minus_non_emph_style = match &opt.minus_non_emph_style { + Some(style_string) => parse_style( + &style_string, + None, + minus_style.ansi_term_style.background, + None, + true_color, + ), + None => minus_style, + }; + + let zero_style = parse_style(&opt.zero_style, None, None, None, true_color); + + let plus_style = parse_style( + &opt.plus_style, + None, + Some(style::get_plus_background_color_default( + is_light_mode, + true_color, + )), + None, + true_color, + ); + + let plus_emph_style = parse_style( + &opt.plus_emph_style, + None, + Some(style::get_plus_emph_background_color_default( + is_light_mode, + true_color, + )), + None, + true_color, + ); + + // The non-emph styles default to the base style. + let plus_non_emph_style = match &opt.plus_non_emph_style { + Some(style_string) => parse_style( + &style_string, + None, + plus_style.ansi_term_style.background, + None, + true_color, + ), + None => plus_style, + }; + + ( + minus_style, + minus_emph_style, + minus_non_emph_style, + zero_style, + plus_style, + plus_emph_style, + plus_non_emph_style, + ) } -fn make_style( - background: Option<&str>, - background_default: Option, - foreground: Option<&str>, +fn make_commit_file_hunk_header_styles(opt: &cli::Opt, true_color: bool) -> (Style, Style, Style) { + ( + _parse_style_respecting_deprecated_foreground_color_arg( + &opt.commit_style, + None, + None, + Some(&opt.commit_decoration_style), + opt.deprecated_commit_color.as_deref(), + true_color, + ), + _parse_style_respecting_deprecated_foreground_color_arg( + &opt.file_style, + None, + None, + Some(&opt.file_decoration_style), + opt.deprecated_file_color.as_deref(), + true_color, + ), + _parse_style_respecting_deprecated_foreground_color_arg( + &opt.hunk_header_style, + None, + None, + Some(&opt.hunk_header_decoration_style), + opt.deprecated_hunk_color.as_deref(), + true_color, + ), + ) +} + +fn _parse_style_respecting_deprecated_foreground_color_arg( + style_string: &str, foreground_default: Option, -) -> StyleModifier { - StyleModifier { - background: color_from_rgb_or_ansi_code_with_default(background, background_default), - foreground: color_from_rgb_or_ansi_code_with_default(foreground, foreground_default), - font_style: None, + background_default: Option, + decoration_style_string: Option<&str>, + deprecated_foreground_color_arg: Option<&str>, + true_color: bool, +) -> Style { + let mut style = parse_style( + style_string, + foreground_default, + background_default, + decoration_style_string, + true_color, + ); + if let Some(s) = deprecated_foreground_color_arg { + style.ansi_term_style.foreground = parse_ansi_term_style(s, None, None, true_color) + .0 + .foreground; + } + style +} + +/// Construct Style from style and decoration-style strings supplied on command line, together with +/// defaults. +pub fn parse_style( + style_string: &str, + foreground_default: Option, + background_default: Option, + decoration_style_string: Option<&str>, + true_color: bool, +) -> Style { + let (style_string, special_attribute_from_style_string) = + extract_special_attribute(style_string); + let (ansi_term_style, is_syntax_highlighted) = parse_ansi_term_style( + &style_string, + foreground_default, + background_default, + true_color, + ); + let decoration_style = match decoration_style_string { + Some(s) if s != "" => parse_decoration_style(s, true_color), + _ => None, + }; + let mut style = Style { + ansi_term_style, + is_syntax_highlighted, + decoration_style, + }; + if let Some(special_attribute) = special_attribute_from_style_string { + if let Some(decoration_style) = apply_special_decoration_attribute( + style.decoration_style, + &special_attribute, + true_color, + ) { + style.decoration_style = Some(decoration_style) + } + } + style +} + +fn apply_special_decoration_attribute( + decoration_style: Option, + special_attribute: &str, + true_color: bool, +) -> Option { + let ansi_term_style = match decoration_style { + None => ansi_term::Style::new(), + Some(style::DecorationStyle::Box(ansi_term_style)) => ansi_term_style, + Some(style::DecorationStyle::Underline(ansi_term_style)) => ansi_term_style, + Some(style::DecorationStyle::Omit) => ansi_term::Style::new(), + }; + match parse_decoration_style(special_attribute, true_color) { + Some(style::DecorationStyle::Box(_)) => Some(style::DecorationStyle::Box(ansi_term_style)), + Some(style::DecorationStyle::Underline(_)) => { + Some(style::DecorationStyle::Underline(ansi_term_style)) + } + Some(style::DecorationStyle::Omit) => Some(style::DecorationStyle::Omit), + None => None, } } -fn color_from_rgb_or_ansi_code(s: &str) -> Color { +fn parse_ansi_term_style( + s: &str, + foreground_default: Option, + background_default: Option, + true_color: bool, +) -> (ansi_term::Style, bool) { + let mut style = ansi_term::Style::new(); + let mut seen_foreground = false; + let mut seen_background = false; + let mut is_syntax_highlighted = false; + for word in s.to_lowercase().split_whitespace() { + if word == "blink" { + style.is_blink = true; + } else if word == "bold" { + style.is_bold = true; + } else if word == "dimmed" { + style.is_dimmed = true; + } else if word == "hidden" { + style.is_hidden = true; + } else if word == "italic" { + style.is_italic = true; + } else if word == "reverse" { + style.is_reverse = true; + } else if word == "strikethrough" { + style.is_strikethrough = true; + } else if !seen_foreground { + if word == "syntax" { + is_syntax_highlighted = true; + } else { + style.foreground = + color_from_rgb_or_ansi_code_with_default(word, foreground_default, true_color); + } + seen_foreground = true; + } else if !seen_background { + if word == "syntax" { + eprintln!( + "You have used the special color 'syntax' as a background color \ + (second color in a style string). It may only be used as a foreground \ + color (first color in a style string)." + ); + process::exit(1); + } else { + style.background = + color_from_rgb_or_ansi_code_with_default(word, background_default, true_color); + } + seen_background = true; + } else { + eprintln!( + "Invalid style string: {}. See the STYLES section of delta --help.", + s + ); + process::exit(1); + } + } + (style, is_syntax_highlighted) +} + +fn parse_decoration_style(style_string: &str, true_color: bool) -> Option { + let style_string = style_string.to_lowercase(); + let (style_string, special_attribute) = extract_special_attribute(&style_string); + let special_attribute = special_attribute.unwrap_or_else(|| { + eprintln!( + "To specify a decoration style, you must supply one of the special attributes \ + 'box', 'underline', or 'omit'.", + ); + process::exit(1); + }); + let (style, is_syntax_highlighted): (ansi_term::Style, bool) = + parse_ansi_term_style(&style_string, None, None, true_color); + if is_syntax_highlighted { + eprintln!("'syntax' may not be used as a color name in a decoration style."); + process::exit(1); + }; + match special_attribute.as_ref() { + "box" => Some(DecorationStyle::Box(style)), + "underline" => Some(DecorationStyle::Underline(style)), + "omit" => Some(DecorationStyle::Omit), + "plain" => None, + _ => unreachable("Unreachable code path reached in parse_decoration_style."), + } +} + +/// If the style string contains a 'special decoration attribute' then extract it and return it +/// along with the modified style string. +fn extract_special_attribute(style_string: &str) -> (String, Option) { + let (special_attributes, standard_attributes): (Vec<&str>, Vec<&str>) = + style_string.split_whitespace().partition(|&token| { + token == "box" || token == "underline" || token == "omit" || token == "plain" + }); + match special_attributes { + attrs if attrs.len() == 0 => (style_string.to_string(), None), + attrs if attrs.len() == 1 => (standard_attributes.join(" "), Some(attrs[0].to_string())), + attrs => { + eprintln!( + "Encountered multiple special attributes: {:?}. \ + You may supply no more than one of the special attributes 'box', 'underline', \ + and 'omit'.", + attrs.join(", ") + ); + process::exit(1); + } + } +} + +pub fn color_from_rgb_or_ansi_code(s: &str, true_color: bool) -> Color { let die = || { eprintln!("Invalid color: {}", s); process::exit(1); }; - if s.starts_with("#") { - Color::from_str(s).unwrap_or_else(|_| die()) + let syntect_color = if s.starts_with("#") { + SyntectColor::from_str(s).unwrap_or_else(|_| die()) } else { s.parse::() .ok() - .and_then(paint::color_from_ansi_number) - .or_else(|| paint::color_from_ansi_name(s)) + .and_then(syntect_color::syntect_color_from_ansi_number) + .or_else(|| syntect_color::syntect_color_from_ansi_name(s)) .unwrap_or_else(die) - } + }; + to_ansi_color(syntect_color, true_color) } fn color_from_rgb_or_ansi_code_with_default( - arg: Option<&str>, + arg: &str, default: Option, + true_color: bool, ) -> Option { - match arg { - Some(string) if string.to_lowercase() == "none" => None, - Some(string) => Some(color_from_rgb_or_ansi_code(&string)), - None => default, + let arg = arg.to_lowercase(); + if arg == "normal" { + None + } else if arg == "auto" { + default + } else { + Some(color_from_rgb_or_ansi_code(&arg, true_color)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::style::ansi_color_name_to_number; + use ansi_term; + + #[test] + fn test_parse_ansi_term_style() { + assert_eq!( + parse_ansi_term_style("", None, None, false), + (ansi_term::Style::new(), false) + ); + assert_eq!( + parse_ansi_term_style("red", None, None, false), + ( + ansi_term::Style { + foreground: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("red").unwrap() + )), + ..ansi_term::Style::new() + }, + false + ) + ); + assert_eq!( + parse_ansi_term_style("red green", None, None, false), + ( + ansi_term::Style { + foreground: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("red").unwrap() + )), + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("green").unwrap() + )), + ..ansi_term::Style::new() + }, + false + ) + ); + } + + #[test] + fn test_parse_ansi_term_style_with_special_syntax_color() { + assert_eq!( + parse_ansi_term_style("syntax", None, None, false), + (ansi_term::Style::new(), true) + ); + assert_eq!( + parse_ansi_term_style("syntax italic white hidden", None, None, false), + ( + ansi_term::Style { + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("white").unwrap() + )), + is_italic: true, + is_hidden: true, + ..ansi_term::Style::new() + }, + true + ) + ); + assert_eq!( + parse_ansi_term_style("bold syntax italic white hidden", None, None, false), + ( + ansi_term::Style { + background: Some(ansi_term::Color::Fixed( + ansi_color_name_to_number("white").unwrap() + )), + is_bold: true, + is_italic: true, + is_hidden: true, + ..ansi_term::Style::new() + }, + true + ) + ); } } diff --git a/src/delta.rs b/src/delta.rs index fb52b131..6c82a224 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -5,18 +5,18 @@ use console::strip_ansi_codes; use std::io::BufRead; use unicode_segmentation::UnicodeSegmentation; -use crate::cli; +use crate::cli::unreachable; use crate::config::Config; use crate::draw; -use crate::paint::{self, Painter}; +use crate::paint::Painter; use crate::parse; -use crate::style; +use crate::style::{self, DecorationStyle}; #[derive(Clone, Debug, PartialEq)] pub enum State { CommitMeta, // In commit metadata section FileMeta, // In diff metadata section, between (possible) commit metadata and first hunk - HunkMeta, // In hunk metadata line + HunkHeader, // In hunk metadata line HunkZero, // In hunk; unchanged line HunkMinus, // In hunk; removed line HunkPlus, // In hunk; added line @@ -33,7 +33,7 @@ pub enum Source { impl State { fn is_in_hunk(&self) -> bool { match *self { - State::HunkMeta | State::HunkZero | State::HunkMinus | State::HunkPlus => true, + State::HunkHeader | State::HunkZero | State::HunkMinus | State::HunkPlus => true, _ => false, } } @@ -42,11 +42,11 @@ impl State { // Possible transitions, with actions on entry: // // -// | from \ to | CommitMeta | FileMeta | HunkMeta | HunkZero | HunkMinus | HunkPlus | +// | from \ to | CommitMeta | FileMeta | HunkHeader | HunkZero | HunkMinus | HunkPlus | // |-------------+-------------+-------------+-------------+-------------+-------------+----------| // | CommitMeta | emit | emit | | | | | // | FileMeta | | emit | emit | | | | -// | HunkMeta | | | | emit | push | push | +// | HunkHeader | | | | emit | push | push | // | HunkZero | emit | emit | emit | emit | push | push | // | HunkMinus | flush, emit | flush, emit | flush, emit | flush, emit | push | push | // | HunkPlus | flush, emit | flush, emit | flush, emit | flush, emit | flush, push | push | @@ -74,7 +74,7 @@ where if line.starts_with("commit ") { painter.paint_buffered_lines(); state = State::CommitMeta; - if config.commit_style != cli::SectionStyle::Plain { + if config.commit_style.decoration_style.is_some() { painter.emit()?; handle_commit_meta_header_line(&mut painter, &raw_line, config)?; continue; @@ -86,7 +86,7 @@ where // FIXME: For unified diff input, removal ("-") of a line starting with "--" (e.g. a // Haskell or SQL comment) will be confused with the "---" file metadata marker. && (line.starts_with("--- ") || line.starts_with("rename from ")) - && config.file_style != cli::SectionStyle::Plain + && config.file_style.decoration_style.is_some() { minus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff); if source == Source::DiffUnified { @@ -99,7 +99,7 @@ where )); } } else if (line.starts_with("+++ ") || line.starts_with("rename to ")) - && config.file_style != cli::SectionStyle::Plain + && config.file_style.decoration_style.is_some() { plus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff); painter.set_syntax(parse::get_file_extension_from_file_meta_line_file_path( @@ -114,11 +114,11 @@ where source == Source::DiffUnified, )?; } else if line.starts_with("@@") { - state = State::HunkMeta; + state = State::HunkHeader; painter.set_highlighter(); - if config.hunk_style != cli::SectionStyle::Plain { + if config.hunk_header_style.decoration_style.is_some() { painter.emit()?; - handle_hunk_meta_line(&mut painter, &line, config)?; + handle_hunk_header_line(&mut painter, &line, config)?; continue; } } else if source == Source::DiffUnified && line.starts_with("Only in ") @@ -140,7 +140,7 @@ where state = State::FileMeta; painter.paint_buffered_lines(); - if config.file_style != cli::SectionStyle::Plain { + if config.file_style.decoration_style.is_some() { painter.emit()?; handle_generic_file_meta_header_line(&mut painter, &raw_line, config)?; continue; @@ -153,7 +153,7 @@ where continue; } - if state == State::FileMeta && config.file_style != cli::SectionStyle::Plain { + if state == State::FileMeta && config.file_style.decoration_style.is_some() { // The file metadata section is 4 lines. Skip them under non-plain file-styles. continue; } else { @@ -190,19 +190,26 @@ fn handle_commit_meta_header_line( line: &str, config: &Config, ) -> std::io::Result<()> { - let draw_fn = match config.commit_style { - cli::SectionStyle::Box => draw::write_boxed_with_line, - cli::SectionStyle::Underline => draw::write_underlined, - cli::SectionStyle::Plain => panic!(), - cli::SectionStyle::Omit => return Ok(()), + let decoration_ansi_term_style; + let draw_fn = match config.commit_style.decoration_style { + Some(DecorationStyle::Box(style)) => { + decoration_ansi_term_style = style; + draw::write_boxed_with_line + } + Some(DecorationStyle::Underline(style)) => { + decoration_ansi_term_style = style; + draw::write_underlined + } + Some(DecorationStyle::Omit) => return Ok(()), + None => unreachable("Unreachable code path reached in handle_commit_meta_header_line."), }; draw_fn( painter.writer, line, config.terminal_width, - config.commit_color, + config.commit_style.ansi_term_style, + decoration_ansi_term_style, true, - config.true_color, )?; Ok(()) } @@ -225,54 +232,68 @@ fn handle_generic_file_meta_header_line( line: &str, config: &Config, ) -> std::io::Result<()> { - let draw_fn = match config.file_style { - cli::SectionStyle::Box => draw::write_boxed_with_line, - cli::SectionStyle::Underline => draw::write_underlined, - cli::SectionStyle::Plain => panic!(), - cli::SectionStyle::Omit => return Ok(()), + let decoration_ansi_term_style; + let draw_fn = match config.file_style.decoration_style { + Some(DecorationStyle::Box(style)) => { + decoration_ansi_term_style = style; + draw::write_boxed_with_line + } + Some(DecorationStyle::Underline(style)) => { + decoration_ansi_term_style = style; + draw::write_underlined + } + Some(DecorationStyle::Omit) => return Ok(()), + None => { + unreachable("Unreachable code path reached in handle_generic_file_meta_header_line.") + } }; writeln!(painter.writer)?; draw_fn( painter.writer, - &paint::paint_text_foreground(line, config.file_color, config.true_color), + &config.file_style.ansi_term_style.paint(line), config.terminal_width, - config.file_color, + config.file_style.ansi_term_style, + decoration_ansi_term_style, false, - config.true_color, )?; Ok(()) } -fn handle_hunk_meta_line( +fn handle_hunk_header_line( painter: &mut Painter, line: &str, config: &Config, ) -> std::io::Result<()> { - let draw_fn = match config.hunk_style { - cli::SectionStyle::Box => draw::write_boxed, - cli::SectionStyle::Underline => draw::write_underlined, - cli::SectionStyle::Plain => panic!(), - cli::SectionStyle::Omit => return Ok(()), + let decoration_ansi_term_style; + let draw_fn = match config.hunk_header_style.decoration_style { + Some(style::DecorationStyle::Box(style)) => { + decoration_ansi_term_style = style; + draw::write_boxed + } + Some(style::DecorationStyle::Underline(style)) => { + decoration_ansi_term_style = style; + draw::write_underlined + } + Some(style::DecorationStyle::Omit) => return Ok(()), + None => unreachable("Unreachable code path reached in handle_hunk_header_line."), }; let (raw_code_fragment, line_number) = parse::parse_hunk_metadata(&line); - let code_fragment = prepare(raw_code_fragment, false, config); - if !code_fragment.is_empty() { - let syntax_style_sections = Painter::get_line_syntax_style_sections( - &code_fragment, - true, + let lines = vec![prepare(raw_code_fragment, false, config)]; + if !lines[0].is_empty() { + let syntax_style_sections = Painter::get_syntax_style_sections_for_lines( + &lines, + &State::HunkHeader, &mut painter.highlighter, &painter.config, ); Painter::paint_lines( - vec![syntax_style_sections], - vec![vec![( - style::NO_BACKGROUND_COLOR_STYLE_MODIFIER, - &code_fragment, - )]], + syntax_style_sections, + vec![vec![(config.hunk_header_style, lines[0].as_str())]], &mut painter.output_buffer, config, "", - style::NO_BACKGROUND_COLOR_STYLE_MODIFIER, + config.null_style, + config.null_style, Some(false), ); painter.output_buffer.pop(); // trim newline @@ -280,17 +301,16 @@ fn handle_hunk_meta_line( painter.writer, &painter.output_buffer, config.terminal_width, - config.hunk_color, + config.hunk_header_style.ansi_term_style, + decoration_ansi_term_style, false, - config.true_color, )?; painter.output_buffer.clear(); } - writeln!( - painter.writer, - "\n{}", - paint::paint_text_foreground(line_number, config.hunk_color, config.true_color) - )?; + match config.hunk_header_style.decoration_ansi_term_style() { + Some(style) => writeln!(painter.writer, "\n{}", style.paint(line_number))?, + None => writeln!(painter.writer, "\n{}", line_number)?, + }; Ok(()) } @@ -331,27 +351,23 @@ fn handle_hunk_line( let state = State::HunkZero; let prefix = if line.is_empty() { "" } else { &line[..1] }; painter.paint_buffered_lines(); - let line = prepare(&line, true, config); - let syntax_style_sections = if config.should_syntax_highlight(&state) { - Painter::get_line_syntax_style_sections( - &line, - true, - &mut painter.highlighter, - &painter.config, - ) - } else { - vec![(style::get_no_style(), line.as_str())] - }; - let diff_style_sections = - vec![(style::NO_BACKGROUND_COLOR_STYLE_MODIFIER, line.as_str())]; + let lines = vec![prepare(&line, true, config)]; + let syntax_style_sections = Painter::get_syntax_style_sections_for_lines( + &lines, + &state, + &mut painter.highlighter, + &painter.config, + ); + let diff_style_sections = vec![(config.zero_style, lines[0].as_str())]; Painter::paint_lines( - vec![syntax_style_sections], + syntax_style_sections, vec![diff_style_sections], &mut painter.output_buffer, config, prefix, - style::NO_BACKGROUND_COLOR_STYLE_MODIFIER, + config.zero_style, + config.zero_style, None, ); state diff --git a/src/draw.rs b/src/draw.rs index c6a6b84c..9b267df4 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,21 +1,20 @@ +/// This lower-level module works directly with ansi_term::Style, rather than Delta's higher-level style::Style. use std::io::Write; +use ansi_term; use box_drawing; use console::strip_ansi_codes; -use syntect::highlighting::Color; use unicode_width::UnicodeWidthStr; -use crate::paint; - /// Write text to stream, surrounded by a box, leaving the cursor just /// beyond the bottom right corner. pub fn write_boxed( writer: &mut dyn Write, text: &str, _line_width: usize, // ignored - color: Color, + text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { let up_left = if heavy { box_drawing::heavy::UP_LEFT @@ -23,12 +22,8 @@ pub fn write_boxed( box_drawing::light::UP_LEFT }; let box_width = UnicodeWidthStr::width(strip_ansi_codes(text).as_ref()) + 1; - write_boxed_partial(writer, text, box_width, color, heavy, true_color)?; - write!( - writer, - "{}", - paint::paint_text_foreground(up_left, color, true_color) - )?; + write_boxed_partial(writer, text, box_width, text_style, decoration_style, heavy)?; + write!(writer, "{}", decoration_style.paint(up_left))?; Ok(()) } @@ -38,12 +33,19 @@ pub fn write_boxed_with_line( writer: &mut dyn Write, text: &str, line_width: usize, - color: Color, + text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { let box_width = UnicodeWidthStr::width(strip_ansi_codes(text).as_ref()) + 1; - write_boxed_with_horizontal_whisker(writer, text, box_width, color, heavy, true_color)?; + write_boxed_with_horizontal_whisker( + writer, + text, + box_width, + text_style, + decoration_style, + heavy, + )?; write_horizontal_line( writer, if line_width > box_width { @@ -51,9 +53,9 @@ pub fn write_boxed_with_line( } else { 0 }, - color, + text_style, + decoration_style, heavy, - true_color, )?; write!(writer, "\n")?; Ok(()) @@ -63,16 +65,12 @@ pub fn write_underlined( writer: &mut dyn Write, text: &str, line_width: usize, - color: Color, + text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { - writeln!( - writer, - "{}", - paint::paint_text_foreground(text, color, true_color) - )?; - write_horizontal_line(writer, line_width - 1, color, heavy, true_color)?; + writeln!(writer, "{}", text_style.paint(text))?; + write_horizontal_line(writer, line_width - 1, text_style, decoration_style, heavy)?; write!(writer, "\n")?; Ok(()) } @@ -80,9 +78,9 @@ pub fn write_underlined( fn write_horizontal_line( writer: &mut dyn Write, line_width: usize, - color: Color, + _text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { let horizontal = if heavy { box_drawing::heavy::HORIZONTAL @@ -92,7 +90,7 @@ fn write_horizontal_line( write!( writer, "{}", - paint::paint_text_foreground(&horizontal.repeat(line_width), color, true_color) + decoration_style.paint(horizontal.repeat(line_width)) ) } @@ -100,21 +98,17 @@ pub fn write_boxed_with_horizontal_whisker( writer: &mut dyn Write, text: &str, box_width: usize, - color: Color, + text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { let up_horizontal = if heavy { box_drawing::heavy::UP_HORIZONTAL } else { box_drawing::light::UP_HORIZONTAL }; - write_boxed_partial(writer, text, box_width, color, heavy, true_color)?; - write!( - writer, - "{}", - paint::paint_text_foreground(up_horizontal, color, true_color) - )?; + write_boxed_partial(writer, text, box_width, text_style, decoration_style, heavy)?; + write!(writer, "{}", decoration_style.paint(up_horizontal))?; Ok(()) } @@ -122,9 +116,9 @@ fn write_boxed_partial( writer: &mut dyn Write, text: &str, box_width: usize, - color: Color, + text_style: ansi_term::Style, + decoration_style: ansi_term::Style, heavy: bool, - true_color: bool, ) -> std::io::Result<()> { let horizontal = if heavy { box_drawing::heavy::HORIZONTAL @@ -146,10 +140,10 @@ fn write_boxed_partial( write!( writer, "{}{}\n{} {}\n{}", - paint::paint_text_foreground(&horizontal_edge, color, true_color), - paint::paint_text_foreground(down_left, color, true_color), - paint::paint_text_foreground(text, color, true_color), - paint::paint_text_foreground(vertical, color, true_color), - paint::paint_text_foreground(&horizontal_edge, color, true_color), + decoration_style.paint(&horizontal_edge), + decoration_style.paint(down_left), + text_style.paint(text), + decoration_style.paint(vertical), + decoration_style.paint(&horizontal_edge), ) } diff --git a/src/edits.rs b/src/edits.rs index 2e16d2be..f19d25b0 100644 --- a/src/edits.rs +++ b/src/edits.rs @@ -436,22 +436,6 @@ mod tests { ], 0.66) } - #[test] - fn test_infer_edits_6_1() { - let (after, before) = ( - " i += c0.len();", - " .fold(0, |offset, ((_, c0), (_, _))| offset + c0.len())", - ); - println!(" before: {}", before); - println!(" after : {}", after); - println!("tokenized before: {:?}", tokenize(before)); - println!("tokenized after : {:?}", tokenize(after)); - println!( - "distance: {:?}", - align::Alignment::new(tokenize(before), tokenize(after)).distance_parts() - ); - } - #[test] fn test_infer_edits_7() { assert_edits( diff --git a/src/main.rs b/src/main.rs index 00c77950..ca0197c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,17 +11,18 @@ mod edits; mod env; mod paint; mod parse; +mod rewrite; mod style; +mod syntect_color; mod tests; use std::io::{self, ErrorKind, Read, Write}; use std::process; -use ansi_term; +use ansi_term::{self, Color}; use atty; use bytelines::ByteLinesReader; use structopt::StructOpt; -use syntect::highlighting::{Color, FontStyle, Style}; use crate::bat::assets::{list_languages, HighlightingAssets}; use crate::bat::output::{OutputType, PagingMode}; @@ -79,40 +80,18 @@ fn show_background_colors(config: &config::Config) { --minus-emph-color=\"{minus_emph_color}\" \ --plus-color=\"{plus_color}\" \ --plus-emph-color=\"{plus_emph_color}\"", - minus_color = get_painted_rgb_string( - config.minus_style_modifier.background.unwrap(), - config.true_color - ), - minus_emph_color = get_painted_rgb_string( - config.minus_emph_style_modifier.background.unwrap(), - config.true_color - ), - plus_color = get_painted_rgb_string( - config.plus_style_modifier.background.unwrap(), - config.true_color - ), - plus_emph_color = get_painted_rgb_string( - config.plus_emph_style_modifier.background.unwrap(), - config.true_color - ), + minus_color = + get_painted_rgb_string(config.minus_style.ansi_term_style.background.unwrap()), + minus_emph_color = + get_painted_rgb_string(config.minus_emph_style.ansi_term_style.background.unwrap()), + plus_color = get_painted_rgb_string(config.plus_style.ansi_term_style.background.unwrap()), + plus_emph_color = + get_painted_rgb_string(config.plus_emph_style.ansi_term_style.background.unwrap()), ) } -fn get_painted_rgb_string(color: Color, true_color: bool) -> String { - let mut string = String::new(); - let style = Style { - foreground: style::NO_COLOR, - background: color, - font_style: FontStyle::empty(), - }; - paint::paint_text( - &format!("#{:02x?}{:02x?}{:02x?}", color.r, color.g, color.b), - style, - &mut string, - true_color, - ); - string.push_str("\x1b[0m"); // reset - string +fn get_painted_rgb_string(color: Color) -> String { + color.paint(format!("{:?}", color)).to_string() } fn list_themes() -> std::io::Result<()> { @@ -154,12 +133,12 @@ index f38589a..0f1bb83 100644 } writeln!(stdout, "\n\nTheme: {}\n", style.paint(theme))?; - let mut config = cli::process_command_line_arguments(cli::Opt { + let config = cli::process_command_line_arguments(cli::Opt { theme: Some(theme.to_string()), + file_decoration_style: "omit".to_string(), + hunk_header_style: "omit".to_string(), ..opt.clone() }); - config.file_style = cli::SectionStyle::Omit; - config.hunk_style = cli::SectionStyle::Omit; let mut output_type = OutputType::from_mode(PagingMode::QuitIfOneScreen, None).unwrap(); let mut writer = output_type.handle().unwrap(); diff --git a/src/paint.rs b/src/paint.rs index 2b9969eb..85939890 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -1,17 +1,15 @@ use std::io::Write; -use std::str::FromStr; use ansi_term; use syntect::easy::HighlightLines; -use syntect::highlighting::{Color, Style, StyleModifier}; +use syntect::highlighting::Style as SyntectStyle; use syntect::parsing::{SyntaxReference, SyntaxSet}; -use crate::bat::terminal::to_ansi_color; use crate::config; use crate::delta::State; use crate::edits; use crate::paint::superimpose_style_sections::superimpose_style_sections; -use crate::style; +use crate::style::Style; pub const ANSI_CSI_ERASE_IN_LINE: &str = "\x1b[K"; pub const ANSI_SGR_RESET: &str = "\x1b[0m"; @@ -61,13 +59,13 @@ impl<'a> Painter<'a> { pub fn paint_buffered_lines(&mut self) { let minus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines( &self.minus_lines, - self.config.should_syntax_highlight(&State::HunkMinus), + &State::HunkMinus, &mut self.highlighter, self.config, ); let plus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines( &self.plus_lines, - self.config.should_syntax_highlight(&State::HunkPlus), + &State::HunkPlus, &mut self.highlighter, self.config, ); @@ -81,7 +79,8 @@ impl<'a> Painter<'a> { &mut self.output_buffer, self.config, self.config.minus_line_marker, - self.config.minus_style_modifier, + self.config.minus_style, + self.config.minus_non_emph_style, None, ); } @@ -92,7 +91,8 @@ impl<'a> Painter<'a> { &mut self.output_buffer, self.config, self.config.plus_line_marker, - self.config.plus_style_modifier, + self.config.plus_style, + self.config.plus_non_emph_style, None, ); } @@ -103,34 +103,51 @@ impl<'a> Painter<'a> { /// Superimpose background styles and foreground syntax /// highlighting styles, and write colored lines to output buffer. pub fn paint_lines( - syntax_style_sections: Vec>, - diff_style_sections: Vec>, + syntax_style_sections: Vec>, + diff_style_sections: Vec>, output_buffer: &mut String, config: &config::Config, prefix: &str, - background_style_modifier: StyleModifier, + base_style: Style, + non_emph_style: Style, background_color_extends_to_terminal_width: Option, ) { - let background_style = config.no_style.apply(background_style_modifier); - let background_ansi_style = to_ansi_style(background_style, config.true_color); + // There's some unfortunate hackery going on here for two reasons: + // + // 1. The prefix needs to be injected into the output stream. We paint + // this with whatever style the line starts with. + // + // 2. We must ensure that we fill rightwards with the appropriate + // non-emph background color. In that case we don't use the last + // style of the line, because this might be emph. for (syntax_sections, diff_sections) in syntax_style_sections.iter().zip(diff_style_sections.iter()) { + let right_fill_style = if diff_sections.len() > 1 { + non_emph_style // line contains an emph section + } else { + base_style + }; let mut ansi_strings = Vec::new(); - if prefix != "" { - ansi_strings.push(background_ansi_style.paint(prefix)); - } - let mut dropped_prefix = prefix == ""; // TODO: Hack - for (style, mut text) in superimpose_style_sections(syntax_sections, diff_sections) { - if !dropped_prefix { - if text.len() > 0 { - text.remove(0); + let mut handled_prefix = false; + for (style, mut text) in superimpose_style_sections( + syntax_sections, + diff_sections, + config.true_color, + config.null_syntect_style, + ) { + if !handled_prefix { + if prefix != "" { + ansi_strings.push(style.ansi_term_style.paint(prefix)); + if text.len() > 0 { + text.remove(0); + } } - dropped_prefix = true; + handled_prefix = true; } - ansi_strings.push(to_ansi_style(style, config.true_color).paint(text)); + ansi_strings.push(style.ansi_term_style.paint(text)); } - ansi_strings.push(background_ansi_style.paint("")); + ansi_strings.push(right_fill_style.ansi_term_style.paint("")); let line = &mut ansi_term::ANSIStrings(&ansi_strings).to_string(); let background_color_extends_to_terminal_width = match background_color_extends_to_terminal_width { @@ -162,139 +179,118 @@ impl<'a> Painter<'a> { Ok(()) } - fn get_syntax_style_sections_for_lines<'s>( - lines: &'s [String], - should_syntax_highlight: bool, + pub fn should_compute_syntax_highlighting(state: &State, config: &config::Config) -> bool { + if config.theme.is_none() { + return false; + } + match state { + State::HunkMinus => { + config.minus_style.is_syntax_highlighted + || config.minus_emph_style.is_syntax_highlighted + } + State::HunkZero => config.zero_style.is_syntax_highlighted, + State::HunkPlus => { + config.plus_style.is_syntax_highlighted + || config.plus_emph_style.is_syntax_highlighted + } + State::HunkHeader => true, + _ => panic!( + "should_compute_syntax_highlighting is undefined for state {:?}", + state + ), + } + } + + pub fn get_syntax_style_sections_for_lines<'s>( + lines: &'s Vec, + state: &State, highlighter: &mut HighlightLines, config: &config::Config, - ) -> Vec> { + ) -> Vec> { + let fake = !Painter::should_compute_syntax_highlighting(state, config); let mut line_sections = Vec::new(); for line in lines.iter() { - line_sections.push(Painter::get_line_syntax_style_sections( - line, - should_syntax_highlight, - highlighter, - &config, - )); + if fake { + line_sections.push(vec![(config.null_syntect_style, line.as_str())]) + } else { + line_sections.push(highlighter.highlight(line, &config.syntax_set)) + } } line_sections } - pub fn get_line_syntax_style_sections( - line: &'a str, - should_syntax_highlight: bool, - highlighter: &mut HighlightLines, - config: &config::Config, - ) -> Vec<(Style, &'a str)> { - if should_syntax_highlight && config.theme.is_some() { - highlighter.highlight(line, &config.syntax_set) - } else { - vec![(config.no_style, line)] - } - } - /// Set background styles to represent diff for minus and plus lines in buffer. fn get_diff_style_sections<'b>( - minus_lines: &'b [String], - plus_lines: &'b [String], + minus_lines: &'b Vec, + plus_lines: &'b Vec, config: &config::Config, - ) -> ( - Vec>, - Vec>, - ) { - edits::infer_edits( + ) -> (Vec>, Vec>) { + let mut diff_sections = edits::infer_edits( minus_lines, plus_lines, - config.minus_style_modifier, - config.minus_emph_style_modifier, - config.plus_style_modifier, - config.plus_emph_style_modifier, + config.minus_style, + config.minus_emph_style, + config.plus_style, + config.plus_emph_style, config.max_line_distance, config.max_line_distance_for_naively_paired_lines, - ) + ); + if config.minus_non_emph_style != config.minus_emph_style { + Self::set_non_emph_styles( + &mut diff_sections.0, + config.minus_emph_style, + config.minus_non_emph_style, + ); + } + if config.plus_non_emph_style != config.plus_emph_style { + Self::set_non_emph_styles( + &mut diff_sections.1, + config.plus_emph_style, + config.plus_non_emph_style, + ); + } + diff_sections } -} -pub fn to_ansi_style(style: Style, true_color: bool) -> ansi_term::Style { - let mut ansi_style = ansi_term::Style::new(); - if style.background != style::NO_COLOR { - ansi_style = ansi_style.on(to_ansi_color(style.background, true_color)); + fn set_non_emph_styles( + style_sections: &mut Vec>, + emph_style: Style, + non_emph_style: Style, + ) { + for line_sections in style_sections { + if line_sections.len() > 1 { + for section in line_sections.iter_mut() { + if section.0 != emph_style { + *section = (non_emph_style, section.1); + } + } + } + } } - if style.foreground != style::NO_COLOR { - ansi_style = ansi_style.fg(to_ansi_color(style.foreground, true_color)); - } - ansi_style -} - -/// Write section text to buffer with shell escape codes specifying foreground and background color. -pub fn paint_text(text: &str, style: Style, output_buffer: &mut String, true_color: bool) { - if text.is_empty() { - return; - } - let ansi_style = to_ansi_style(style, true_color); - output_buffer.push_str(&ansi_style.paint(text).to_string()); -} - -/// Return text together with shell escape codes specifying the foreground color. -pub fn paint_text_foreground(text: &str, color: Color, true_color: bool) -> String { - to_ansi_color(color, true_color).paint(text).to_string() -} - -#[allow(dead_code)] -pub fn paint_text_background(text: &str, color: Color, true_color: bool) -> String { - let style = ansi_term::Style::new().on(to_ansi_color(color, true_color)); - style.paint(text).to_string() -} - -// See -// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit -pub fn ansi_color_name_to_number(name: &str) -> Option { - match name.to_lowercase().as_ref() { - "black" => Some(0), - "red" => Some(1), - "green" => Some(2), - "yellow" => Some(3), - "blue" => Some(4), - "magenta" => Some(5), - "purple" => Some(5), - "cyan" => Some(6), - "white" => Some(7), - "bright-black" => Some(8), - "bright-red" => Some(9), - "bright-green" => Some(10), - "bright-yellow" => Some(11), - "bright-blue" => Some(12), - "bright-magenta" => Some(13), - "bright-purple" => Some(13), - "bright-cyan" => Some(14), - "bright-white" => Some(15), - _ => None, - } -} - -pub fn color_from_ansi_name(name: &str) -> Option { - ansi_color_name_to_number(name).and_then(color_from_ansi_number) -} - -/// Convert 8-bit ANSI code to #RGBA string with ANSI code in red channel and 0 in alpha channel. -// See https://github.com/sharkdp/bat/pull/543 -pub fn color_from_ansi_number(n: u8) -> Option { - Color::from_str(&format!("#{:02x}000000", n)).ok() } mod superimpose_style_sections { - use syntect::highlighting::{Style, StyleModifier}; + use syntect::highlighting::Style as SyntectStyle; + + use crate::bat::terminal::to_ansi_color; + use crate::style::Style; pub fn superimpose_style_sections( - sections_1: &[(Style, &str)], - sections_2: &[(StyleModifier, &str)], + sections_1: &[(SyntectStyle, &str)], + sections_2: &[(Style, &str)], + true_color: bool, + null_syntect_style: SyntectStyle, ) -> Vec<(Style, String)> { - coalesce(superimpose( - explode(sections_1) - .iter() - .zip(explode(sections_2)) - .collect::>(), - )) + coalesce( + superimpose( + explode(sections_1) + .iter() + .zip(explode(sections_2)) + .collect::>(), + ), + true_color, + null_syntect_style, + ) } fn explode(style_sections: &[(T, &str)]) -> Vec<(T, char)> @@ -311,32 +307,50 @@ mod superimpose_style_sections { } fn superimpose( - style_section_pairs: Vec<(&(Style, char), (StyleModifier, char))>, - ) -> Vec<(Style, char)> { - let mut superimposed: Vec<(Style, char)> = Vec::new(); - for ((style, char_1), (modifier, char_2)) in style_section_pairs { + style_section_pairs: Vec<(&(SyntectStyle, char), (Style, char))>, + ) -> Vec<((SyntectStyle, Style), char)> { + let mut superimposed: Vec<((SyntectStyle, Style), char)> = Vec::new(); + for ((syntax_style, char_1), (style, char_2)) in style_section_pairs { if *char_1 != char_2 { panic!( "String mismatch encountered while superimposing style sections: '{}' vs '{}'", *char_1, char_2 ) } - superimposed.push((style.apply(modifier), *char_1)); + superimposed.push(((*syntax_style, style), *char_1)); } superimposed } - fn coalesce(style_sections: Vec<(Style, char)>) -> Vec<(Style, String)> { + fn coalesce( + style_sections: Vec<((SyntectStyle, Style), char)>, + true_color: bool, + null_syntect_style: SyntectStyle, + ) -> Vec<(Style, String)> { + let make_superimposed_style = |(syntect_style, style): (SyntectStyle, Style)| { + if style.is_syntax_highlighted && syntect_style != null_syntect_style { + Style { + ansi_term_style: ansi_term::Style { + foreground: Some(to_ansi_color(syntect_style.foreground, true_color)), + ..style.ansi_term_style + }, + ..style + } + } else { + style + } + }; let mut coalesced: Vec<(Style, String)> = Vec::new(); let mut style_sections = style_sections.iter(); - if let Some((style, c)) = style_sections.next() { + if let Some((style_pair, c)) = style_sections.next() { let mut current_string = c.to_string(); - let mut current_style = style; - for (style, c) in style_sections { - if style != current_style { - coalesced.push((*current_style, current_string)); + let mut current_style_pair = style_pair; + for (style_pair, c) in style_sections { + if style_pair != current_style_pair { + let style = make_superimposed_style(*current_style_pair); + coalesced.push((style, current_string)); current_string = String::new(); - current_style = style; + current_style_pair = style_pair; } current_string.push(*c); } @@ -347,51 +361,100 @@ mod superimpose_style_sections { // highlighter to work correctly. current_string.truncate(current_string.len() - 1); } - - coalesced.push((*current_style, current_string)); + let style = make_superimposed_style(*current_style_pair); + coalesced.push((style, current_string)); } coalesced } #[cfg(test)] mod tests { - use super::*; - use syntect::highlighting::{Color, FontStyle, Style, StyleModifier}; + use lazy_static::lazy_static; - const STYLE: Style = Style { - foreground: Color::BLACK, - background: Color::BLACK, - font_style: FontStyle::BOLD, - }; - const STYLE_MODIFIER: StyleModifier = StyleModifier { - foreground: Some(Color::WHITE), - background: Some(Color::WHITE), - font_style: Some(FontStyle::UNDERLINE), - }; - const SUPERIMPOSED_STYLE: Style = Style { - foreground: Color::WHITE, - background: Color::WHITE, - font_style: FontStyle::UNDERLINE, - }; + use super::*; + use ansi_term::{self, Color}; + use syntect::highlighting::Color as SyntectColor; + use syntect::highlighting::FontStyle as SyntectFontStyle; + use syntect::highlighting::Style as SyntectStyle; + + use crate::style::Style; + + lazy_static! { + static ref SYNTAX_STYLE: SyntectStyle = SyntectStyle { + foreground: SyntectColor::BLACK, + background: SyntectColor::BLACK, + font_style: SyntectFontStyle::BOLD, + }; + } + lazy_static! { + static ref SYNTAX_HIGHLIGHTED_STYLE: Style = Style { + ansi_term_style: ansi_term::Style { + foreground: Some(Color::White), + background: Some(Color::White), + is_underline: true, + ..ansi_term::Style::new() + }, + is_syntax_highlighted: true, + decoration_style: None, + }; + } + lazy_static! { + static ref NON_SYNTAX_HIGHLIGHTED_STYLE: Style = Style { + ansi_term_style: ansi_term::Style { + foreground: Some(Color::White), + background: Some(Color::White), + is_underline: true, + ..ansi_term::Style::new() + }, + is_syntax_highlighted: false, + decoration_style: None, + }; + } + lazy_static! { + static ref SUPERIMPOSED_STYLE: Style = Style { + ansi_term_style: ansi_term::Style { + foreground: Some(to_ansi_color(SyntectColor::BLACK, true)), + background: Some(Color::White), + is_underline: true, + ..ansi_term::Style::new() + }, + is_syntax_highlighted: true, + decoration_style: None, + }; + } #[test] fn test_superimpose_style_sections_1() { - let sections_1 = vec![(STYLE, "ab")]; - let sections_2 = vec![(STYLE_MODIFIER, "ab")]; - let superimposed = vec![(SUPERIMPOSED_STYLE, "ab".to_string())]; + let sections_1 = vec![(*SYNTAX_STYLE, "ab")]; + let sections_2 = vec![(*SYNTAX_HIGHLIGHTED_STYLE, "ab")]; + let superimposed = vec![(*SUPERIMPOSED_STYLE, "ab".to_string())]; assert_eq!( - superimpose_style_sections(§ions_1, §ions_2), + superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()), superimposed ); } #[test] fn test_superimpose_style_sections_2() { - let sections_1 = vec![(STYLE, "ab")]; - let sections_2 = vec![(STYLE_MODIFIER, "a"), (STYLE_MODIFIER, "b")]; - let superimposed = vec![(SUPERIMPOSED_STYLE, String::from("ab"))]; + let sections_1 = vec![(*SYNTAX_STYLE, "ab")]; + let sections_2 = vec![ + (*SYNTAX_HIGHLIGHTED_STYLE, "a"), + (*SYNTAX_HIGHLIGHTED_STYLE, "b"), + ]; + let superimposed = vec![(*SUPERIMPOSED_STYLE, String::from("ab"))]; assert_eq!( - superimpose_style_sections(§ions_1, §ions_2), + superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()), + superimposed + ); + } + + #[test] + fn test_superimpose_style_sections_3() { + let sections_1 = vec![(*SYNTAX_STYLE, "ab")]; + let sections_2 = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab")]; + let superimposed = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab".to_string())]; + assert_eq!( + superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()), superimposed ); } @@ -407,9 +470,12 @@ mod superimpose_style_sections { #[test] fn test_superimpose() { - let x = (STYLE, 'a'); - let pairs = vec![(&x, (STYLE_MODIFIER, 'a'))]; - assert_eq!(superimpose(pairs), vec![(SUPERIMPOSED_STYLE, 'a')]); + let x = (*SYNTAX_STYLE, 'a'); + let pairs = vec![(&x, (*SYNTAX_HIGHLIGHTED_STYLE, 'a'))]; + assert_eq!( + superimpose(pairs), + vec![((*SYNTAX_STYLE, *SYNTAX_HIGHLIGHTED_STYLE), 'a')] + ); } } } diff --git a/src/rewrite.rs b/src/rewrite.rs new file mode 100644 index 00000000..a182b491 --- /dev/null +++ b/src/rewrite.rs @@ -0,0 +1,218 @@ +/// This module applies rewrite rules to the command line options, in order to +/// 1. Express deprecated usages in the new non-deprecated form +/// 2. Implement options such as --color-only which are defined to be equivalent to some set of +/// other options. +use std::process; + +use crate::cli; + +pub fn apply_rewrite_rules(opt: &mut cli::Opt) { + _rewrite_style_strings_to_honor_deprecated_minus_plus_options(opt); + _rewrite_options_to_implement_deprecated_commit_file_hunk_header_options(opt); + _rewrite_options_to_implement_color_only(opt); +} + +#[cfg(test)] +mod tests { + use std::ffi::OsString; + + use structopt::StructOpt; + + use crate::cli; + use crate::rewrite::apply_rewrite_rules; + + #[test] + fn test_default_is_stable_under_rewrites() { + let mut opt = cli::Opt::from_iter(Vec::::new()); + let before = opt.clone(); + + apply_rewrite_rules(&mut opt); + + assert_eq!(opt, before); + } + + /// Since --hunk-header-decoration-style is at its default value of "box", + /// the deprecated option is allowed to overwrite it. + #[test] + fn test_deprecated_hunk_style_is_rewritten() { + let mut opt = cli::Opt::from_iter(Vec::::new()); + opt.deprecated_hunk_style = Some("underline".to_string()); + let default = "blue box"; + assert_eq!(opt.hunk_header_decoration_style, default); + apply_rewrite_rules(&mut opt); + assert_eq!(opt.deprecated_hunk_style, None); + assert_eq!(opt.hunk_header_decoration_style, "underline"); + } + + #[test] + fn test_deprecated_hunk_style_is_not_rewritten() { + let mut opt = cli::Opt::from_iter(Vec::::new()); + opt.deprecated_hunk_style = Some("".to_string()); + let default = "blue box"; + assert_eq!(opt.hunk_header_decoration_style, default); + apply_rewrite_rules(&mut opt); + assert_eq!(opt.deprecated_hunk_style, None); + assert_eq!(opt.hunk_header_decoration_style, default); + } +} + +/// Implement --color-only +fn _rewrite_options_to_implement_color_only(opt: &mut cli::Opt) { + if opt.color_only { + opt.keep_plus_minus_markers = true; + opt.tab_width = 0; + opt.commit_decoration_style = "".to_string(); + opt.file_decoration_style = "".to_string(); + opt.hunk_header_decoration_style = "".to_string(); + } +} + +fn _rewrite_options_to_implement_deprecated_commit_file_hunk_header_options(opt: &mut cli::Opt) { + _rewrite_options_to_implement_deprecated_hunk_style_option(opt); +} + +/// Honor deprecated arguments by rewriting the canonical --*-style arguments if appropriate. +// TODO: How to avoid repeating the default values for style options here and in +// the structopt definition? +fn _rewrite_style_strings_to_honor_deprecated_minus_plus_options(opt: &mut cli::Opt) { + // If --highlight-removed was passed then we should set minus and minus emph foreground to + // "syntax", if they are still at their default values. + let deprecated_minus_foreground_arg = if opt.deprecated_highlight_minus_lines { + Some("syntax") + } else { + None + }; + + if let Some(rewritten) = _get_rewritten_minus_plus_style_string( + &opt.minus_style, + ("normal", "auto"), + ( + deprecated_minus_foreground_arg, + opt.deprecated_minus_background_color.as_deref(), + ), + "minus", + ) { + opt.minus_style = rewritten.to_string(); + } + if let Some(rewritten) = _get_rewritten_minus_plus_style_string( + &opt.minus_emph_style, + ("normal", "auto"), + ( + deprecated_minus_foreground_arg, + opt.deprecated_minus_emph_background_color.as_deref(), + ), + "minus-emph", + ) { + opt.minus_emph_style = rewritten.to_string(); + } + if let Some(rewritten) = _get_rewritten_minus_plus_style_string( + &opt.plus_style, + ("syntax", "auto"), + (None, opt.deprecated_plus_background_color.as_deref()), + "plus", + ) { + opt.plus_style = rewritten.to_string(); + } + if let Some(rewritten) = _get_rewritten_minus_plus_style_string( + &opt.plus_emph_style, + ("syntax", "auto"), + (None, opt.deprecated_plus_emph_background_color.as_deref()), + "plus-emph", + ) { + opt.plus_emph_style = rewritten.to_string(); + } +} + +fn _rewrite_options_to_implement_deprecated_hunk_style_option(opt: &mut cli::Opt) { + // Examples of how --hunk-style was originally used are + // --hunk-style box => --hunk-header-decoration-style box + // --hunk-style underline => --hunk-header-decoration-style underline + // --hunk-style plain => --hunk-header-decoration-style '' + if opt.deprecated_hunk_style.is_some() { + // As in the other cases, we only honor the deprecated option if the replacement option has + // apparently been left at its default value. + let hunk_header_decoration_default = "blue box"; + if opt.hunk_header_decoration_style != hunk_header_decoration_default { + eprintln!( + "Deprecated option --hunk-style cannot be used with --hunk-header-decoration-style. \ + Use --hunk-header-decoration-style."); + process::exit(1); + } + match opt.deprecated_hunk_style.as_deref().map(str::to_lowercase) { + Some(attr) if attr == "plain" => opt.hunk_header_decoration_style = "".to_string(), + Some(attr) if attr == "" => {} + Some(attr) => opt.hunk_header_decoration_style = attr, + None => {} + } + opt.deprecated_hunk_style = None; + } +} + +fn _get_rewritten_commit_file_hunk_header_style_string( + style_default_pair: (&str, Option<&str>), + deprecated_args_style_pair: (Option<&str>, Option<&str>), +) -> Option { + let format_style = |pair: (&str, Option<&str>)| { + format!( + "{}{}", + pair.0, + match pair.1 { + Some(s) => format!(" {}", s), + None => "".to_string(), + } + ) + }; + match deprecated_args_style_pair { + (None, None) => None, + deprecated_args_style_pair => Some(format_style(( + deprecated_args_style_pair.0.unwrap_or(style_default_pair.0), + match deprecated_args_style_pair.1 { + Some(s) => Some(s), + None => style_default_pair.1, + }, + ))), + } +} + +fn _get_rewritten_minus_plus_style_string( + style: &str, + style_default_pair: (&str, &str), + deprecated_args_style_pair: (Option<&str>, Option<&str>), + element_name: &str, +) -> Option { + let format_style = |pair: (&str, &str)| format!("{} {}", pair.0, pair.1); + match (style, deprecated_args_style_pair) { + (_, (None, None)) => None, // no rewrite + (style, deprecated_args_style_pair) if style == format_style(style_default_pair) => { + // TODO: We allow the deprecated argument values to have effect if + // the style argument value is equal to its default value. This is + // non-ideal, because the user may have explicitly supplied the + // style argument (i.e. it might just happen to equal the default). + Some(format_style(( + deprecated_args_style_pair.0.unwrap_or(style_default_pair.0), + deprecated_args_style_pair.1.unwrap_or(style_default_pair.1), + ))) + } + (_, (_, Some(_))) => { + eprintln!( + "--{name}-color cannot be used with --{name}-style. \ + Use --{name}-style=\"fg bg attr1 attr2 ...\" to set \ + foreground color, background color, and style attributes. \ + --{name}-color can only be used to set the background color. \ + (It is still available for backwards-compatibility.)", + name = element_name, + ); + process::exit(1); + } + (_, (Some(_), None)) => { + eprintln!( + "Deprecated option --highlight-removed cannot be used with \ + --{name}-style. Use --{name}-style=\"fg bg attr1 attr2 ...\" \ + to set foreground color, background color, and style \ + attributes.", + name = element_name, + ); + process::exit(1); + } + } +} diff --git a/src/style.rs b/src/style.rs index 80256805..5c87dc66 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,4 +1,62 @@ -use syntect::highlighting::{Color, FontStyle, Style, StyleModifier}; +use ansi_term::{self, Color}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Style { + pub ansi_term_style: ansi_term::Style, + pub is_syntax_highlighted: bool, + pub decoration_style: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DecorationStyle { + Box(ansi_term::Style), + Underline(ansi_term::Style), + Omit, +} + +impl Style { + pub fn new() -> Self { + Self { + ansi_term_style: ansi_term::Style::new(), + is_syntax_highlighted: false, + decoration_style: None, + } + } + + pub fn decoration_ansi_term_style(&self) -> Option { + match self.decoration_style { + Some(DecorationStyle::Box(style)) => Some(style), + Some(DecorationStyle::Underline(style)) => Some(style), + _ => None, + } + } +} + +// See +// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +pub fn ansi_color_name_to_number(name: &str) -> Option { + match name.to_lowercase().as_ref() { + "black" => Some(0), + "red" => Some(1), + "green" => Some(2), + "yellow" => Some(3), + "blue" => Some(4), + "magenta" => Some(5), + "purple" => Some(5), + "cyan" => Some(6), + "white" => Some(7), + "bright-black" => Some(8), + "bright-red" => Some(9), + "bright-green" => Some(10), + "bright-yellow" => Some(11), + "bright-blue" => Some(12), + "bright-magenta" => Some(13), + "bright-purple" => Some(13), + "bright-cyan" => Some(14), + "bright-white" => Some(15), + _ => None, + } +} pub const LIGHT_THEMES: [&str; 5] = [ "GitHub", @@ -19,7 +77,7 @@ pub fn is_no_syntax_highlighting_theme_name(theme_name: &str) -> bool { theme_name.to_lowercase() == "none" } -pub fn get_minus_color_default(is_light_mode: bool, is_true_color: bool) -> Color { +pub fn get_minus_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { match (is_light_mode, is_true_color) { (true, true) => LIGHT_THEME_MINUS_COLOR, (true, false) => LIGHT_THEME_MINUS_COLOR_256, @@ -28,7 +86,7 @@ pub fn get_minus_color_default(is_light_mode: bool, is_true_color: bool) -> Colo } } -pub fn get_minus_emph_color_default(is_light_mode: bool, is_true_color: bool) -> Color { +pub fn get_minus_emph_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { match (is_light_mode, is_true_color) { (true, true) => LIGHT_THEME_MINUS_EMPH_COLOR, (true, false) => LIGHT_THEME_MINUS_EMPH_COLOR_256, @@ -37,7 +95,7 @@ pub fn get_minus_emph_color_default(is_light_mode: bool, is_true_color: bool) -> } } -pub fn get_plus_color_default(is_light_mode: bool, is_true_color: bool) -> Color { +pub fn get_plus_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { match (is_light_mode, is_true_color) { (true, true) => LIGHT_THEME_PLUS_COLOR, (true, false) => LIGHT_THEME_PLUS_COLOR_256, @@ -46,7 +104,7 @@ pub fn get_plus_color_default(is_light_mode: bool, is_true_color: bool) -> Color } } -pub fn get_plus_emph_color_default(is_light_mode: bool, is_true_color: bool) -> Color { +pub fn get_plus_emph_background_color_default(is_light_mode: bool, is_true_color: bool) -> Color { match (is_light_mode, is_true_color) { (true, true) => LIGHT_THEME_PLUS_EMPH_COLOR, (true, false) => LIGHT_THEME_PLUS_EMPH_COLOR_256, @@ -55,131 +113,34 @@ pub fn get_plus_emph_color_default(is_light_mode: bool, is_true_color: bool) -> } } -const LIGHT_THEME_MINUS_COLOR: Color = Color { - r: 0xff, - g: 0xe0, - b: 0xe0, - a: 0xff, -}; +const LIGHT_THEME_MINUS_COLOR: Color = Color::RGB(0xff, 0xe0, 0xe0); -const LIGHT_THEME_MINUS_COLOR_256: Color = Color { - r: 224, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const LIGHT_THEME_MINUS_COLOR_256: Color = Color::Fixed(224); -const LIGHT_THEME_MINUS_EMPH_COLOR: Color = Color { - r: 0xff, - g: 0xc0, - b: 0xc0, - a: 0xff, -}; +const LIGHT_THEME_MINUS_EMPH_COLOR: Color = Color::RGB(0xff, 0xc0, 0xc0); -const LIGHT_THEME_MINUS_EMPH_COLOR_256: Color = Color { - r: 217, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const LIGHT_THEME_MINUS_EMPH_COLOR_256: Color = Color::Fixed(217); -const LIGHT_THEME_PLUS_COLOR: Color = Color { - r: 0xd0, - g: 0xff, - b: 0xd0, - a: 0xff, -}; +const LIGHT_THEME_PLUS_COLOR: Color = Color::RGB(0xd0, 0xff, 0xd0); -const LIGHT_THEME_PLUS_COLOR_256: Color = Color { - r: 194, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const LIGHT_THEME_PLUS_COLOR_256: Color = Color::Fixed(194); -const LIGHT_THEME_PLUS_EMPH_COLOR: Color = Color { - r: 0xa0, - g: 0xef, - b: 0xa0, - a: 0xff, -}; +const LIGHT_THEME_PLUS_EMPH_COLOR: Color = Color::RGB(0xa0, 0xef, 0xa0); -const LIGHT_THEME_PLUS_EMPH_COLOR_256: Color = Color { - r: 157, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const LIGHT_THEME_PLUS_EMPH_COLOR_256: Color = Color::Fixed(157); -const DARK_THEME_MINUS_COLOR: Color = Color { - r: 0x3f, - g: 0x00, - b: 0x01, - a: 0xff, -}; +const DARK_THEME_MINUS_COLOR: Color = Color::RGB(0x3f, 0x00, 0x01); -const DARK_THEME_MINUS_COLOR_256: Color = Color { - r: 52, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const DARK_THEME_MINUS_COLOR_256: Color = Color::Fixed(52); -const DARK_THEME_MINUS_EMPH_COLOR: Color = Color { - r: 0x90, - g: 0x10, - b: 0x11, - a: 0xff, -}; +const DARK_THEME_MINUS_EMPH_COLOR: Color = Color::RGB(0x90, 0x10, 0x11); -const DARK_THEME_MINUS_EMPH_COLOR_256: Color = Color { - r: 124, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const DARK_THEME_MINUS_EMPH_COLOR_256: Color = Color::Fixed(124); -const DARK_THEME_PLUS_COLOR: Color = Color { - r: 0x00, - g: 0x28, - b: 0x00, - a: 0xff, -}; +const DARK_THEME_PLUS_COLOR: Color = Color::RGB(0x00, 0x28, 0x00); -const DARK_THEME_PLUS_COLOR_256: Color = Color { - r: 22, - g: 0x00, - b: 0x00, - a: 0x00, -}; +const DARK_THEME_PLUS_COLOR_256: Color = Color::Fixed(22); -const DARK_THEME_PLUS_EMPH_COLOR: Color = Color { - r: 0x00, - g: 0x60, - b: 0x00, - a: 0xff, -}; +const DARK_THEME_PLUS_EMPH_COLOR: Color = Color::RGB(0x00, 0x60, 0x00); -const DARK_THEME_PLUS_EMPH_COLOR_256: Color = Color { - r: 28, - g: 0x00, - b: 0x00, - a: 0x00, -}; - -/// A special color to specify that no color escape codes should be emitted. -pub const NO_COLOR: Color = Color::BLACK; - -pub fn get_no_style() -> Style { - Style { - foreground: NO_COLOR, - background: NO_COLOR, - font_style: FontStyle::empty(), - } -} - -pub const NO_BACKGROUND_COLOR_STYLE_MODIFIER: StyleModifier = StyleModifier { - foreground: None, - background: Some(NO_COLOR), - font_style: None, -}; +const DARK_THEME_PLUS_EMPH_COLOR_256: Color = Color::Fixed(28); diff --git a/src/syntect_color.rs b/src/syntect_color.rs new file mode 100644 index 00000000..6e0a8a36 --- /dev/null +++ b/src/syntect_color.rs @@ -0,0 +1,15 @@ +use std::str::FromStr; + +use syntect::highlighting::Color; + +use crate::style; + +pub fn syntect_color_from_ansi_name(name: &str) -> Option { + style::ansi_color_name_to_number(name).and_then(syntect_color_from_ansi_number) +} + +/// Convert 8-bit ANSI code to #RGBA string with ANSI code in red channel and 0 in alpha channel. +// See https://github.com/sharkdp/bat/pull/543 +pub fn syntect_color_from_ansi_number(n: u8) -> Option { + Color::from_str(&format!("#{:02x}000000", n)).ok() +} diff --git a/src/tests/ansi_test_utils.rs b/src/tests/ansi_test_utils.rs index 5167999c..04e0eeda 100644 --- a/src/tests/ansi_test_utils.rs +++ b/src/tests/ansi_test_utils.rs @@ -1,89 +1,55 @@ #[cfg(test)] pub mod ansi_test_utils { - use ansi_parser::{self, AnsiParser}; + use ansi_term; use console::strip_ansi_codes; - use itertools::Itertools; - use syntect::highlighting::StyleModifier; - use crate::config::{ColorLayer::*, Config}; - use crate::delta::State; + use crate::config::{color_from_rgb_or_ansi_code, Config}; use crate::paint; + use crate::style::Style; - use ansi_parser::AnsiSequence::*; - use ansi_parser::Output::*; - - // Note that ansi_parser seems to be parsing 24-bit sequences as TextBlock - // rather than Escape(SetGraphicsMode). As a workaround, we examime the - // TextBlock string value using functions such as - // string_has_some_background_color and string_has_some_foreground_color. - - pub fn is_syntax_highlighted(line: &str) -> bool { - line.ansi_parse() - .filter(|tok| match tok { - TextBlock(s) => string_has_some_foreground_color(s), - Escape(SetGraphicsMode(parameters)) => parameters[0] == 38, - _ => false, - }) - .next() - .is_some() + pub fn has_foreground_color(string: &str, color: ansi_term::Color) -> bool { + let style = ansi_term::Style::default().fg(color); + string.starts_with(&style.prefix().to_string()) } - pub fn line_has_background_color(line: &str, state: &State, config: &Config) -> bool { - line.ansi_parse() - .filter(|tok| match tok { - TextBlock(s) => string_has_background_color(s, state, config), - _ => false, - }) - .next() - .is_some() + pub fn assert_line_has_foreground_color( + output: &str, + line_number: usize, + expected_prefix: &str, + expected_color: &str, + config: &Config, + ) { + let line = output.lines().nth(line_number).unwrap(); + assert!(strip_ansi_codes(line).starts_with(expected_prefix)); + assert!(has_foreground_color( + line, + color_from_rgb_or_ansi_code(expected_color, config.true_color) + )); } - pub fn line_has_no_background_color(line: &str) -> bool { - line.ansi_parse() - .filter(|tok| match tok { - TextBlock(s) => string_has_some_background_color(s), - _ => false, - }) - .next() - .is_none() + pub fn assert_line_has_no_color(output: &str, line_number: usize, expected_prefix: &str) { + let line = output.lines().nth(line_number).unwrap(); + let stripped_line = strip_ansi_codes(line); + assert!(stripped_line.starts_with(expected_prefix)); + assert_eq!(line, stripped_line); } - fn string_has_background_color(string: &str, state: &State, config: &Config) -> bool { - let painted = paint::paint_text_background( - "", - config - .get_color(state, Background) - .unwrap_or_else(|| panic!("state {:?} does not have a background color", state)), - true, - ); - let ansi_sequence = painted - .trim_end_matches(paint::ANSI_SGR_RESET) - .trim_end_matches("m"); - string.starts_with(ansi_sequence) - } - - fn string_has_some_background_color(s: &str) -> bool { - s.starts_with("\x1b[48;") - } - - fn string_has_some_foreground_color(s: &str) -> bool { - s.starts_with("\x1b[38;") - } - - pub fn assert_line_has_expected_ansi_sequences(line: &str, expected: &Vec<(&str, &str)>) { - let parsed_line = line.ansi_parse().filter(|token| match token { - Escape(SetGraphicsMode(parameters)) if parameters == &vec![0 as u32] => false, - _ => true, - }); - for ((expected_ansi_sequence, _), ref token) in expected.iter().zip_eq(parsed_line) { - match token { - TextBlock(s) => { - assert!(s.starts_with(*expected_ansi_sequence)); - } - Escape(SetGraphicsMode(parameters)) => assert_eq!(parameters, &vec![0 as u32]), - _ => panic!("Unexpected token: {:?}", token), - } - } + /// Assert that the specified line number of output (a) matches + /// `expected_prefix` and (b) for the length of expected_prefix is + /// syntax-highlighted according to `language_extension`. + pub fn assert_line_is_syntax_highlighted( + output: &str, + line_number: usize, + expected_prefix: &str, + language_extension: &str, + config: &Config, + ) { + let line = output.lines().nth(line_number).unwrap(); + let stripped_line = &strip_ansi_codes(line); + assert!(stripped_line.starts_with(expected_prefix)); + let painted_line = paint_line(expected_prefix, language_extension, config); + // remove trailing newline appended by paint::paint_lines. + assert!(line.starts_with(painted_line.trim_end())); } pub fn assert_has_color_other_than_plus_color(string: &str, config: &Config) { @@ -102,18 +68,38 @@ pub mod ansi_test_utils { pub fn get_color_variants(string: &str, config: &Config) -> (String, String) { let string_without_any_color = strip_ansi_codes(string).to_string(); - let string_with_plus_color_only = paint_text( - &string_without_any_color, - config.plus_style_modifier, - config, - ); - (string_without_any_color, string_with_plus_color_only) + let string_with_plus_color_only = config + .plus_style + .ansi_term_style + .paint(&string_without_any_color); + ( + string_without_any_color.to_string(), + string_with_plus_color_only.to_string(), + ) } - fn paint_text(input: &str, style_modifier: StyleModifier, config: &Config) -> String { - let mut output = String::new(); - let style = config.no_style.apply(style_modifier); - paint::paint_text(&input, style, &mut output, config.true_color); - output + pub fn paint_line(line: &str, language_extension: &str, config: &Config) -> String { + let mut output_buffer = String::new(); + let mut unused_writer = Vec::::new(); + let mut painter = paint::Painter::new(&mut unused_writer, config); + let syntax_highlighted_style = Style { + is_syntax_highlighted: true, + ..Style::new() + }; + painter.set_syntax(Some(language_extension)); + painter.set_highlighter(); + let lines = vec![line]; + let syntax_style_sections = painter.highlighter.highlight(line, &config.syntax_set); + paint::Painter::paint_lines( + vec![syntax_style_sections], + vec![vec![(syntax_highlighted_style, lines[0])]], + &mut output_buffer, + config, + "", + config.null_style, + config.null_style, + None, + ); + output_buffer } } diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs index 6836d85d..62ce041f 100644 --- a/src/tests/integration_test_utils.rs +++ b/src/tests/integration_test_utils.rs @@ -1,55 +1,32 @@ #[cfg(test)] pub mod integration_test_utils { + use std::ffi::OsString; + use std::io::BufReader; + use bytelines::ByteLines; use console::strip_ansi_codes; - use std::io::BufReader; + use structopt::StructOpt; use crate::cli; use crate::config; use crate::delta::delta; pub fn get_command_line_options() -> cli::Opt { - cli::Opt { - light: false, - dark: false, - minus_color: None, - minus_emph_color: None, - minus_foreground_color: None, - minus_emph_foreground_color: None, - plus_color: None, - plus_emph_color: None, - plus_foreground_color: None, - plus_emph_foreground_color: None, - color_only: false, - keep_plus_minus_markers: false, - theme: None, - lines_to_be_syntax_highlighted: "0+".to_string(), - highlight_minus_lines: false, - commit_style: cli::SectionStyle::Plain, - commit_color: "Yellow".to_string(), - file_style: cli::SectionStyle::Underline, - file_color: "Blue".to_string(), - hunk_style: cli::SectionStyle::Box, - hunk_color: "blue".to_string(), - true_color: "always".to_string(), - width: Some("variable".to_string()), - paging_mode: "auto".to_string(), - tab_width: 4, - show_background_colors: false, - list_languages: false, - list_theme_names: false, - list_themes: false, - max_line_distance: 0.3, - } + let mut opt = cli::Opt::from_iter(Vec::::new()); + opt.theme = None; // TODO: Why does opt.theme have the value Some("")? + opt.width = Some("variable".to_string()); + opt } pub fn get_line_of_code_from_delta<'a>( input: &str, + line_number: usize, + expected_text: &str, options: cli::Opt, ) -> (String, config::Config<'a>) { let (output, config) = run_delta(&input, options); - let line_of_code = output.lines().nth(12).unwrap(); - assert!(strip_ansi_codes(line_of_code) == " class X:"); + let line_of_code = output.lines().nth(line_number).unwrap(); + assert!(strip_ansi_codes(line_of_code) == expected_text); (line_of_code.to_string(), config) } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index afa09977..f95a9ef5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,4 +1,3 @@ pub mod ansi_test_utils; pub mod integration_test_utils; pub mod test_example_diffs; -pub mod test_hunk_highlighting; diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs index f46e2169..592fa4c0 100644 --- a/src/tests/test_example_diffs.rs +++ b/src/tests/test_example_diffs.rs @@ -1,8 +1,9 @@ #[cfg(test)] mod tests { + use console::strip_ansi_codes; + use crate::tests::ansi_test_utils::ansi_test_utils; use crate::tests::integration_test_utils::integration_test_utils; - use console::strip_ansi_codes; #[test] fn test_added_file() { @@ -47,8 +48,12 @@ mod tests { fn test_recognized_file_type() { // In addition to the background color, the code has language syntax highlighting. let options = integration_test_utils::get_command_line_options(); - let (output, config) = - integration_test_utils::get_line_of_code_from_delta(&ADDED_FILE_INPUT, options); + let (output, config) = integration_test_utils::get_line_of_code_from_delta( + &ADDED_FILE_INPUT, + 12, + " class X:", + options, + ); ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config); } @@ -58,7 +63,8 @@ mod tests { // .txt syntax under the theme. let options = integration_test_utils::get_command_line_options(); let input = ADDED_FILE_INPUT.replace("a.py", "a"); - let (output, config) = integration_test_utils::get_line_of_code_from_delta(&input, options); + let (output, config) = + integration_test_utils::get_line_of_code_from_delta(&input, 12, " class X:", options); ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config); } @@ -69,7 +75,8 @@ mod tests { let mut options = integration_test_utils::get_command_line_options(); options.theme = Some("none".to_string()); let input = ADDED_FILE_INPUT.replace("a.py", "a"); - let (output, config) = integration_test_utils::get_line_of_code_from_delta(&input, options); + let (output, config) = + integration_test_utils::get_line_of_code_from_delta(&input, 12, " class X:", options); ansi_test_utils::assert_has_plus_color_only(&output, &config); } @@ -196,6 +203,299 @@ mod tests { assert!(output.contains("\n Subject: [PATCH] Init\n")); } + #[test] + fn test_commit_style_plain() { + let mut options = integration_test_utils::get_command_line_options(); + options.commit_decoration_style = "".to_string(); + // TODO: --commit-color has no effect in conjunction with --commit-style plain + let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_no_color( + &output, + 0, + "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e", + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + "\ +commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e +" + )); + } + + #[test] + fn test_commit_style_box() { + let mut options = integration_test_utils::get_command_line_options(); + options.commit_style = "blue".to_string(); + options.commit_decoration_style = "blue box".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 0, + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓", + "blue", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 1, + "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e ┃", + "blue", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 2, + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━", + "blue", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + "\ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e ┃ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━" + )); + } + + #[test] + fn test_commit_style_underline() { + let mut options = integration_test_utils::get_command_line_options(); + options.commit_style = "yellow".to_string(); + options.commit_decoration_style = "yellow underline".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 0, + "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e", + "yellow", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 1, + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", + "yellow", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + "\ +commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + )); + } + + #[test] + fn test_file_style_plain() { + let mut options = integration_test_utils::get_command_line_options(); + options.file_decoration_style = "".to_string(); + // TODO: --file-color has no effect in conjunction with --file-style plain + let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + for (i, line) in vec![ + "diff --git a/src/align.rs b/src/align.rs", + "index 8e37a9e..6ce4863 100644", + "--- a/src/align.rs", + "+++ b/src/align.rs", + ] + .iter() + .enumerate() + { + ansi_test_utils::assert_line_has_no_color(&output, 6 + i, line); + } + let output = strip_ansi_codes(&output); + assert!(output.contains( + " +diff --git a/src/align.rs b/src/align.rs +index 8e37a9e..6ce4863 100644 +--- a/src/align.rs ++++ b/src/align.rs +" + )); + } + + #[test] + fn test_file_style_box() { + let mut options = integration_test_utils::get_command_line_options(); + options.file_style = "green".to_string(); + options.file_decoration_style = "green box".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 7, + "─────────────┐", + "green", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 8, + "src/align.rs │", + "green", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 9, + "─────────────┴─", + "green", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + " +─────────────┐ +src/align.rs │ +─────────────┴─" + )); + } + + #[test] + fn test_file_style_underline() { + let mut options = integration_test_utils::get_command_line_options(); + options.file_style = "magenta".to_string(); + options.file_decoration_style = "magenta underline".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 7, + "src/align.rs", + "magenta", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 8, + "────────────", + "magenta", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + " +src/align.rs +────────────" + )); + } + + #[test] + fn test_hunk_style_plain() { + let mut options = integration_test_utils::get_command_line_options(); + options.hunk_header_decoration_style = "".to_string(); + // TODO: --hunk-color has no effect in conjunction with --hunk-style plain + let (output, _) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_no_color( + &output, + 9, + "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {", + ); + let output = strip_ansi_codes(&output); + assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {")); + } + + #[test] + fn test_hunk_style_box() { + let mut options = integration_test_utils::get_command_line_options(); + options.hunk_header_decoration_style = "white box".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 9, + "──────────────────────────┐", + "white", + &config, + ); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 11, + "──────────────────────────┘", + "white", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + " +──────────────────────────┐ + impl<'a> Alignment<'a> { │ +──────────────────────────┘ +" + )); + } + + #[test] + fn test_hunk_style_underline() { + let mut options = integration_test_utils::get_command_line_options(); + options.hunk_header_decoration_style = "black underline".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_foreground_color( + &output, + 10, + "─────────────────────────", + "black", + &config, + ); + let output = strip_ansi_codes(&output); + assert!(output.contains( + " + impl<'a> Alignment<'a> { +─────────────────────────" + )); + } + + #[test] + fn test_hunk_style_box_with_syntax_highlighting() { + let mut options = integration_test_utils::get_command_line_options(); + options.hunk_header_style = "syntax".to_string(); + // For this test we are currently forced to disable styling of the decoration, since + // otherwise it will confuse assert_line_is_syntax_highlighted. + options.hunk_header_decoration_style = "box".to_string(); + let (output, config) = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, options); + ansi_test_utils::assert_line_has_no_color(&output, 9, "──────────────────────────┐"); + ansi_test_utils::assert_line_is_syntax_highlighted( + &output, + 10, + " impl<'a> Alignment<'a> {", + "rs", + &config, + ); + ansi_test_utils::assert_line_has_no_color(&output, 11, "──────────────────────────┘"); + let output = strip_ansi_codes(&output); + assert!(output.contains( + " +──────────────────────────┐ + impl<'a> Alignment<'a> { │ +──────────────────────────┘ +" + )); + } + + const GIT_DIFF_SINGLE_HUNK: &str = "\ +commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e +Author: Dan Davison +Date: Thu May 14 11:13:17 2020 -0400 + + rustfmt + +diff --git a/src/align.rs b/src/align.rs +index 8e37a9e..6ce4863 100644 +--- a/src/align.rs ++++ b/src/align.rs +@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { + + for (i, x_i) in self.x.iter().enumerate() { + for (j, y_j) in self.y.iter().enumerate() { +- let (left, diag, up) = ( +- self.index(i, j + 1), +- self.index(i, j), +- self.index(i + 1, j), +- ); ++ let (left, diag, up) = ++ (self.index(i, j + 1), self.index(i, j), self.index(i + 1, j)); + let candidates = [ + Cell { + parent: left, +"; + const DIFF_IN_DIFF: &str = "\ diff --git a/0001-Init.patch b/0001-Init.patch deleted file mode 100644 diff --git a/src/tests/test_hunk_highlighting.rs b/src/tests/test_hunk_highlighting.rs deleted file mode 100644 index 730218c0..00000000 --- a/src/tests/test_hunk_highlighting.rs +++ /dev/null @@ -1,187 +0,0 @@ -#[cfg(test)] -mod tests { - use itertools::Itertools; - - use crate::cli; - use crate::config::ColorLayer::*; - use crate::delta::State; - use crate::paint; - use crate::tests::ansi_test_utils::ansi_test_utils; - use crate::tests::integration_test_utils::integration_test_utils; - - const VERBOSE: bool = false; - - #[test] - fn test_hunk_highlighting() { - let mut options = integration_test_utils::get_command_line_options(); - options.theme = Some("GitHub".to_string()); - options.max_line_distance = 1.0; - options.minus_emph_color = Some("#ffa0a0".to_string()); - options.plus_emph_color = Some("#80ef80".to_string()); - for minus_foreground_color in vec![None, Some("green".to_string())] { - options.minus_foreground_color = minus_foreground_color; - for minus_emph_foreground_color in vec![None, Some("#80ef80".to_string())] { - options.minus_emph_foreground_color = minus_emph_foreground_color; - for plus_foreground_color in vec![None, Some("red".to_string())] { - options.plus_foreground_color = plus_foreground_color; - for plus_emph_foreground_color in vec![None, Some("#ffa0a0".to_string())] { - options.plus_emph_foreground_color = plus_emph_foreground_color; - for lines_to_be_syntax_highlighted in vec!["none", "all"] { - options.lines_to_be_syntax_highlighted = - lines_to_be_syntax_highlighted.to_string(); - if VERBOSE { - println!(); - print!( - " --syntax-highlight {:?}", - options.lines_to_be_syntax_highlighted - ); - print!( - " --minus-foreground-color {:?}", - options.minus_foreground_color - ); - print!( - " --minus-emph-foreground-color {:?}", - options.minus_emph_foreground_color - ); - print!( - " --plus-foreground-color {:?}", - options.plus_foreground_color - ); - print!( - " --plus-emph-foreground-color {:?}", - options.plus_emph_foreground_color - ); - println!(); - } - _do_hunk_color_test(options.clone()); - } - } - } - } - } - } - - fn _do_hunk_color_test(options: cli::Opt) { - let (output, config) = integration_test_utils::run_delta( - DIFF_YIELDING_ALL_HUNK_LINE_COLOR_CATEGORIES, - options, - ); - let lines = output.trim().split("\n").skip(4); - - let minus = - paint::paint_text_background("", config.minus_style_modifier.background.unwrap(), true) - .trim_end_matches(paint::ANSI_SGR_RESET) - .trim_end_matches("m") - .to_string(); - let minus_emph = paint::paint_text_background( - "", - config.minus_emph_style_modifier.background.unwrap(), - true, - ) - .trim_end_matches(paint::ANSI_SGR_RESET) - .trim_end_matches("m") - .to_string(); - let plus = - paint::paint_text_background("", config.plus_style_modifier.background.unwrap(), true) - .trim_end_matches(paint::ANSI_SGR_RESET) - .trim_end_matches("m") - .to_string(); - let plus_emph = paint::paint_text_background( - "", - config.plus_emph_style_modifier.background.unwrap(), - true, - ) - .trim_end_matches(paint::ANSI_SGR_RESET) - .trim_end_matches("m") - .to_string(); - - let expectation = vec![ - // line 1: unchanged - ( - State::HunkZero, - vec![("", "(11111111, 11111111, 11111111)")], - ), - // line 2: removed, final token is minus-emph - ( - State::HunkMinus, - vec![ - (minus.as_str(), "(22222222, 22222222"), - (minus_emph.as_str(), ", 22222222"), - (minus.as_str(), ")"), - ], - ), - // line 3: removed - ( - State::HunkMinus, - vec![(minus.as_str(), "(33333333, 33333333, 33333333)")], - ), - // line 4: removed - ( - State::HunkMinus, - vec![(minus.as_str(), "(44444444, 44444444, 44444444)")], - ), - // line 5: added, and syntax-higlighted. - ( - State::HunkPlus, - vec![(plus.as_str(), "(22222222, 22222222)")], - ), - // line 6: added, and syntax-highlighted. First is plus-emph. - ( - State::HunkPlus, - vec![ - (plus.as_str(), "("), - (plus_emph.as_str(), "33333333, "), - (plus.as_str(), "33333333, 33333333, 33333333)"), - ], - ), - // line 7: unchanged - ( - State::HunkZero, - vec![("", "(55555555, 55555555, 55555555)")], - ), - // line 8: added, and syntax-highlighted. - ( - State::HunkPlus, - vec![(plus.as_str(), "(66666666, 66666666, 66666666)")], - ), - ]; - - // TODO: check same length - for ((state, assertion), line) in expectation.iter().zip_eq(lines) { - if VERBOSE { - println!("{}", line) - }; - if config.should_syntax_highlight(state) { - assert!(ansi_test_utils::is_syntax_highlighted(line)); - } else { - // An explicit assertion about the ANSI sequences should be available (when there's - // syntax highlighting the pattern of ANSI sequences is too complex to make the - // assertion). - ansi_test_utils::assert_line_has_expected_ansi_sequences(line, &assertion) - } - // Background color should match the line's state. - match config.get_color(state, Background) { - Some(_color) => assert!(ansi_test_utils::line_has_background_color( - line, state, &config - )), - None => assert!(ansi_test_utils::line_has_no_background_color(line)), - } - } - } - - const DIFF_YIELDING_ALL_HUNK_LINE_COLOR_CATEGORIES: &str = r" -diff --git a/file.py b/file.py -index 15c0fa2..dc2254c 100644 ---- a/file.py -+++ b/file.py -@@ -1,6 +1,6 @@ - (11111111, 11111111, 11111111) --(22222222, 22222222, 22222222) --(33333333, 33333333, 33333333) --(44444444, 44444444, 44444444) -+(22222222, 22222222) -+(33333333, 33333333, 33333333, 33333333) - (55555555, 55555555, 55555555) -+(66666666, 66666666, 66666666) -"; -} diff --git a/tests/test_deprecated_options b/tests/test_deprecated_options new file mode 100755 index 00000000..2bd8741b --- /dev/null +++ b/tests/test_deprecated_options @@ -0,0 +1,21 @@ +foreground_color=red +for decoration_attr in box underline plain; do + + git show | ./target/release/delta --commit-style $decoration_attr + git show | ./target/release/delta --file-style $decoration_attr + git show | ./target/release/delta --hunk-style $decoration_attr + + git show | ./target/release/delta --commit-color $foreground_color + git show | ./target/release/delta --file-color $foreground_color + git show | ./target/release/delta --hunk-color $foreground_color + + git show | ./target/release/delta --commit-color $foreground_color --commit-style $decoration_attr + git show | ./target/release/delta --file-color $foreground_color --file-style $decoration_attr + git show | ./target/release/delta --hunk-color $foreground_color --hunk-style $decoration_attr + +done + +background_color=blue +for option in --minus-color --plus-color --minus-emph-color --plus-emph-color; do + git show | ./target/release/delta $option $background_color +done