mirror of
https://github.com/dandavison/delta.git
synced 2024-10-05 04:17:21 +03:00
commit
d2b7fdc57e
42
Cargo.lock
generated
42
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
1
Makefile
1
Makefile
@ -14,6 +14,7 @@ unit-test:
|
||||
|
||||
end-to-end-test: build
|
||||
./tests/test_color_only_output_matches_git_on_full_repo_history
|
||||
./tests/test_deprecated_options > /dev/null
|
||||
|
||||
release:
|
||||
@make -f release.Makefile release
|
||||
|
1
ci/script.sh
vendored
1
ci/script.sh
vendored
@ -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
|
||||
|
441
src/cli.rs
441
src/cli.rs
@ -1,8 +1,5 @@
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use bit_set::BitSet;
|
||||
use console::Term;
|
||||
use structopt::clap::AppSettings::{ColorAlways, ColoredHelp, DeriveDisplayOrder};
|
||||
use structopt::StructOpt;
|
||||
@ -10,11 +7,11 @@ use structopt::StructOpt;
|
||||
use crate::bat::assets::HighlightingAssets;
|
||||
use crate::bat::output::PagingMode;
|
||||
use crate::config;
|
||||
use crate::delta::State;
|
||||
use crate::env;
|
||||
use crate::rewrite;
|
||||
use crate::style;
|
||||
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
#[derive(StructOpt, Clone, Debug, PartialEq)]
|
||||
#[structopt(
|
||||
name = "delta",
|
||||
about = "A syntax-highlighter for git and diff output",
|
||||
@ -22,10 +19,60 @@ use crate::style;
|
||||
setting(ColoredHelp),
|
||||
setting(DeriveDisplayOrder),
|
||||
after_help = "\
|
||||
Colors
|
||||
STYLES
|
||||
------
|
||||
|
||||
All delta color options work the same way. There are three ways to specify a color:
|
||||
All options that have a name like --*-style work the same way. It is very similar to how
|
||||
colors/styles are specified in a gitconfig file:
|
||||
https://git-scm.com/docs/git-config#Documentation/git-config.txt-color
|
||||
|
||||
Here is an example:
|
||||
|
||||
--minus-style 'red bold underline #ffeeee'
|
||||
|
||||
That means: For removed lines, set the foreground (text) color to 'red', make it bold and
|
||||
underlined, and set the background color to '#ffeeee'.
|
||||
|
||||
See the COLORS section below for how to specify a color. In addition to real colors, there are 3
|
||||
special color names: 'auto', 'normal', 'syntax'.
|
||||
|
||||
Here is an example of using special color names together with a single attribute:
|
||||
|
||||
--minus-style 'syntax bold auto'
|
||||
|
||||
That means: For removed lines, syntax-highlight the text, and make it bold, and do whatever delta
|
||||
normally does for the background.
|
||||
|
||||
The available attributes are: 'blink', 'bold', 'dimmed', 'hidden', 'italic', 'reverse',
|
||||
'strikethrough', 'underline'.
|
||||
|
||||
A complete description of the style string syntax follows:
|
||||
|
||||
- A style string consists of 0, 1, or 2 colors, together with an arbitrary number of style
|
||||
attributes, all separated by spaces.
|
||||
|
||||
- The first color is the foreground (text) color. The second color is the background color.
|
||||
Attributes can go in any position.
|
||||
|
||||
- This means that in order to specify a background color you must also specify a foreground (text)
|
||||
color.
|
||||
|
||||
- If you just want delta to do what it would normally do for one of the colors, then use the
|
||||
special color 'auto'. This can be used for both foreground and background.
|
||||
|
||||
- If you want the foreground text to be syntax-highlighted according to its language, then use the
|
||||
special foreground color 'syntax'. This can only be used for the foreground (text).
|
||||
|
||||
- If you want delta to not apply any color, then use the special color 'normal'. This can be used
|
||||
for both foreground and background.
|
||||
|
||||
- The minimal style specification is the empty string ''. This means: do not apply any colors or
|
||||
styling to the element in question.
|
||||
|
||||
COLORS
|
||||
------
|
||||
|
||||
There are three ways to specify a color:
|
||||
|
||||
1. RGB hex code
|
||||
|
||||
@ -63,70 +110,87 @@ All delta color options work the same way. There are three ways to specify a col
|
||||
"
|
||||
)]
|
||||
pub struct Opt {
|
||||
/// Use default colors appropriate for a light terminal background. For more control, see the other
|
||||
/// color options.
|
||||
#[structopt(long = "theme", env = "BAT_THEME")]
|
||||
/// The code syntax highlighting theme to use. Use --list-themes to demo available themes. If
|
||||
/// the theme is not set using this option, it will be taken from the BAT_THEME environment
|
||||
/// variable, if that contains a valid theme name. --theme=none disables all syntax
|
||||
/// highlighting.
|
||||
pub theme: Option<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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
557
src/config.rs
557
src/config.rs
@ -1,16 +1,18 @@
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bit_set::BitSet;
|
||||
use syntect::highlighting::{Color, Style, StyleModifier, Theme, ThemeSet};
|
||||
use ansi_term::Color;
|
||||
use syntect::highlighting::Color as SyntectColor;
|
||||
use syntect::highlighting::Style as SyntectStyle;
|
||||
use syntect::highlighting::{Theme, ThemeSet};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
use crate::bat::output::PagingMode;
|
||||
use crate::cli;
|
||||
use crate::delta::State;
|
||||
use crate::bat::terminal::to_ansi_color;
|
||||
use crate::cli::{self, unreachable};
|
||||
use crate::env;
|
||||
use crate::paint;
|
||||
use crate::style;
|
||||
use crate::style::{self, DecorationStyle, Style};
|
||||
use crate::syntect_color;
|
||||
|
||||
pub struct Config<'a> {
|
||||
pub theme: Option<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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
154
src/delta.rs
154
src/delta.rs
@ -5,18 +5,18 @@ use console::strip_ansi_codes;
|
||||
use std::io::BufRead;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::cli;
|
||||
use crate::cli::unreachable;
|
||||
use crate::config::Config;
|
||||
use crate::draw;
|
||||
use crate::paint::{self, Painter};
|
||||
use crate::paint::Painter;
|
||||
use crate::parse;
|
||||
use crate::style;
|
||||
use crate::style::{self, DecorationStyle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum State {
|
||||
CommitMeta, // In commit metadata section
|
||||
FileMeta, // In diff metadata section, between (possible) commit metadata and first hunk
|
||||
HunkMeta, // In hunk metadata line
|
||||
HunkHeader, // In hunk metadata line
|
||||
HunkZero, // In hunk; unchanged line
|
||||
HunkMinus, // In hunk; removed line
|
||||
HunkPlus, // In hunk; added line
|
||||
@ -33,7 +33,7 @@ pub enum Source {
|
||||
impl State {
|
||||
fn is_in_hunk(&self) -> bool {
|
||||
match *self {
|
||||
State::HunkMeta | State::HunkZero | State::HunkMinus | State::HunkPlus => true,
|
||||
State::HunkHeader | State::HunkZero | State::HunkMinus | State::HunkPlus => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -42,11 +42,11 @@ impl State {
|
||||
// Possible transitions, with actions on entry:
|
||||
//
|
||||
//
|
||||
// | from \ to | CommitMeta | FileMeta | HunkMeta | HunkZero | HunkMinus | HunkPlus |
|
||||
// | from \ to | CommitMeta | FileMeta | HunkHeader | HunkZero | HunkMinus | HunkPlus |
|
||||
// |-------------+-------------+-------------+-------------+-------------+-------------+----------|
|
||||
// | CommitMeta | emit | emit | | | | |
|
||||
// | FileMeta | | emit | emit | | | |
|
||||
// | HunkMeta | | | | emit | push | push |
|
||||
// | HunkHeader | | | | emit | push | push |
|
||||
// | HunkZero | emit | emit | emit | emit | push | push |
|
||||
// | HunkMinus | flush, emit | flush, emit | flush, emit | flush, emit | push | push |
|
||||
// | HunkPlus | flush, emit | flush, emit | flush, emit | flush, emit | flush, push | push |
|
||||
@ -74,7 +74,7 @@ where
|
||||
if line.starts_with("commit ") {
|
||||
painter.paint_buffered_lines();
|
||||
state = State::CommitMeta;
|
||||
if config.commit_style != cli::SectionStyle::Plain {
|
||||
if config.commit_style.decoration_style.is_some() {
|
||||
painter.emit()?;
|
||||
handle_commit_meta_header_line(&mut painter, &raw_line, config)?;
|
||||
continue;
|
||||
@ -86,7 +86,7 @@ where
|
||||
// FIXME: For unified diff input, removal ("-") of a line starting with "--" (e.g. a
|
||||
// Haskell or SQL comment) will be confused with the "---" file metadata marker.
|
||||
&& (line.starts_with("--- ") || line.starts_with("rename from "))
|
||||
&& config.file_style != cli::SectionStyle::Plain
|
||||
&& config.file_style.decoration_style.is_some()
|
||||
{
|
||||
minus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff);
|
||||
if source == Source::DiffUnified {
|
||||
@ -99,7 +99,7 @@ where
|
||||
));
|
||||
}
|
||||
} else if (line.starts_with("+++ ") || line.starts_with("rename to "))
|
||||
&& config.file_style != cli::SectionStyle::Plain
|
||||
&& config.file_style.decoration_style.is_some()
|
||||
{
|
||||
plus_file = parse::get_file_path_from_file_meta_line(&line, source == Source::GitDiff);
|
||||
painter.set_syntax(parse::get_file_extension_from_file_meta_line_file_path(
|
||||
@ -114,11 +114,11 @@ where
|
||||
source == Source::DiffUnified,
|
||||
)?;
|
||||
} else if line.starts_with("@@") {
|
||||
state = State::HunkMeta;
|
||||
state = State::HunkHeader;
|
||||
painter.set_highlighter();
|
||||
if config.hunk_style != cli::SectionStyle::Plain {
|
||||
if config.hunk_header_style.decoration_style.is_some() {
|
||||
painter.emit()?;
|
||||
handle_hunk_meta_line(&mut painter, &line, config)?;
|
||||
handle_hunk_header_line(&mut painter, &line, config)?;
|
||||
continue;
|
||||
}
|
||||
} else if source == Source::DiffUnified && line.starts_with("Only in ")
|
||||
@ -140,7 +140,7 @@ where
|
||||
|
||||
state = State::FileMeta;
|
||||
painter.paint_buffered_lines();
|
||||
if config.file_style != cli::SectionStyle::Plain {
|
||||
if config.file_style.decoration_style.is_some() {
|
||||
painter.emit()?;
|
||||
handle_generic_file_meta_header_line(&mut painter, &raw_line, config)?;
|
||||
continue;
|
||||
@ -153,7 +153,7 @@ where
|
||||
continue;
|
||||
}
|
||||
|
||||
if state == State::FileMeta && config.file_style != cli::SectionStyle::Plain {
|
||||
if state == State::FileMeta && config.file_style.decoration_style.is_some() {
|
||||
// The file metadata section is 4 lines. Skip them under non-plain file-styles.
|
||||
continue;
|
||||
} else {
|
||||
@ -190,19 +190,26 @@ fn handle_commit_meta_header_line(
|
||||
line: &str,
|
||||
config: &Config,
|
||||
) -> std::io::Result<()> {
|
||||
let draw_fn = match config.commit_style {
|
||||
cli::SectionStyle::Box => draw::write_boxed_with_line,
|
||||
cli::SectionStyle::Underline => draw::write_underlined,
|
||||
cli::SectionStyle::Plain => panic!(),
|
||||
cli::SectionStyle::Omit => return Ok(()),
|
||||
let decoration_ansi_term_style;
|
||||
let draw_fn = match config.commit_style.decoration_style {
|
||||
Some(DecorationStyle::Box(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_boxed_with_line
|
||||
}
|
||||
Some(DecorationStyle::Underline(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_underlined
|
||||
}
|
||||
Some(DecorationStyle::Omit) => return Ok(()),
|
||||
None => unreachable("Unreachable code path reached in handle_commit_meta_header_line."),
|
||||
};
|
||||
draw_fn(
|
||||
painter.writer,
|
||||
line,
|
||||
config.terminal_width,
|
||||
config.commit_color,
|
||||
config.commit_style.ansi_term_style,
|
||||
decoration_ansi_term_style,
|
||||
true,
|
||||
config.true_color,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -225,54 +232,68 @@ fn handle_generic_file_meta_header_line(
|
||||
line: &str,
|
||||
config: &Config,
|
||||
) -> std::io::Result<()> {
|
||||
let draw_fn = match config.file_style {
|
||||
cli::SectionStyle::Box => draw::write_boxed_with_line,
|
||||
cli::SectionStyle::Underline => draw::write_underlined,
|
||||
cli::SectionStyle::Plain => panic!(),
|
||||
cli::SectionStyle::Omit => return Ok(()),
|
||||
let decoration_ansi_term_style;
|
||||
let draw_fn = match config.file_style.decoration_style {
|
||||
Some(DecorationStyle::Box(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_boxed_with_line
|
||||
}
|
||||
Some(DecorationStyle::Underline(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_underlined
|
||||
}
|
||||
Some(DecorationStyle::Omit) => return Ok(()),
|
||||
None => {
|
||||
unreachable("Unreachable code path reached in handle_generic_file_meta_header_line.")
|
||||
}
|
||||
};
|
||||
writeln!(painter.writer)?;
|
||||
draw_fn(
|
||||
painter.writer,
|
||||
&paint::paint_text_foreground(line, config.file_color, config.true_color),
|
||||
&config.file_style.ansi_term_style.paint(line),
|
||||
config.terminal_width,
|
||||
config.file_color,
|
||||
config.file_style.ansi_term_style,
|
||||
decoration_ansi_term_style,
|
||||
false,
|
||||
config.true_color,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_hunk_meta_line(
|
||||
fn handle_hunk_header_line(
|
||||
painter: &mut Painter,
|
||||
line: &str,
|
||||
config: &Config,
|
||||
) -> std::io::Result<()> {
|
||||
let draw_fn = match config.hunk_style {
|
||||
cli::SectionStyle::Box => draw::write_boxed,
|
||||
cli::SectionStyle::Underline => draw::write_underlined,
|
||||
cli::SectionStyle::Plain => panic!(),
|
||||
cli::SectionStyle::Omit => return Ok(()),
|
||||
let decoration_ansi_term_style;
|
||||
let draw_fn = match config.hunk_header_style.decoration_style {
|
||||
Some(style::DecorationStyle::Box(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_boxed
|
||||
}
|
||||
Some(style::DecorationStyle::Underline(style)) => {
|
||||
decoration_ansi_term_style = style;
|
||||
draw::write_underlined
|
||||
}
|
||||
Some(style::DecorationStyle::Omit) => return Ok(()),
|
||||
None => unreachable("Unreachable code path reached in handle_hunk_header_line."),
|
||||
};
|
||||
let (raw_code_fragment, line_number) = parse::parse_hunk_metadata(&line);
|
||||
let code_fragment = prepare(raw_code_fragment, false, config);
|
||||
if !code_fragment.is_empty() {
|
||||
let syntax_style_sections = Painter::get_line_syntax_style_sections(
|
||||
&code_fragment,
|
||||
true,
|
||||
let lines = vec![prepare(raw_code_fragment, false, config)];
|
||||
if !lines[0].is_empty() {
|
||||
let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
|
||||
&lines,
|
||||
&State::HunkHeader,
|
||||
&mut painter.highlighter,
|
||||
&painter.config,
|
||||
);
|
||||
Painter::paint_lines(
|
||||
vec![syntax_style_sections],
|
||||
vec![vec![(
|
||||
style::NO_BACKGROUND_COLOR_STYLE_MODIFIER,
|
||||
&code_fragment,
|
||||
)]],
|
||||
syntax_style_sections,
|
||||
vec![vec![(config.hunk_header_style, lines[0].as_str())]],
|
||||
&mut painter.output_buffer,
|
||||
config,
|
||||
"",
|
||||
style::NO_BACKGROUND_COLOR_STYLE_MODIFIER,
|
||||
config.null_style,
|
||||
config.null_style,
|
||||
Some(false),
|
||||
);
|
||||
painter.output_buffer.pop(); // trim newline
|
||||
@ -280,17 +301,16 @@ fn handle_hunk_meta_line(
|
||||
painter.writer,
|
||||
&painter.output_buffer,
|
||||
config.terminal_width,
|
||||
config.hunk_color,
|
||||
config.hunk_header_style.ansi_term_style,
|
||||
decoration_ansi_term_style,
|
||||
false,
|
||||
config.true_color,
|
||||
)?;
|
||||
painter.output_buffer.clear();
|
||||
}
|
||||
writeln!(
|
||||
painter.writer,
|
||||
"\n{}",
|
||||
paint::paint_text_foreground(line_number, config.hunk_color, config.true_color)
|
||||
)?;
|
||||
match config.hunk_header_style.decoration_ansi_term_style() {
|
||||
Some(style) => writeln!(painter.writer, "\n{}", style.paint(line_number))?,
|
||||
None => writeln!(painter.writer, "\n{}", line_number)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -331,27 +351,23 @@ fn handle_hunk_line(
|
||||
let state = State::HunkZero;
|
||||
let prefix = if line.is_empty() { "" } else { &line[..1] };
|
||||
painter.paint_buffered_lines();
|
||||
let line = prepare(&line, true, config);
|
||||
let syntax_style_sections = if config.should_syntax_highlight(&state) {
|
||||
Painter::get_line_syntax_style_sections(
|
||||
&line,
|
||||
true,
|
||||
&mut painter.highlighter,
|
||||
&painter.config,
|
||||
)
|
||||
} else {
|
||||
vec![(style::get_no_style(), line.as_str())]
|
||||
};
|
||||
let diff_style_sections =
|
||||
vec![(style::NO_BACKGROUND_COLOR_STYLE_MODIFIER, line.as_str())];
|
||||
let lines = vec![prepare(&line, true, config)];
|
||||
let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
|
||||
&lines,
|
||||
&state,
|
||||
&mut painter.highlighter,
|
||||
&painter.config,
|
||||
);
|
||||
let diff_style_sections = vec![(config.zero_style, lines[0].as_str())];
|
||||
|
||||
Painter::paint_lines(
|
||||
vec![syntax_style_sections],
|
||||
syntax_style_sections,
|
||||
vec![diff_style_sections],
|
||||
&mut painter.output_buffer,
|
||||
config,
|
||||
prefix,
|
||||
style::NO_BACKGROUND_COLOR_STYLE_MODIFIER,
|
||||
config.zero_style,
|
||||
config.zero_style,
|
||||
None,
|
||||
);
|
||||
state
|
||||
|
78
src/draw.rs
78
src/draw.rs
@ -1,21 +1,20 @@
|
||||
/// This lower-level module works directly with ansi_term::Style, rather than Delta's higher-level style::Style.
|
||||
use std::io::Write;
|
||||
|
||||
use ansi_term;
|
||||
use box_drawing;
|
||||
use console::strip_ansi_codes;
|
||||
use syntect::highlighting::Color;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::paint;
|
||||
|
||||
/// Write text to stream, surrounded by a box, leaving the cursor just
|
||||
/// beyond the bottom right corner.
|
||||
pub fn write_boxed(
|
||||
writer: &mut dyn Write,
|
||||
text: &str,
|
||||
_line_width: usize, // ignored
|
||||
color: Color,
|
||||
text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let up_left = if heavy {
|
||||
box_drawing::heavy::UP_LEFT
|
||||
@ -23,12 +22,8 @@ pub fn write_boxed(
|
||||
box_drawing::light::UP_LEFT
|
||||
};
|
||||
let box_width = UnicodeWidthStr::width(strip_ansi_codes(text).as_ref()) + 1;
|
||||
write_boxed_partial(writer, text, box_width, color, heavy, true_color)?;
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
paint::paint_text_foreground(up_left, color, true_color)
|
||||
)?;
|
||||
write_boxed_partial(writer, text, box_width, text_style, decoration_style, heavy)?;
|
||||
write!(writer, "{}", decoration_style.paint(up_left))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -38,12 +33,19 @@ pub fn write_boxed_with_line(
|
||||
writer: &mut dyn Write,
|
||||
text: &str,
|
||||
line_width: usize,
|
||||
color: Color,
|
||||
text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let box_width = UnicodeWidthStr::width(strip_ansi_codes(text).as_ref()) + 1;
|
||||
write_boxed_with_horizontal_whisker(writer, text, box_width, color, heavy, true_color)?;
|
||||
write_boxed_with_horizontal_whisker(
|
||||
writer,
|
||||
text,
|
||||
box_width,
|
||||
text_style,
|
||||
decoration_style,
|
||||
heavy,
|
||||
)?;
|
||||
write_horizontal_line(
|
||||
writer,
|
||||
if line_width > box_width {
|
||||
@ -51,9 +53,9 @@ pub fn write_boxed_with_line(
|
||||
} else {
|
||||
0
|
||||
},
|
||||
color,
|
||||
text_style,
|
||||
decoration_style,
|
||||
heavy,
|
||||
true_color,
|
||||
)?;
|
||||
write!(writer, "\n")?;
|
||||
Ok(())
|
||||
@ -63,16 +65,12 @@ pub fn write_underlined(
|
||||
writer: &mut dyn Write,
|
||||
text: &str,
|
||||
line_width: usize,
|
||||
color: Color,
|
||||
text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
writeln!(
|
||||
writer,
|
||||
"{}",
|
||||
paint::paint_text_foreground(text, color, true_color)
|
||||
)?;
|
||||
write_horizontal_line(writer, line_width - 1, color, heavy, true_color)?;
|
||||
writeln!(writer, "{}", text_style.paint(text))?;
|
||||
write_horizontal_line(writer, line_width - 1, text_style, decoration_style, heavy)?;
|
||||
write!(writer, "\n")?;
|
||||
Ok(())
|
||||
}
|
||||
@ -80,9 +78,9 @@ pub fn write_underlined(
|
||||
fn write_horizontal_line(
|
||||
writer: &mut dyn Write,
|
||||
line_width: usize,
|
||||
color: Color,
|
||||
_text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let horizontal = if heavy {
|
||||
box_drawing::heavy::HORIZONTAL
|
||||
@ -92,7 +90,7 @@ fn write_horizontal_line(
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
paint::paint_text_foreground(&horizontal.repeat(line_width), color, true_color)
|
||||
decoration_style.paint(horizontal.repeat(line_width))
|
||||
)
|
||||
}
|
||||
|
||||
@ -100,21 +98,17 @@ pub fn write_boxed_with_horizontal_whisker(
|
||||
writer: &mut dyn Write,
|
||||
text: &str,
|
||||
box_width: usize,
|
||||
color: Color,
|
||||
text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let up_horizontal = if heavy {
|
||||
box_drawing::heavy::UP_HORIZONTAL
|
||||
} else {
|
||||
box_drawing::light::UP_HORIZONTAL
|
||||
};
|
||||
write_boxed_partial(writer, text, box_width, color, heavy, true_color)?;
|
||||
write!(
|
||||
writer,
|
||||
"{}",
|
||||
paint::paint_text_foreground(up_horizontal, color, true_color)
|
||||
)?;
|
||||
write_boxed_partial(writer, text, box_width, text_style, decoration_style, heavy)?;
|
||||
write!(writer, "{}", decoration_style.paint(up_horizontal))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -122,9 +116,9 @@ fn write_boxed_partial(
|
||||
writer: &mut dyn Write,
|
||||
text: &str,
|
||||
box_width: usize,
|
||||
color: Color,
|
||||
text_style: ansi_term::Style,
|
||||
decoration_style: ansi_term::Style,
|
||||
heavy: bool,
|
||||
true_color: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let horizontal = if heavy {
|
||||
box_drawing::heavy::HORIZONTAL
|
||||
@ -146,10 +140,10 @@ fn write_boxed_partial(
|
||||
write!(
|
||||
writer,
|
||||
"{}{}\n{} {}\n{}",
|
||||
paint::paint_text_foreground(&horizontal_edge, color, true_color),
|
||||
paint::paint_text_foreground(down_left, color, true_color),
|
||||
paint::paint_text_foreground(text, color, true_color),
|
||||
paint::paint_text_foreground(vertical, color, true_color),
|
||||
paint::paint_text_foreground(&horizontal_edge, color, true_color),
|
||||
decoration_style.paint(&horizontal_edge),
|
||||
decoration_style.paint(down_left),
|
||||
text_style.paint(text),
|
||||
decoration_style.paint(vertical),
|
||||
decoration_style.paint(&horizontal_edge),
|
||||
)
|
||||
}
|
||||
|
16
src/edits.rs
16
src/edits.rs
@ -436,22 +436,6 @@ mod tests {
|
||||
], 0.66)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_infer_edits_6_1() {
|
||||
let (after, before) = (
|
||||
" i += c0.len();",
|
||||
" .fold(0, |offset, ((_, c0), (_, _))| offset + c0.len())",
|
||||
);
|
||||
println!(" before: {}", before);
|
||||
println!(" after : {}", after);
|
||||
println!("tokenized before: {:?}", tokenize(before));
|
||||
println!("tokenized after : {:?}", tokenize(after));
|
||||
println!(
|
||||
"distance: {:?}",
|
||||
align::Alignment::new(tokenize(before), tokenize(after)).distance_parts()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_infer_edits_7() {
|
||||
assert_edits(
|
||||
|
51
src/main.rs
51
src/main.rs
@ -11,17 +11,18 @@ mod edits;
|
||||
mod env;
|
||||
mod paint;
|
||||
mod parse;
|
||||
mod rewrite;
|
||||
mod style;
|
||||
mod syntect_color;
|
||||
mod tests;
|
||||
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::process;
|
||||
|
||||
use ansi_term;
|
||||
use ansi_term::{self, Color};
|
||||
use atty;
|
||||
use bytelines::ByteLinesReader;
|
||||
use structopt::StructOpt;
|
||||
use syntect::highlighting::{Color, FontStyle, Style};
|
||||
|
||||
use crate::bat::assets::{list_languages, HighlightingAssets};
|
||||
use crate::bat::output::{OutputType, PagingMode};
|
||||
@ -79,40 +80,18 @@ fn show_background_colors(config: &config::Config) {
|
||||
--minus-emph-color=\"{minus_emph_color}\" \
|
||||
--plus-color=\"{plus_color}\" \
|
||||
--plus-emph-color=\"{plus_emph_color}\"",
|
||||
minus_color = get_painted_rgb_string(
|
||||
config.minus_style_modifier.background.unwrap(),
|
||||
config.true_color
|
||||
),
|
||||
minus_emph_color = get_painted_rgb_string(
|
||||
config.minus_emph_style_modifier.background.unwrap(),
|
||||
config.true_color
|
||||
),
|
||||
plus_color = get_painted_rgb_string(
|
||||
config.plus_style_modifier.background.unwrap(),
|
||||
config.true_color
|
||||
),
|
||||
plus_emph_color = get_painted_rgb_string(
|
||||
config.plus_emph_style_modifier.background.unwrap(),
|
||||
config.true_color
|
||||
),
|
||||
minus_color =
|
||||
get_painted_rgb_string(config.minus_style.ansi_term_style.background.unwrap()),
|
||||
minus_emph_color =
|
||||
get_painted_rgb_string(config.minus_emph_style.ansi_term_style.background.unwrap()),
|
||||
plus_color = get_painted_rgb_string(config.plus_style.ansi_term_style.background.unwrap()),
|
||||
plus_emph_color =
|
||||
get_painted_rgb_string(config.plus_emph_style.ansi_term_style.background.unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_painted_rgb_string(color: Color, true_color: bool) -> String {
|
||||
let mut string = String::new();
|
||||
let style = Style {
|
||||
foreground: style::NO_COLOR,
|
||||
background: color,
|
||||
font_style: FontStyle::empty(),
|
||||
};
|
||||
paint::paint_text(
|
||||
&format!("#{:02x?}{:02x?}{:02x?}", color.r, color.g, color.b),
|
||||
style,
|
||||
&mut string,
|
||||
true_color,
|
||||
);
|
||||
string.push_str("\x1b[0m"); // reset
|
||||
string
|
||||
fn get_painted_rgb_string(color: Color) -> String {
|
||||
color.paint(format!("{:?}", color)).to_string()
|
||||
}
|
||||
|
||||
fn list_themes() -> std::io::Result<()> {
|
||||
@ -154,12 +133,12 @@ index f38589a..0f1bb83 100644
|
||||
}
|
||||
|
||||
writeln!(stdout, "\n\nTheme: {}\n", style.paint(theme))?;
|
||||
let mut config = cli::process_command_line_arguments(cli::Opt {
|
||||
let config = cli::process_command_line_arguments(cli::Opt {
|
||||
theme: Some(theme.to_string()),
|
||||
file_decoration_style: "omit".to_string(),
|
||||
hunk_header_style: "omit".to_string(),
|
||||
..opt.clone()
|
||||
});
|
||||
config.file_style = cli::SectionStyle::Omit;
|
||||
config.hunk_style = cli::SectionStyle::Omit;
|
||||
let mut output_type = OutputType::from_mode(PagingMode::QuitIfOneScreen, None).unwrap();
|
||||
let mut writer = output_type.handle().unwrap();
|
||||
|
||||
|
416
src/paint.rs
416
src/paint.rs
@ -1,17 +1,15 @@
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ansi_term;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{Color, Style, StyleModifier};
|
||||
use syntect::highlighting::Style as SyntectStyle;
|
||||
use syntect::parsing::{SyntaxReference, SyntaxSet};
|
||||
|
||||
use crate::bat::terminal::to_ansi_color;
|
||||
use crate::config;
|
||||
use crate::delta::State;
|
||||
use crate::edits;
|
||||
use crate::paint::superimpose_style_sections::superimpose_style_sections;
|
||||
use crate::style;
|
||||
use crate::style::Style;
|
||||
|
||||
pub const ANSI_CSI_ERASE_IN_LINE: &str = "\x1b[K";
|
||||
pub const ANSI_SGR_RESET: &str = "\x1b[0m";
|
||||
@ -61,13 +59,13 @@ impl<'a> Painter<'a> {
|
||||
pub fn paint_buffered_lines(&mut self) {
|
||||
let minus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines(
|
||||
&self.minus_lines,
|
||||
self.config.should_syntax_highlight(&State::HunkMinus),
|
||||
&State::HunkMinus,
|
||||
&mut self.highlighter,
|
||||
self.config,
|
||||
);
|
||||
let plus_line_syntax_style_sections = Self::get_syntax_style_sections_for_lines(
|
||||
&self.plus_lines,
|
||||
self.config.should_syntax_highlight(&State::HunkPlus),
|
||||
&State::HunkPlus,
|
||||
&mut self.highlighter,
|
||||
self.config,
|
||||
);
|
||||
@ -81,7 +79,8 @@ impl<'a> Painter<'a> {
|
||||
&mut self.output_buffer,
|
||||
self.config,
|
||||
self.config.minus_line_marker,
|
||||
self.config.minus_style_modifier,
|
||||
self.config.minus_style,
|
||||
self.config.minus_non_emph_style,
|
||||
None,
|
||||
);
|
||||
}
|
||||
@ -92,7 +91,8 @@ impl<'a> Painter<'a> {
|
||||
&mut self.output_buffer,
|
||||
self.config,
|
||||
self.config.plus_line_marker,
|
||||
self.config.plus_style_modifier,
|
||||
self.config.plus_style,
|
||||
self.config.plus_non_emph_style,
|
||||
None,
|
||||
);
|
||||
}
|
||||
@ -103,34 +103,51 @@ impl<'a> Painter<'a> {
|
||||
/// Superimpose background styles and foreground syntax
|
||||
/// highlighting styles, and write colored lines to output buffer.
|
||||
pub fn paint_lines(
|
||||
syntax_style_sections: Vec<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(§ions_1, §ions_2),
|
||||
superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()),
|
||||
superimposed
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_superimpose_style_sections_2() {
|
||||
let sections_1 = vec![(STYLE, "ab")];
|
||||
let sections_2 = vec![(STYLE_MODIFIER, "a"), (STYLE_MODIFIER, "b")];
|
||||
let superimposed = vec![(SUPERIMPOSED_STYLE, String::from("ab"))];
|
||||
let sections_1 = vec![(*SYNTAX_STYLE, "ab")];
|
||||
let sections_2 = vec![
|
||||
(*SYNTAX_HIGHLIGHTED_STYLE, "a"),
|
||||
(*SYNTAX_HIGHLIGHTED_STYLE, "b"),
|
||||
];
|
||||
let superimposed = vec![(*SUPERIMPOSED_STYLE, String::from("ab"))];
|
||||
assert_eq!(
|
||||
superimpose_style_sections(§ions_1, §ions_2),
|
||||
superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()),
|
||||
superimposed
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_superimpose_style_sections_3() {
|
||||
let sections_1 = vec![(*SYNTAX_STYLE, "ab")];
|
||||
let sections_2 = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab")];
|
||||
let superimposed = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab".to_string())];
|
||||
assert_eq!(
|
||||
superimpose_style_sections(§ions_1, §ions_2, true, SyntectStyle::default()),
|
||||
superimposed
|
||||
);
|
||||
}
|
||||
@ -407,9 +470,12 @@ mod superimpose_style_sections {
|
||||
|
||||
#[test]
|
||||
fn test_superimpose() {
|
||||
let x = (STYLE, 'a');
|
||||
let pairs = vec![(&x, (STYLE_MODIFIER, 'a'))];
|
||||
assert_eq!(superimpose(pairs), vec![(SUPERIMPOSED_STYLE, 'a')]);
|
||||
let x = (*SYNTAX_STYLE, 'a');
|
||||
let pairs = vec![(&x, (*SYNTAX_HIGHLIGHTED_STYLE, 'a'))];
|
||||
assert_eq!(
|
||||
superimpose(pairs),
|
||||
vec![((*SYNTAX_STYLE, *SYNTAX_HIGHLIGHTED_STYLE), 'a')]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
218
src/rewrite.rs
Normal file
218
src/rewrite.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
197
src/style.rs
197
src/style.rs
@ -1,4 +1,62 @@
|
||||
use syntect::highlighting::{Color, FontStyle, Style, StyleModifier};
|
||||
use ansi_term::{self, Color};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Style {
|
||||
pub ansi_term_style: ansi_term::Style,
|
||||
pub is_syntax_highlighted: bool,
|
||||
pub decoration_style: Option<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
15
src/syntect_color.rs
Normal 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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
pub mod ansi_test_utils;
|
||||
pub mod integration_test_utils;
|
||||
pub mod test_example_diffs;
|
||||
pub mod test_hunk_highlighting;
|
||||
|
@ -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
|
||||
|
@ -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
21
tests/test_deprecated_options
Executable 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
|
Loading…
Reference in New Issue
Block a user