From e527a69e5e61b16bd4198fc6afc8b64b09d0f59b Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Sat, 14 Nov 2020 14:19:36 -0500 Subject: Rename directory: src/bat -> src/bat_utils --- src/bat_utils/.travis.yml | 94 +++++++++++++++++++++++++++ src/bat_utils/LICENSE | 26 ++++++++ src/bat_utils/assets.rs | 144 +++++++++++++++++++++++++++++++++++++++++ src/bat_utils/dirs.rs | 42 ++++++++++++ src/bat_utils/less.rs | 62 ++++++++++++++++++ src/bat_utils/mod.rs | 5 ++ src/bat_utils/output.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++ src/bat_utils/terminal.rs | 53 +++++++++++++++ 8 files changed, 586 insertions(+) create mode 100644 src/bat_utils/.travis.yml create mode 100644 src/bat_utils/LICENSE create mode 100644 src/bat_utils/assets.rs create mode 100644 src/bat_utils/dirs.rs create mode 100644 src/bat_utils/less.rs create mode 100644 src/bat_utils/mod.rs create mode 100644 src/bat_utils/output.rs create mode 100644 src/bat_utils/terminal.rs (limited to 'src/bat_utils') diff --git a/src/bat_utils/.travis.yml b/src/bat_utils/.travis.yml new file mode 100644 index 00000000..ec594b2b --- /dev/null +++ b/src/bat_utils/.travis.yml @@ -0,0 +1,94 @@ +language: rust + +matrix: + include: + - 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: + - TARGET=x86_64-unknown-linux-musl + - CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc + - os: linux + rust: stable + env: TARGET=i686-unknown-linux-gnu + - os: osx + rust: stable + env: TARGET=x86_64-apple-darwin + - os: linux + rust: stable + env: + - TARGET=arm-unknown-linux-gnueabihf + - CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8 + - CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8 + - os: linux + rust: stable + env: + - TARGET=aarch64-unknown-linux-gnu + - CC_aarch64_unknown_linux_gnu=/usr/bin/aarch64-linux-gnu-gcc-4.8 + - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc-4.8 + +sudo: required + +before_install: + - etc/ci/before_install.sh + +env: + global: + # Default target on travis-ci. + # Used as conditional check in the install stage + - HOST=x86_64-unknown-linux-gnu + # Used in before_deploy.sh + - PROJECT_NAME=delta + - PACKAGE_NAME=git-delta + +install: + # prevent target re-add error from rustup + - if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi + +script: + - etc/ci/script.sh + +before_deploy: + - bash etc/ci/before_deploy.sh + +deploy: + provider: releases + # NOTE updating the `api_key.secure` + # - go to: https://github.com/settings/tokens/new + # - generate new token using `public_repo` scope + # - encrypt it using: `travis encrypt API_KEY_HERE` + # - paste the output below + api_key: + secure: "HLd8A4j/Z+4jrTZmzRAMX+Ko7igvARr0kO1YBQ9goB3m+RLtt77adaZUXrQI6EMHxohSjmtcOsX2HmWBH0hZPk39q8fRmHxIgvzqFM/DmQFIPmmlxgPgOeImJApy9hPxLDJoy1aAtpOEA1Ra33bA98W7CQ+WH9qF3qgTz08+ucq+mdTwV7pA+7bYLNRSzbU+sofPfZxMs97Lvv+c/n7gPJwA6eFrP/I97vux6RU1+RqyJ/Sg8hA19EEaAvLu4oYijuKFR2W6s/k4ODnDIUdF59dgROs7vccMLYNFmx4ECFAhzPtO18htJpyvmWHfRfByw/yFkLAalQIhGxoyILNEeaFSq1pJGRJllGNtmoqeXZctJcbWxt0N7/30PBrzjashxCzpvvjhpzJ9EYu2VBRme7RtLsSRE+R3NVpz4NfmJHEJWnE32Ds+saFJ7ixUcKk6xFG2rJ7/F9vG7x2LluJghDwMky50Glvi79YccrgudmKNY7bAJMVkffgG8r0FlR7HZ2piPa+mEHWGnnVRpWWm0aTQZFYR7GEsPl10LIsjRoVEwUdHVjDA+NiuH3my4FGdu8BKj8JDpEEQrvT1bPkX+M6mfbkr76YoL7GyCx86jLNt1B1uCjUzQW1166YHL11U+UIsrw2RKcYftCzyON4wxelkjafynlI1MOM6XffWKJs=" + # for uploading multiple files + file_glob: true + # NOTE explanation on each env variable + # - PROJECT_NAME: name of the project, set on the `env.global` above + # - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number + # - TARGET: target triple of the build + file: + - $PROJECT_NAME-$TRAVIS_TAG-$TARGET.* + - $PACKAGE_NAME*.deb + # don't delete artifacts from previous stage + skip_cleanup: true + on: + # deploy only if we push a tag + tags: true + # deploy only on stable channel that has TARGET env variable sets + condition: $TRAVIS_RUST_VERSION = stable && $TARGET != "" + +notifications: + email: + on_success: never + +# Caching +cache: + directories: + - /home/travis/.cargo +before_cache: + - rm -rf /home/travis/.cargo/registry diff --git a/src/bat_utils/LICENSE b/src/bat_utils/LICENSE new file mode 100644 index 00000000..8af3b8b8 --- /dev/null +++ b/src/bat_utils/LICENSE @@ -0,0 +1,26 @@ +Files under this directory (i.e. src/bat_utils/) originated as copies of +files from the `bat` project (https://github.com/sharkdp/bat), with +subsequent modifications, as recorded in git history. The `bat` +license is reproduced below. + +----------------------------------------------------------------------------- + +Copyright (c) 2018 bat-developers (https://github.com/sharkdp/bat). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/bat_utils/assets.rs b/src/bat_utils/assets.rs new file mode 100644 index 00000000..6715a821 --- /dev/null +++ b/src/bat_utils/assets.rs @@ -0,0 +1,144 @@ +// Based on code from https://github.com/sharkdp/bat a1b9334a44a2c652f52dddaa83dbacba57372468 +// See src/bat_utils/LICENSE + +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, from_reader}; +use syntect::highlighting::ThemeSet; +use syntect::parsing::SyntaxSet; + +use crate::bat_utils::dirs::PROJECT_DIRS; +use crate::errors::*; + +pub struct HighlightingAssets { + pub syntax_set: SyntaxSet, + pub theme_set: ThemeSet, +} + +impl HighlightingAssets { + pub fn new() -> Self { + Self::from_cache().unwrap_or_else(|_| Self::from_binary()) + } + + fn get_integrated_syntaxset() -> SyntaxSet { + from_binary(include_bytes!("../../etc/assets/syntaxes.bin")) + } + + fn get_integrated_themeset() -> ThemeSet { + from_binary(include_bytes!("../../etc/assets/themes.bin")) + } + + fn from_cache() -> Result { + 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(); + + HighlightingAssets { + syntax_set, + theme_set, + } + } +} + +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 + .syntax_set + .syntaxes() + .iter() + .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty()) + .collect::>(); + languages.sort_by_key(|lang| lang.name.to_uppercase()); + + let loop_through = false; + let colored_output = true; + + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + if loop_through { + for lang in languages { + writeln!(stdout, "{}:{}", lang.name, lang.file_extensions.join(","))?; + } + } else { + let longest = languages + .iter() + .map(|syntax| syntax.name.len()) + .max() + .unwrap_or(32); // Fallback width if they have no language definitions. + + let comma_separator = ", "; + let separator = " "; + // Line-wrapping for the possible file extension overflow. + let desired_width = 100; + + let style = if colored_output { + Green.normal() + } else { + Style::default() + }; + + for lang in languages { + write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?; + + // Number of characters on this line so far, wrap before `desired_width` + let mut num_chars = 0; + + let mut extension = lang.file_extensions.iter().peekable(); + while let Some(word) = extension.next() { + // If we can't fit this word in, then create a line break and align it in. + let new_chars = word.len() + comma_separator.len(); + if num_chars + new_chars >= desired_width { + num_chars = 0; + write!(stdout, "\n{:width$}{}", "", separator, width = longest)?; + } + + num_chars += new_chars; + write!(stdout, "{}", style.paint(&word[..]))?; + if extension.peek().is_some() { + write!(stdout, "{}", comma_separator)?; + } + } + writeln!(stdout)?; + } + } + + Ok(()) +} diff --git a/src/bat_utils/dirs.rs b/src/bat_utils/dirs.rs new file mode 100644 index 00000000..eb3835a4 --- /dev/null +++ b/src/bat_utils/dirs.rs @@ -0,0 +1,42 @@ +// Based on code from https://github.com/sharkdp/bat e981e974076a926a38f124b7d8746de2ca5f0a28 +// See src/bat_utils/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 { + #[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_utils/less.rs b/src/bat_utils/less.rs new file mode 100644 index 00000000..f7429333 --- /dev/null +++ b/src/bat_utils/less.rs @@ -0,0 +1,62 @@ +use std::process::Command; + +pub fn retrieve_less_version() -> Option { + let cmd = Command::new("less").arg("--version").output().ok()?; + parse_less_version(&cmd.stdout) +} + +fn parse_less_version(output: &[u8]) -> Option { + if output.starts_with(b"less ") { + let version = std::str::from_utf8(&output[5..]).ok()?; + let end = version.find(' ')?; + version[..end].parse::().ok() + } else { + None + } +} + +#[test] +fn test_parse_less_version_487() { + let output = b"less 487 (GNU regular expressions) +Copyright (C) 1984-2016 Mark Nudelman + +less comes with NO WARRANTY, to the extent permitted by law. +For information about the terms of redistribution, +see the file named README in the less distribution. +Homepage: http://www.greenwoodsoftware.com/less"; + + assert_eq!(Some(487), parse_less_version(output)); +} + +#[test] +fn test_parse_less_version_529() { + let output = b"less 529 (Spencer V8 regular expressions) +Copyright (C) 1984-2017 Mark Nudelman + +less comes with NO WARRANTY, to the extent permitted by law. +For information about the terms of redistribution, +see the file named README in the less distribution. +Homepage: http://www.greenwoodsoftware.com/less"; + + assert_eq!(Some(529), parse_less_version(output)); +} + +#[test] +fn test_parse_less_version_551() { + let output = b"less 551 (PCRE regular expressions) +Copyright (C) 1984-2019 Mark Nudelman + +less comes with NO WARRANTY, to the extent permitted by law. +For information about the terms of redistribution, +see the file named README in the less distribution. +Home page: http://www.greenwoodsoftware.com/less"; + + assert_eq!(Some(551), parse_less_version(output)); +} + +#[test] +fn test_parse_less_version_wrong_program() { + let output = b"more from util-linux 2.34"; + + assert_eq!(None, parse_less_version(output)); +} diff --git a/src/bat_utils/mod.rs b/src/bat_utils/mod.rs new file mode 100644 index 00000000..7812e7c0 --- /dev/null +++ b/src/bat_utils/mod.rs @@ -0,0 +1,5 @@ +pub mod assets; +pub mod dirs; +mod less; +pub mod output; +pub mod terminal; diff --git a/src/bat_utils/output.rs b/src/bat_utils/output.rs new file mode 100644 index 00000000..eef00cd5 --- /dev/null +++ b/src/bat_utils/output.rs @@ -0,0 +1,160 @@ +// https://github.com/sharkdp/bat a1b9334a44a2c652f52dddaa83dbacba57372468 +// src/output.rs +// See src/bat_utils/LICENSE +use std::env; +use std::ffi::OsString; +use std::io::{self, Write}; +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; + +use super::less::retrieve_less_version; + +use crate::config; +use crate::features::navigate; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(dead_code)] +pub enum PagingMode { + Always, + QuitIfOneScreen, + Never, +} +use crate::errors::*; + +pub enum OutputType { + Pager(Child), + Stdout(io::Stdout), +} + +impl OutputType { + pub fn from_mode( + mode: PagingMode, + pager: Option<&str>, + config: &config::Config, + ) -> Result { + use self::PagingMode::*; + Ok(match mode { + Always => OutputType::try_pager(false, pager, config)?, + QuitIfOneScreen => OutputType::try_pager(true, pager, config)?, + _ => OutputType::stdout(), + }) + } + + /// Try to launch the pager. Fall back to stdout in case of errors. + fn try_pager( + quit_if_one_screen: bool, + pager_from_config: Option<&str>, + config: &config::Config, + ) -> Result { + let mut replace_arguments_to_less = false; + + let pager_from_env = match ( + env::var("DELTA_PAGER"), + env::var("BAT_PAGER"), + env::var("PAGER"), + ) { + (Ok(delta_pager), _, _) => Some(delta_pager), + (_, Ok(bat_pager), _) => Some(bat_pager), + (_, _, Ok(pager)) => { + // less needs to be called with the '-R' option in order to properly interpret ANSI + // color sequences. If someone has set PAGER="less -F", we therefore need to + // overwrite the arguments and add '-R'. + // We only do this for PAGER, since it is used in other contexts. + replace_arguments_to_less = true; + Some(pager) + } + _ => None, + }; + + let pager_from_config = pager_from_config.map(|p| p.to_string()); + + if pager_from_config.is_some() { + replace_arguments_to_less = false; + } + + let pager = pager_from_config + .or(pager_from_env) + .unwrap_or_else(|| String::from("less")); + + let pagerflags = + shell_words::split(&pager).chain_err(|| "Could not parse pager command.")?; + + match pagerflags.split_first() { + Some((pager_name, args)) => { + let pager_path = PathBuf::from(pager_name); + + let is_less = pager_path.file_stem() == Some(&OsString::from("less")); + + let mut process = if is_less { + let mut p = Command::new(&pager_path); + if args.is_empty() || replace_arguments_to_less { + p.args(vec!["--RAW-CONTROL-CHARS"]); + + // Passing '--no-init' fixes a bug with '--quit-if-one-screen' in older + // versions of 'less'. Unfortunately, it also breaks mouse-wheel support. + // + // See: http://www.greenwoodsoftware.com/less/news.530.html + // + // For newer versions (530 or 558 on Windows), we omit '--no-init' as it + // is not needed anymore. + match retrieve_less_version() { + None => { + p.arg("--no-init"); + } + Some(version) + if (version < 530 || (cfg!(windows) && version < 558)) => + { + p.arg("--no-init"); + } + _ => {} + } + + if quit_if_one_screen { + p.arg("--quit-if-one-screen"); + } + } else { + p.args(args); + } + p.env("LESSCHARSET", "UTF-8"); + p + } else { + let mut p = Command::new(&pager_path); + p.args(args); + p + }; + if config.navigate { + process.args(&["--pattern", &navigate::make_navigate_regexp(&config)]); + } + Ok(process + .env("LESSANSIENDCHARS", "mK") + .stdin(Stdio::piped()) + .spawn() + .map(OutputType::Pager) + .unwrap_or_else(|_| OutputType::stdout())) + } + None => Ok(OutputType::stdout()), + } + } + + fn stdout() -> Self { + OutputType::Stdout(io::stdout()) + } + + pub fn handle(&mut self) -> Result<&mut dyn Write> { + Ok(match *self { + OutputType::Pager(ref mut command) => command + .stdin + .as_mut() + .chain_err(|| "Could not open stdin for pager")?, + OutputType::Stdout(ref mut handle) => handle, + }) + } +} + +impl Drop for OutputType { + fn drop(&mut self) { + if let OutputType::Pager(ref mut command) = *self { + let _ = command.wait(); + } + } +} diff --git a/src/bat_utils/terminal.rs b/src/bat_utils/terminal.rs new file mode 100644 index 00000000..50351c48 --- /dev/null +++ b/src/bat_utils/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, +) -> 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() +} -- cgit v1.2.3