Merge pull request #204 from dandavison/style-strings

Style strings
This commit is contained in:
Dan Davison 2020-05-26 11:48:27 -04:00 committed by GitHub
commit d2b7fdc57e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1760 additions and 1152 deletions

42
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

1
ci/script.sh vendored
View File

@ -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

View File

@ -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<String>,
/// 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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[structopt(long = "plus-emph-color")]
/// The background color for emphasized sections of added lines.
pub plus_emph_color: Option<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[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<String>,
#[structopt(long = "minus-emph-color")]
/// Deprecated: use --minus-emph-style='normal my_background_color'.
pub deprecated_minus_emph_background_color: Option<String>,
#[structopt(long = "plus-color")]
/// Deprecated: Use --plus-style='normal my_background_color'.
pub deprecated_plus_background_color: Option<String>,
#[structopt(long = "plus-emph-color")]
/// Deprecated: Use --plus-emph-style='normal my_background_color'.
pub deprecated_plus_emph_background_color: Option<String>,
#[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<String>,
#[structopt(long = "file-color")]
/// Deprecated: use --file-style='my_foreground_color' --file-decoration-style='my_foreground_color'.
pub deprecated_file_color: Option<String>,
#[structopt(long = "hunk-style")]
/// Deprecated: synonym of --hunk-header-decoration-style.
pub deprecated_hunk_style: Option<String>,
#[structopt(long = "hunk-color")]
/// Deprecated: use --hunk-header-style='my_foreground_color' --hunk-header-decoration-style='my_foreground_color'.
pub deprecated_hunk_color: Option<String>,
}
#[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<SectionStyle, Error> {
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
)
);
}
}

View File

@ -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<Theme>,
@ -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<Color> {
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<Color>,
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<Color>,
) -> 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<Color>,
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<Color>,
background_default: Option<Color>,
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<style::DecorationStyle>,
special_attribute: &str,
true_color: bool,
) -> Option<DecorationStyle> {
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<Color>,
background_default: Option<Color>,
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<DecorationStyle> {
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<String>) {
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::<u8>()
.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<Color>,
true_color: bool,
) -> Option<Color> {
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
)
);
}
}

View File

@ -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

View File

@ -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),
)
}

View File

@ -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(

View File

@ -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();

View File

@ -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<Vec<(Style, &str)>>,
diff_style_sections: Vec<Vec<(StyleModifier, &str)>>,
syntax_style_sections: Vec<Vec<(SyntectStyle, &str)>>,
diff_style_sections: Vec<Vec<(Style, &str)>>,
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<bool>,
) {
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<String>,
state: &State,
highlighter: &mut HighlightLines,
config: &config::Config,
) -> Vec<Vec<(Style, &'s str)>> {
) -> Vec<Vec<(SyntectStyle, &'s str)>> {
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<String>,
plus_lines: &'b Vec<String>,
config: &config::Config,
) -> (
Vec<Vec<(StyleModifier, &'b str)>>,
Vec<Vec<(StyleModifier, &'b str)>>,
) {
edits::infer_edits(
) -> (Vec<Vec<(Style, &'b str)>>, Vec<Vec<(Style, &'b str)>>) {
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<Vec<(Style, &str)>>,
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<u8> {
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<Color> {
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> {
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::<Vec<(&(Style, char), (StyleModifier, char))>>(),
))
coalesce(
superimpose(
explode(sections_1)
.iter()
.zip(explode(sections_2))
.collect::<Vec<(&(SyntectStyle, char), (Style, char))>>(),
),
true_color,
null_syntect_style,
)
}
fn explode<T>(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(&sections_1, &sections_2),
superimpose_style_sections(&sections_1, &sections_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(&sections_1, &sections_2),
superimpose_style_sections(&sections_1, &sections_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(&sections_1, &sections_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')]
);
}
}
}

218
src/rewrite.rs Normal file
View File

@ -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::<OsString>::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::<OsString>::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::<OsString>::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<String> {
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<String> {
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);
}
}
}

View File

@ -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<DecorationStyle>,
}
#[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<ansi_term::Style> {
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<u8> {
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);

15
src/syntect_color.rs Normal file
View File

@ -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<Color> {
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> {
Color::from_str(&format!("#{:02x}000000", n)).ok()
}

View File

@ -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::<u8>::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
}
}

View File

@ -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::<OsString>::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)
}

View File

@ -1,4 +1,3 @@
pub mod ansi_test_utils;
pub mod integration_test_utils;
pub mod test_example_diffs;
pub mod test_hunk_highlighting;

View File

@ -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 <dandavison7@gmail.com>
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

View File

@ -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)
";
}

21
tests/test_deprecated_options Executable file
View File

@ -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