diff options
author | Dan Davison <dandavison7@gmail.com> | 2020-04-15 16:40:34 -0400 |
---|---|---|
committer | Dan Davison <dandavison7@gmail.com> | 2020-04-15 16:40:34 -0400 |
commit | 55703edbd385d36e7b6eb978a047bae91fd19516 (patch) | |
tree | efb5c901618776c23b4393d51c7c90945fd6450d /src | |
parent | b9ed24d34fa7d69abcc8169441c5649463915013 (diff) | |
parent | 714f97398eab62a85dc60aa6b02648431567071f (diff) |
Merge branch 'master' into width-calc-fix
Diffstat (limited to 'src')
-rw-r--r-- | src/align.rs | 12 | ||||
-rw-r--r-- | src/bat/.travis.yml | 8 | ||||
-rw-r--r-- | src/bat/assets.rs | 45 | ||||
-rw-r--r-- | src/bat/dirs.rs | 42 | ||||
-rw-r--r-- | src/bat/mod.rs | 2 | ||||
-rw-r--r-- | src/bat/terminal.rs | 53 | ||||
-rw-r--r-- | src/cli.rs | 165 | ||||
-rw-r--r-- | src/config.rs | 119 | ||||
-rw-r--r-- | src/delta.rs | 540 | ||||
-rw-r--r-- | src/draw.rs | 64 | ||||
-rw-r--r-- | src/edits.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 63 | ||||
-rw-r--r-- | src/paint.rs | 133 | ||||
-rw-r--r-- | src/parse.rs | 66 | ||||
-rw-r--r-- | src/style.rs | 119 |
15 files changed, 1189 insertions, 243 deletions
diff --git a/src/align.rs b/src/align.rs index 2a30afb4..8e37a9e7 100644 --- a/src/align.rs +++ b/src/align.rs @@ -69,12 +69,12 @@ impl<'a> Alignment<'a> { }; } - for (i, x_i) in (1..=self.x.len()).zip(self.x.iter()) { - for (j, y_j) in (1..=self.y.len()).zip(self.y.iter()) { + 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 - 1, j), - self.index(i - 1, j - 1), - self.index(i, j - 1), + self.index(i, j + 1), + self.index(i, j), + self.index(i + 1, j), ); let candidates = [ Cell { @@ -94,7 +94,7 @@ impl<'a> Alignment<'a> { cost: self.table[up].cost + INSERTION_COST, }, ]; - let index = self.index(i, j); + let index = self.index(i + 1, j + 1); self.table[index] = candidates .iter() .min_by_key(|cell| cell.cost) diff --git a/src/bat/.travis.yml b/src/bat/.travis.yml index e45683b1..69611c8d 100644 --- a/src/bat/.travis.yml +++ b/src/bat/.travis.yml @@ -5,9 +5,6 @@ matrix: - os: linux rust: stable env: TARGET=x86_64-unknown-linux-gnu - - os: windows - rust: stable - env: TARGET=x86_64-pc-windows-msvc - os: linux rust: stable env: @@ -36,8 +33,9 @@ env: # Default target on travis-ci. # Used as conditional check in the install stage - HOST=x86_64-unknown-linux-gnu - # Used on the deployment script + # Used in before_deploy.sh - PROJECT_NAME=delta + - PACKAGE_NAME=git-delta install: # prevent target re-add error from rustup @@ -66,7 +64,7 @@ deploy: # - TARGET: target triple of the build file: - $PROJECT_NAME-$TRAVIS_TAG-$TARGET.* - - $PROJECT_NAME*.deb + - $PACKAGE_NAME*.deb # don't delete artifacts from previous stage skip_cleanup: true on: diff --git a/src/bat/assets.rs b/src/bat/assets.rs index b8bd4561..f436fef1 100644 --- a/src/bat/assets.rs +++ b/src/bat/assets.rs @@ -1,14 +1,19 @@ // Based on code from https://github.com/sharkdp/bat a1b9334a44a2c652f52dddaa83dbacba57372468 // See src/bat/LICENSE -use std::io::{self, Write}; +use std::fs::File; +use std::io::{self, BufReader, Write}; +use std::path::PathBuf; use ansi_term::Colour::Green; use ansi_term::Style; -use syntect::dumps::from_binary; +use syntect::dumps::{from_binary, from_reader}; use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxSet; +use crate::bat::dirs::PROJECT_DIRS; +use crate::errors::*; + pub struct HighlightingAssets { pub syntax_set: SyntaxSet, pub theme_set: ThemeSet, @@ -16,7 +21,7 @@ pub struct HighlightingAssets { impl HighlightingAssets { pub fn new() -> Self { - Self::from_binary() + Self::from_cache().unwrap_or_else(|_| Self::from_binary()) } fn get_integrated_syntaxset() -> SyntaxSet { @@ -27,6 +32,32 @@ impl HighlightingAssets { from_binary(include_bytes!("../../assets/themes.bin")) } + fn from_cache() -> Result<Self> { + let theme_set_path = theme_set_path(); + let syntax_set_file = File::open(&syntax_set_path()).chain_err(|| { + format!( + "Could not load cached syntax set '{}'", + syntax_set_path().to_string_lossy() + ) + })?; + let syntax_set: SyntaxSet = from_reader(BufReader::new(syntax_set_file)) + .chain_err(|| "Could not parse cached syntax set")?; + + let theme_set_file = File::open(&theme_set_path).chain_err(|| { + format!( + "Could not load cached theme set '{}'", + theme_set_path.to_string_lossy() + ) + })?; + let theme_set: ThemeSet = from_reader(BufReader::new(theme_set_file)) + .chain_err(|| "Could not parse cached theme set")?; + + Ok(HighlightingAssets { + syntax_set, + theme_set, + }) + } + fn from_binary() -> Self { let syntax_set = Self::get_integrated_syntaxset(); let theme_set = Self::get_integrated_themeset(); @@ -38,6 +69,14 @@ impl HighlightingAssets { } } +fn theme_set_path() -> PathBuf { + PROJECT_DIRS.cache_dir().join("themes.bin") +} + +fn syntax_set_path() -> PathBuf { + PROJECT_DIRS.cache_dir().join("syntaxes.bin") +} + pub fn list_languages() -> std::io::Result<()> { let assets = HighlightingAssets::new(); let mut languages = assets diff --git a/src/bat/dirs.rs b/src/bat/dirs.rs new file mode 100644 index 00000000..26c49b20 --- /dev/null +++ b/src/bat/dirs.rs @@ -0,0 +1,42 @@ +// Based on code from https://github.com/sharkdp/bat e981e974076a926a38f124b7d8746de2ca5f0a28 +// See src/bat/LICENSE + +use dirs as dirs_rs; +use lazy_static::lazy_static; +use std::path::{Path, PathBuf}; + +#[cfg(target_os = "macos")] +use std::env; + +/// Wrapper for 'dirs' that treats MacOS more like Linux, by following the XDG specification. +/// This means that the `XDG_CACHE_HOME` and `XDG_CONFIG_HOME` environment variables are +/// checked first. The fallback directories are `~/.cache/bat` and `~/.config/bat`, respectively. +pub struct BatProjectDirs { + cache_dir: PathBuf, +} + +impl BatProjectDirs { + fn new() -> Option<BatProjectDirs> { + #[cfg(target_os = "macos")] + let cache_dir_op = env::var_os("XDG_CACHE_HOME") + .map(PathBuf::from) + .filter(|p| p.is_absolute()) + .or_else(|| dirs_rs::home_dir().map(|d| d.join(".cache"))); + + #[cfg(not(target_os = "macos"))] + let cache_dir_op = dirs_rs::cache_dir(); + + let cache_dir = cache_dir_op.map(|d| d.join("bat"))?; + + Some(BatProjectDirs { cache_dir }) + } + + pub fn cache_dir(&self) -> &Path { + &self.cache_dir + } +} + +lazy_static! { + pub static ref PROJECT_DIRS: BatProjectDirs = + BatProjectDirs::new().expect("Could not get home directory"); +} diff --git a/src/bat/mod.rs b/src/bat/mod.rs index e26ac304..04f6dbf7 100644 --- a/src/bat/mod.rs +++ b/src/bat/mod.rs @@ -1,2 +1,4 @@ pub mod assets; +pub mod dirs; pub mod output; +pub mod terminal; diff --git a/src/bat/terminal.rs b/src/bat/terminal.rs new file mode 100644 index 00000000..50351c48 --- /dev/null +++ b/src/bat/terminal.rs @@ -0,0 +1,53 @@ +extern crate ansi_colours; + +use ansi_term::Colour::{Fixed, RGB}; +use ansi_term::{self, Style}; + +use syntect::highlighting::{self, FontStyle}; + +pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Colour { + if color.a == 0 { + // Themes can specify one of the user-configurable terminal colors by + // encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set + // to the color palette number. The built-in themes ansi-light, + // ansi-dark, and base16 use this. + Fixed(color.r) + } else if true_color { + RGB(color.r, color.g, color.b) + } else { + Fixed(ansi_colours::ansi256_from_rgb((color.r, color.g, color.b))) + } +} + +#[allow(dead_code)] +pub fn as_terminal_escaped( + style: highlighting::Style, + text: &str, + true_color: bool, + colored: bool, + italics: bool, + background_color: Option<highlighting::Color>, +) -> String { + if text.is_empty() { + return text.to_string(); + } + + let mut style = if !colored { + Style::default() + } else { + let color = to_ansi_color(style.foreground, true_color); + + if style.font_style.contains(FontStyle::BOLD) { + color.bold() + } else if style.font_style.contains(FontStyle::UNDERLINE) { + color.underline() + } else if italics && style.font_style.contains(FontStyle::ITALIC) { + color.italic() + } else { + color.normal() + } + }; + + style.background = background_color.map(|c| to_ansi_color(c, true_color)); + style.paint(text).to_string() +} @@ -6,42 +6,90 @@ use console::Term; use structopt::StructOpt; use crate::bat::assets::HighlightingAssets; +use crate::bat::output::PagingMode; use crate::config; +use crate::env; use crate::style; #[derive(StructOpt, Clone, Debug)] -#[structopt(name = "delta", about = "A syntax-highlighting pager for git")] +#[structopt( + name = "delta", + about = "A syntax-highlighter for git and diff output", + after_help = "\ +Colors +------ + +All delta color options work the same way. There are three ways to specify a color: + +1. RGB hex code + + An example of using an RGB hex code is: + --file-color=\"#0e7c0e\" + +2. ANSI color name + + There are 8 ANSI color names: + black, red, green, yellow, blue, magenta, cyan, white. + + In addition, all of them have a bright form: + bright-black, bright-red, bright-green, bright-yellow, bright-blue, bright-magenta, bright-cyan, bright-white + + An example of using an ANSI color name is: + --file-color=\"green\" + + Unlike RGB hex codes, ANSI color names are just names: you can choose the exact color that each + name corresponds to in the settings of your terminal application (the application you use to + enter commands at a shell prompt). This means that if you use ANSI color names, and you change + the color theme used by your terminal, then delta's colors will respond automatically, without + needing to change the delta command line. + + \"purple\" is accepted as a synonym for \"magenta\". Color names and codes are case-insensitive. + +3. ANSI color number + + An example of using an ANSI color number is: + --file-color=28 + + There are 256 ANSI color numbers: 0-255. The first 16 are the same as the colors described in + the \"ANSI color name\" section above. See https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit. + Specifying colors like this is useful if your terminal only supports 256 colors (i.e. doesn\'t + support 24-bit color). +" +)] pub struct Opt { - /// Use colors appropriate for a light terminal background. For - /// more control, see --theme, --plus-color, and --minus-color. + /// Use default colors appropriate for a light terminal background. For more control, see the other + /// color options. #[structopt(long = "light")] pub light: bool, - /// Use colors appropriate for a dark terminal background. For - /// more control, see --theme, --plus-color, and --minus-color. + /// Use default colors appropriate for a dark terminal background. For more control, see the + /// other color options. #[structopt(long = "dark")] pub dark: bool, #[structopt(long = "minus-color")] - /// The background color (RGB hex) to use for removed lines. + /// The background color to use for removed lines. pub minus_color: Option<String>, #[structopt(long = "minus-emph-color")] - /// The background color (RGB hex) to use for emphasized sections of removed lines. + /// The background color to use for emphasized sections of removed lines. pub minus_emph_color: Option<String>, #[structopt(long = "plus-color")] - /// The background color (RGB hex) to use for added lines. + /// The background color to use for added lines. pub plus_color: Option<String>, #[structopt(long = "plus-emph-color")] - /// The background color (RGB hex) to use for emphasized sections of added lines. + /// The background color to use for emphasized sections of added lines. pub plus_emph_color: Option<String>, #[structopt(long = "theme", env = "BAT_THEME")] - /// The 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 and --compare-themes to view available themes. + /// 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 and --compare-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 = "highlight-removed")] @@ -49,21 +97,44 @@ pub struct Opt { /// apply syntax highlighting to unchanged and new lines only. pub highlight_removed: bool, + #[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`. + pub color_only: bool, + + #[structopt(long = "keep-plus-minus-markers")] + /// Prefix added/removed lines with a +/- character, respectively, exactly as git does. The + /// 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 commit section of git output. Options + /// 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 file section of git output. Options + /// 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 hunk section of git output. Options + /// 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, + /// The width (in characters) of the background color /// highlighting. By default, the width is the current terminal /// width. Use --width=variable to apply background colors to the @@ -90,24 +161,38 @@ pub struct Opt { #[structopt(long = "list-languages")] pub list_languages: bool, - /// List available syntax highlighting themes. + /// List available syntax-highlighting color themes. + #[structopt(long = "list-theme-names")] + pub list_theme_names: bool, + + /// List available syntax highlighting themes, each with an example of highlighted diff output. + /// If diff output is supplied on standard input then this will be used for the demo. For + /// example: `git show --color=always | delta --list-themes`. #[structopt(long = "list-themes")] pub list_themes: bool, - /// Compare available syntax highlighting themes. To use this - /// option, supply git diff output to delta on standard input. - /// For example: `git show --color=always | delta --compare-themes`. - #[structopt(long = "compare-themes")] - pub compare_themes: bool, - /// The maximum distance between two lines for them to be inferred to be homologous. Homologous /// line pairs are highlighted according to the deletion and insertion operations transforming /// one into the other. #[structopt(long = "max-line-distance", default_value = "0.3")] pub max_line_distance: f64, + + /// Whether to emit 24-bit ("true color") RGB color codes. Options are auto, always, and never. + /// "auto" means that delta will emit 24-bit color codes iff the environment variable COLORTERM + /// has the value "truecolor" or "24bit". If your terminal application (the application you use + /// to enter commands at a shell prompt) supports 24 bit colors, then it probably already sets + /// this environment variable, in which case you don't need to do anything. + #[structopt(long = "24-bit-color", default_value = "auto")] + pub true_color: String, + + /// Whether to use a pager when displaying output. Options are: auto, always, and never. The + /// default pager is `less`: this can be altered by setting the environment variables BAT_PAGER + /// or PAGER (BAT_PAGER has priority). + #[structopt(long = "paging", default_value = "auto")] + pub paging_mode: String, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum SectionStyle { Box, Plain, @@ -189,11 +274,45 @@ pub fn process_command_line_arguments<'a>( None => Some(available_terminal_width), }; + 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); + } + }; + config::get_config( opt, &assets.syntax_set, &assets.theme_set, + true_color, available_terminal_width, background_color_width, + paging_mode, ) } + +fn is_truecolor_terminal() -> bool { + env::get_env_var("COLORTERM") + .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") + .unwrap_or(false) +} diff --git a/src/config.rs b/src/config.rs index 500c5e2e..e35d2e4f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,35 +1,75 @@ +use std::process; use std::str::FromStr; use syntect::highlighting::{Color, Style, StyleModifier, Theme, ThemeSet}; use syntect::parsing::SyntaxSet; +use crate::bat::output::PagingMode; use crate::cli; use crate::env; +use crate::paint; use crate::style; pub struct Config<'a> { pub theme: Option<&'a Theme>, pub theme_name: String, + pub max_line_distance: 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_line_marker: &'a str, + pub plus_line_marker: &'a str, + pub highlight_removed: bool, + 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 syntax_set: &'a SyntaxSet, pub terminal_width: usize, + pub true_color: bool, pub width: Option<usize>, pub tab_width: usize, - pub opt: &'a cli::Opt, pub no_style: Style, pub max_buffered_lines: usize, + pub paging_mode: PagingMode, } pub fn get_config<'a>( opt: &'a cli::Opt, syntax_set: &'a SyntaxSet, theme_set: &'a ThemeSet, + true_color: bool, terminal_width: usize, width: Option<usize>, + paging_mode: PagingMode, ) -> Config<'a> { + // Implement --color-only + let keep_plus_minus_markers = if opt.color_only { + true + } else { + opt.keep_plus_minus_markers + }; + let width = if opt.color_only { None } else { width }; + let tab_width = if opt.color_only { 0 } else { opt.tab_width }; + let commit_style = if opt.color_only { + cli::SectionStyle::Plain + } else { + opt.commit_style + }; + let file_style = if opt.color_only { + cli::SectionStyle::Plain + } else { + opt.file_style + }; + let hunk_style = if opt.color_only { + cli::SectionStyle::Plain + } else { + 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(), @@ -45,11 +85,9 @@ pub fn get_config<'a>( }; let minus_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_rgb_or_ansi_code_with_default( opt.minus_color.as_ref(), - is_light_mode, - style::LIGHT_THEME_MINUS_COLOR, - style::DARK_THEME_MINUS_COLOR, + style::get_minus_color_default(is_light_mode, true_color), )), foreground: if opt.highlight_removed { None @@ -60,11 +98,9 @@ pub fn get_config<'a>( }; let minus_emph_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_rgb_or_ansi_code_with_default( opt.minus_emph_color.as_ref(), - is_light_mode, - style::LIGHT_THEME_MINUS_EMPH_COLOR, - style::DARK_THEME_MINUS_EMPH_COLOR, + style::get_minus_emph_color_default(is_light_mode, true_color), )), foreground: if opt.highlight_removed { None @@ -75,41 +111,51 @@ pub fn get_config<'a>( }; let plus_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_rgb_or_ansi_code_with_default( opt.plus_color.as_ref(), - is_light_mode, - style::LIGHT_THEME_PLUS_COLOR, - style::DARK_THEME_PLUS_COLOR, + style::get_plus_color_default(is_light_mode, true_color), )), foreground: None, font_style: None, }; let plus_emph_style_modifier = StyleModifier { - background: Some(color_from_arg( + background: Some(color_from_rgb_or_ansi_code_with_default( opt.plus_emph_color.as_ref(), - is_light_mode, - style::LIGHT_THEME_PLUS_EMPH_COLOR, - style::DARK_THEME_PLUS_EMPH_COLOR, + style::get_plus_emph_color_default(is_light_mode, true_color), )), foreground: None, font_style: None, }; + let minus_line_marker = if keep_plus_minus_markers { "-" } else { " " }; + let plus_line_marker = if keep_plus_minus_markers { "+" } else { " " }; + Config { theme, theme_name, + max_line_distance: opt.max_line_distance, minus_style_modifier, minus_emph_style_modifier, plus_style_modifier, plus_emph_style_modifier, + highlight_removed: opt.highlight_removed, + 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), + true_color, terminal_width, width, - tab_width: opt.tab_width, + tab_width, syntax_set, - opt, no_style: style::get_no_style(), max_buffered_lines: 32, + paging_mode, } } @@ -168,18 +214,25 @@ fn valid_theme_name_or_none(theme_name: Option<&String>, theme_set: &ThemeSet) - } } -fn color_from_arg( - arg: Option<&String>, - is_light_mode: bool, - light_theme_default: Color, - dark_theme_default: Color, -) -> Color { - arg.and_then(|s| Color::from_str(s).ok()) - .unwrap_or_else(|| { - if is_light_mode { - light_theme_default - } else { - dark_theme_default - } - }) +fn color_from_rgb_or_ansi_code(s: &str) -> Color { + let die = || { + eprintln!("Invalid color: {}", s); < |