1
0
mirror of https://github.com/sharkdp/bat synced 2026-08-03 18:51:44 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
sharkdp 696d1b3ed3 Update CHANGELOG 2020-04-25 13:17:07 +02:00
sharkdp 17d98724a5 Bump version to v0.15.0 2020-04-25 13:09:24 +02:00
sharkdp 7d7ee1b594 Update cached verison of themes 2020-04-25 13:08:30 +02:00
sharkdp 682b4209f2 Update solarized themes to braver/Solarized, closes #941 2020-04-25 13:08:30 +02:00
sharkdp 41ddcbd240 Remove old Sublime themes 2020-04-25 13:08:30 +02:00
sharkdp 48a7ce3bf2 Write error messages to pager, if attached
closes #946
2020-04-25 13:08:00 +02:00
Lzu Tao 3bcc4d0d55 lto=true and codegen-units=1 together 2020-04-24 17:39:25 +02:00
Lzu Tao 8821bca656 appveyor: Use rustup minimal profile 2020-04-24 17:39:25 +02:00
Lzu Tao 56111aa20d simplify build.rs 2020-04-24 17:39:25 +02:00
sharkdp 5fe8a8342b Add annotations again 2020-04-24 16:51:11 +02:00
sharkdp 989109145a Add '-d' for '--diff' 2020-04-24 16:12:38 +02:00
sharkdp 23afc8e90c Skip non-file inputs when using --diff 2020-04-24 16:12:38 +02:00
sharkdp 8e18786556 Run 'cargo fmt' 2020-04-24 16:11:05 +02:00
sharkdp 81488adf8b Use unreachable!(…) 2020-04-24 16:11:05 +02:00
Lzu Tao e37e9c1214 Fix some clippy lints
Some might actually improve perf
2020-04-24 16:11:05 +02:00
Lzu Tao a4828387c1 simplify build.rs 2020-04-24 16:11:05 +02:00
sharkdp abeac8b12e Bump min. required Rust version to 1.40 2020-04-24 09:57:57 +02:00
sharkdp 82e7786e74 Implementation of 'bat --diff'
This adds a new `--diff` option that can be used to only show lines
close to Git changes (added/removed/modified lines). The amount of
additional context can be controlled with `--diff-context=N`.

closes #23
2020-04-24 09:57:57 +02:00
sharkdp 0064321323 cargo fmt 2020-04-23 00:56:35 +02:00
sharkdp 9e622a1a5f Update version in README 2020-04-23 00:56:27 +02:00
31 changed files with 269 additions and 113 deletions
+3 -3
View File
@@ -166,9 +166,6 @@
[submodule "assets/themes/Nord-sublime"] [submodule "assets/themes/Nord-sublime"]
path = assets/themes/Nord-sublime path = assets/themes/Nord-sublime
url = https://github.com/crabique/Nord-plist.git url = https://github.com/crabique/Nord-plist.git
[submodule "assets/themes/solarized-sublime"]
path = assets/themes/solarized-sublime
url = https://github.com/paulcpederson/solarized-sublime.git
[submodule "assets/syntaxes/Vue"] [submodule "assets/syntaxes/Vue"]
path = assets/syntaxes/02_Extra/Vue path = assets/syntaxes/02_Extra/Vue
url = https://github.com/vuejs/vue-syntax-highlight.git url = https://github.com/vuejs/vue-syntax-highlight.git
@@ -179,3 +176,6 @@
[submodule "assets/syntaxes/02_Extra/Stylus"] [submodule "assets/syntaxes/02_Extra/Stylus"]
path = assets/syntaxes/02_Extra/Stylus path = assets/syntaxes/02_Extra/Stylus
url = https://github.com/billymoon/Stylus url = https://github.com/billymoon/Stylus
[submodule "assets/themes/Solarized"]
path = assets/themes/Solarized
url = https://github.com/braver/Solarized
+3 -3
View File
@@ -32,15 +32,15 @@ matrix:
# Minimum Rust supported channel. # Minimum Rust supported channel.
- os: linux - os: linux
rust: 1.37.0 rust: 1.40.0
env: TARGET=x86_64-unknown-linux-gnu env: TARGET=x86_64-unknown-linux-gnu
- os: linux - os: linux
rust: 1.37.0 rust: 1.40.0
env: env:
- TARGET=x86_64-unknown-linux-musl - TARGET=x86_64-unknown-linux-musl
- CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc - CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc
- os: osx - os: osx
rust: 1.37.0 rust: 1.40.0
env: TARGET=x86_64-apple-darwin env: TARGET=x86_64-apple-darwin
# Disable nightly for now # Disable nightly for now
+22 -1
View File
@@ -4,12 +4,33 @@
## Features ## Features
## Bugfixes ## Bugfixes
## Other ## Other
## `bat` as a library
## New syntaxes ## New syntaxes
## New themes ## New themes
## `bat` as a library
## Packaging ## Packaging
# v0.15.0
## Features
- Add a new `--diff`/`-d` option that can be used to only show lines surrounding
Git changes, i.e. added, removed or modified lines. The amount of additional
context can be controlled with `--diff-context=N`. See #23 and #940
## Bugfixes
- Error message printed in the middle of the output for another file, see #946
## Themes
- Updated version of the Solarized dark/light themes, see #941
## `bat` as a library
- There are a few changes in the "low level" API (the `Config` struct has changed and
the error handler needs a new `&mut dyn Write` argument). The high-level API is not
affected.
# v0.14.0 # v0.14.0
Generated
+1 -1
View File
@@ -114,7 +114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "bat" name = "bat"
version = "0.14.0" version = "0.15.0"
dependencies = [ dependencies = [
"ansi_colours 1.0.1 (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)", "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
+2 -2
View File
@@ -7,7 +7,7 @@ license = "MIT/Apache-2.0"
name = "bat" name = "bat"
readme = "README.md" readme = "README.md"
repository = "https://github.com/sharkdp/bat" repository = "https://github.com/sharkdp/bat"
version = "0.14.0" version = "0.15.0"
exclude = [ exclude = [
"assets/syntaxes/*", "assets/syntaxes/*",
"assets/themes/*", "assets/themes/*",
@@ -79,7 +79,7 @@ assert_cmd = "1.0.1"
[build-dependencies] [build-dependencies]
clap = { version = "2.33", optional = true } clap = { version = "2.33", optional = true }
liquid = { version = "0.20", optional = true } liquid = { version = "0.20", optional = true }
lazy_static = { version = "1.4", optional = true }
[profile.release] [profile.release]
lto = true lto = true
codegen-units = 1
+2 -2
View File
@@ -187,7 +187,7 @@ the most recent release of `bat`, download the latest `.deb` package from the
[release page](https://github.com/sharkdp/bat/releases) and install it via: [release page](https://github.com/sharkdp/bat/releases) and install it via:
```bash ```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture sudo dpkg -i bat_0.15.0_amd64.deb # adapt version number and architecture
``` ```
### On Alpine Linux ### On Alpine Linux
@@ -348,7 +348,7 @@ binaries are also available: look for archives with `musl` in the file name.
### From source ### From source
If you want to build `bat` from source, you need Rust 1.37 or If you want to build `bat` from source, you need Rust 1.40 or
higher. You can then use `cargo` to build everything: higher. You can then use `cargo` to build everything:
```bash ```bash
+1 -1
View File
@@ -8,7 +8,7 @@ environment:
install: install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host %TARGET% - rustup-init.exe -y --default-host %TARGET% --profile minimal
- set PATH=%PATH%;C:\cargo\bin - set PATH=%PATH%;C:\cargo\bin
- rustc -Vv - rustc -Vv
- cargo -V - cargo -V
BIN
View File
Binary file not shown.
Vendored Submodule
+1
Submodule assets/themes/Solarized added at 87e01090cf
+8 -14
View File
@@ -12,16 +12,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use lazy_static::lazy_static;
// Read environment variables. // Read environment variables.
lazy_static! { let project_name = option_env!("PROJECT_NAME").unwrap_or("bat");
static ref PROJECT_NAME: &'static str = option_env!("PROJECT_NAME").unwrap_or("bat"); let executable_name = option_env!("PROJECT_EXECUTABLE").unwrap_or(project_name);
static ref PROJECT_VERSION: &'static str = option_env!("CARGO_PKG_VERSION").unwrap(); static PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
static ref EXECUTABLE_NAME: &'static str = option_env!("PROJECT_EXECUTABLE")
.or(option_env!("PROJECT_NAME"))
.unwrap_or("bat");
}
/// Generates a file from a liquid template. /// Generates a file from a liquid template.
fn template( fn template(
@@ -38,16 +32,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
let variables = liquid::object!({ let variables = liquid::object!({
"PROJECT_NAME": PROJECT_NAME.to_owned(), "PROJECT_NAME": project_name,
"PROJECT_EXECUTABLE": EXECUTABLE_NAME.to_owned(), "PROJECT_EXECUTABLE": executable_name,
"PROJECT_VERSION": PROJECT_VERSION.to_owned(), "PROJECT_VERSION": PROJECT_VERSION,
}); });
let out_dir_env = std::env::var_os("OUT_DIR").expect("OUT_DIR to be set in build.rs"); let out_dir_env = std::env::var_os("OUT_DIR").expect("OUT_DIR to be set in build.rs");
let out_dir = Path::new(&out_dir_env); let out_dir = Path::new(&out_dir_env);
std::fs::create_dir_all(out_dir.join("assets/manual")).unwrap(); fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
std::fs::create_dir_all(out_dir.join("assets/completions")).unwrap(); fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
template( template(
&variables, &variables,
+1 -1
View File
@@ -180,7 +180,7 @@ apt install bat
batの最新リリースを実行する場合、または Ubuntu/Debian の古いバージョンを使用している場合は、[release page](https://github.com/sharkdp/bat/releases) から最新の `.deb` パッケージをダウンロードし、 batの最新リリースを実行する場合、または Ubuntu/Debian の古いバージョンを使用している場合は、[release page](https://github.com/sharkdp/bat/releases) から最新の `.deb` パッケージをダウンロードし、
次の方法でインストールします: 次の方法でインストールします:
```bash ```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture sudo dpkg -i bat_0.15.0_amd64.deb # adapt version number and architecture
``` ```
### On Alpine Linux ### On Alpine Linux
+1 -1
View File
@@ -170,7 +170,7 @@ apt install bat
만약 최근 릴리즈된 bat을 사용을 원하거나 buntu/Debian 예전 버전을 사용하는 경우, [릴리즈 페이지](https://github.com/sharkdp/bat/releases)에서 다음과 같이 `.deb` 패키지를 받아 설치 할 수도 있습니다: 만약 최근 릴리즈된 bat을 사용을 원하거나 buntu/Debian 예전 버전을 사용하는 경우, [릴리즈 페이지](https://github.com/sharkdp/bat/releases)에서 다음과 같이 `.deb` 패키지를 받아 설치 할 수도 있습니다:
```bash ```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture sudo dpkg -i bat_0.15.0_amd64.deb # adapt version number and architecture
``` ```
### On Alpine Linux ### On Alpine Linux
+3 -3
View File
@@ -180,7 +180,7 @@ impl HighlightingAssets {
theme theme
); );
} }
&self.theme_set.themes[self.fallback_theme.unwrap_or(Self::default_theme())] &self.theme_set.themes[self.fallback_theme.unwrap_or_else(|| Self::default_theme())]
} }
} }
} }
@@ -204,7 +204,7 @@ impl HighlightingAssets {
let path = Path::new(path_str); let path = Path::new(path_str);
let line_syntax = self.get_first_line_syntax(&mut input.reader); let line_syntax = self.get_first_line_syntax(&mut input.reader);
let absolute_path = path.canonicalize().ok().unwrap_or(path.to_owned()); let absolute_path = path.canonicalize().ok().unwrap_or_else(|| path.to_owned());
match mapping.get_syntax_for(absolute_path) { match mapping.get_syntax_for(absolute_path) {
Some(MappingTarget::MapTo(syntax_name)) => { Some(MappingTarget::MapTo(syntax_name)) => {
// TODO: we should probably return an error here if this syntax can not be // TODO: we should probably return an error here if this syntax can not be
@@ -221,7 +221,7 @@ impl HighlightingAssets {
OpenedInputKind::StdIn | OpenedInputKind::CustomReader => { OpenedInputKind::StdIn | OpenedInputKind::CustomReader => {
if let Some(ref name) = input.metadata.user_provided_name { if let Some(ref name) = input.metadata.user_provided_name {
self.get_extension_syntax(&name) self.get_extension_syntax(&name)
.or(self.get_first_line_syntax(&mut input.reader)) .or_else(|| self.get_first_line_syntax(&mut input.reader))
} else { } else {
self.get_first_line_syntax(&mut input.reader) self.get_first_line_syntax(&mut input.reader)
} }
+1 -1
View File
@@ -13,7 +13,7 @@ pub struct AssetsMetadata {
creation_time: Option<SystemTime>, creation_time: Option<SystemTime>,
} }
const FILENAME: &'static str = "metadata.yaml"; const FILENAME: &str = "metadata.yaml";
impl AssetsMetadata { impl AssetsMetadata {
pub(crate) fn new(current_version: &str) -> AssetsMetadata { pub(crate) fn new(current_version: &str) -> AssetsMetadata {
+26 -16
View File
@@ -15,7 +15,7 @@ use console::Term;
use bat::{ use bat::{
assets::HighlightingAssets, assets::HighlightingAssets,
config::Config, config::{Config, VisibleLines},
error::*, error::*,
input::Input, input::Input,
line_range::{HighlightedLineRanges, LineRange, LineRanges}, line_range::{HighlightedLineRanges, LineRange, LineRanges},
@@ -80,7 +80,7 @@ impl App {
let paging_mode = match self.matches.value_of("paging") { let paging_mode = match self.matches.value_of("paging") {
Some("always") => PagingMode::Always, Some("always") => PagingMode::Always,
Some("never") => PagingMode::Never, Some("never") => PagingMode::Never,
Some("auto") | _ => { Some("auto") | None => {
if self.matches.occurrences_of("plain") > 1 { if self.matches.occurrences_of("plain") > 1 {
// If we have -pp as an option when in auto mode, the pager should be disabled. // If we have -pp as an option when in auto mode, the pager should be disabled.
PagingMode::Never PagingMode::Never
@@ -99,6 +99,7 @@ impl App {
PagingMode::Never PagingMode::Never
} }
} }
_ => unreachable!("other values for --paging are not allowed"),
}; };
let mut syntax_mapping = SyntaxMapping::builtin(); let mut syntax_mapping = SyntaxMapping::builtin();
@@ -147,13 +148,14 @@ impl App {
match self.matches.value_of("wrap") { match self.matches.value_of("wrap") {
Some("character") => WrappingMode::Character, Some("character") => WrappingMode::Character,
Some("never") => WrappingMode::NoWrapping, Some("never") => WrappingMode::NoWrapping,
Some("auto") | _ => { Some("auto") | None => {
if style_components.plain() { if style_components.plain() {
WrappingMode::NoWrapping WrappingMode::NoWrapping
} else { } else {
WrappingMode::Character WrappingMode::Character
} }
} }
_ => unreachable!("other values for --paging are not allowed"),
} }
} else { } else {
// We don't have the tty width when piping to another program. // We don't have the tty width when piping to another program.
@@ -196,13 +198,23 @@ impl App {
} }
}) })
.unwrap_or_else(|| String::from(HighlightingAssets::default_theme())), .unwrap_or_else(|| String::from(HighlightingAssets::default_theme())),
line_ranges: self visible_lines: if self.matches.is_present("diff") {
.matches VisibleLines::DiffContext(
.values_of("line-range") self.matches
.map(|vs| vs.map(LineRange::from).collect()) .value_of("diff-context")
.transpose()? .and_then(|t| t.parse().ok())
.map(LineRanges::from) .unwrap_or(2),
.unwrap_or_default(), )
} else {
VisibleLines::Ranges(
self.matches
.values_of("line-range")
.map(|vs| vs.map(LineRange::from).collect())
.transpose()?
.map(LineRanges::from)
.unwrap_or_default(),
)
},
style_components, style_components,
syntax_mapping, syntax_mapping,
pager: self.matches.value_of("pager"), pager: self.matches.value_of("pager"),
@@ -238,19 +250,17 @@ impl App {
.map(|values| values.collect()); .map(|values| values.collect());
let mut filenames_or_none: Box<dyn Iterator<Item = _>> = match filenames { let mut filenames_or_none: Box<dyn Iterator<Item = _>> = match filenames {
Some(ref filenames) => { Some(ref filenames) => Box::new(filenames.iter().map(|name| Some(OsStr::new(*name)))),
Box::new(filenames.into_iter().map(|name| Some(OsStr::new(*name))))
}
None => Box::new(std::iter::repeat(None)), None => Box::new(std::iter::repeat(None)),
}; };
let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect()); let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
if files.is_none() { if files.is_none() {
let input = Input::stdin().with_name(filenames_or_none.nth(0).unwrap_or(None)); let input = Input::stdin().with_name(filenames_or_none.next().unwrap_or(None));
return Ok(vec![input]); return Ok(vec![input]);
} }
let files_or_none: Box<dyn Iterator<Item = _>> = match files { let files_or_none: Box<dyn Iterator<Item = _>> = match files {
Some(ref files) => Box::new(files.into_iter().map(|name| Some(*name))), Some(ref files) => Box::new(files.iter().map(|name| Some(*name))),
None => Box::new(std::iter::repeat(None)), None => Box::new(std::iter::repeat(None)),
}; };
@@ -264,7 +274,7 @@ impl App {
} }
} }
} }
return Ok(file_input); Ok(file_input)
} }
fn style_components(&self) -> Result<StyleComponents> { fn style_components(&self) -> Result<StyleComponents> {
+2 -1
View File
@@ -53,5 +53,6 @@ pub fn assets_from_cache_or_binary() -> Result<HighlightingAssets> {
} }
} }
Ok(HighlightingAssets::from_cache(&cache_dir).unwrap_or(HighlightingAssets::from_binary())) Ok(HighlightingAssets::from_cache(&cache_dir)
.unwrap_or_else(|_| HighlightingAssets::from_binary()))
} }
+31 -1
View File
@@ -105,6 +105,35 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
data to bat from STDIN when bat does not otherwise know \ data to bat from STDIN when bat does not otherwise know \
the filename."), the filename."),
) )
.arg(
Arg::with_name("diff")
.long("diff")
.short("d")
.help("Only show lines that have been added/removed/modified.")
.long_help(
"Only show lines that have been added/removed/modified with respect \
to the Git index. Use --diff-context=N to control how much context you want to see.",
),
)
.arg(
Arg::with_name("diff-context")
.long("diff-context")
.overrides_with("diff-context")
.takes_value(true)
.value_name("N")
.validator(
|n| {
n.parse::<usize>()
.map_err(|_| "must be a number")
.map(|_| ()) // Convert to Result<(), &str>
.map_err(|e| e.to_string())
}, // Convert to Result<(), String>
)
.hidden_short_help(true)
.long_help(
"Include N lines of context around added/removed/modified lines when using '--diff'.",
),
)
.arg( .arg(
Arg::with_name("tabs") Arg::with_name("tabs")
.long("tabs") .long("tabs")
@@ -152,7 +181,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
t.parse::<i32>() t.parse::<i32>()
.map_err(|_e| "must be an offset or number") .map_err(|_e| "must be an offset or number")
.and_then(|v| if v == 0 && !is_offset { .and_then(|v| if v == 0 && !is_offset {
Err("terminal width cannot be zero".into()) Err("terminal width cannot be zero")
} else { } else {
Ok(()) Ok(())
}) })
@@ -339,6 +368,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.takes_value(true) .takes_value(true)
.number_of_values(1) .number_of_values(1)
.value_name("N:M") .value_name("N:M")
.conflicts_with("diff")
.help("Only print the lines from N to M.") .help("Only print the lines from N to M.")
.long_help( .long_help(
"Only print the specified range of lines for each file. \ "Only print the specified range of lines for each file. \
+4 -3
View File
@@ -35,10 +35,11 @@ pub fn generate_config_file() -> bat::error::Result<()> {
match config_dir { match config_dir {
Some(path) => fs::create_dir_all(path)?, Some(path) => fs::create_dir_all(path)?,
None => { None => {
return Ok(Err(format!( return Err(format!(
"Unable to write config file to: {}", "Unable to write config file to: {}",
config_file.to_string_lossy() config_file.to_string_lossy()
))?) )
.into());
} }
} }
} }
@@ -77,7 +78,7 @@ pub fn generate_config_file() -> bat::error::Result<()> {
config_file.to_string_lossy() config_file.to_string_lossy()
); );
return Ok(()); Ok(())
} }
pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> { pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> {
+3 -2
View File
@@ -70,7 +70,7 @@ pub fn list_languages(config: &Config) -> Result<()> {
if config.loop_through { if config.loop_through {
for lang in languages { for lang in languages {
write!(stdout, "{}:{}\n", lang.name, lang.file_extensions.join(","))?; writeln!(stdout, "{}:{}", lang.name, lang.file_extensions.join(","))?;
} }
} else { } else {
let longest = languages let longest = languages
@@ -210,7 +210,8 @@ fn main() {
match result { match result {
Err(error) => { Err(error) => {
default_error_handler(&error); let stderr = std::io::stderr();
default_error_handler(&error, &mut stderr.lock());
process::exit(1); process::exit(1);
} }
Ok(false) => { Ok(false) => {
+29 -6
View File
@@ -5,6 +5,32 @@ use crate::style::StyleComponents;
use crate::syntax_mapping::SyntaxMapping; use crate::syntax_mapping::SyntaxMapping;
use crate::wrapping::WrappingMode; use crate::wrapping::WrappingMode;
#[derive(Debug, Clone)]
pub enum VisibleLines {
/// Show all lines which are included in the line ranges
Ranges(LineRanges),
#[cfg(feature = "git")]
/// Only show lines surrounding added/deleted/modified lines
DiffContext(usize),
}
impl VisibleLines {
pub fn diff_mode(&self) -> bool {
match self {
Self::Ranges(_) => false,
#[cfg(feature = "git")]
Self::DiffContext(_) => true,
}
}
}
impl Default for VisibleLines {
fn default() -> Self {
VisibleLines::Ranges(LineRanges::default())
}
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Config<'a> { pub struct Config<'a> {
/// The explicitly configured language, if any /// The explicitly configured language, if any
@@ -39,8 +65,8 @@ pub struct Config<'a> {
#[cfg(feature = "paging")] #[cfg(feature = "paging")]
pub paging_mode: PagingMode, pub paging_mode: PagingMode,
/// Specifies the lines that should be printed /// Specifies which lines should be printed
pub line_ranges: LineRanges, pub visible_lines: VisibleLines,
/// The syntax highlighting theme /// The syntax highlighting theme
pub theme: String, pub theme: String,
@@ -62,10 +88,7 @@ pub struct Config<'a> {
fn default_config_should_include_all_lines() { fn default_config_should_include_all_lines() {
use crate::line_range::RangeCheckResult; use crate::line_range::RangeCheckResult;
assert_eq!( assert_eq!(LineRanges::default().check(17), RangeCheckResult::InRange);
Config::default().line_ranges.check(17),
RangeCheckResult::InRange
);
} }
#[test] #[test]
+78 -8
View File
@@ -1,9 +1,13 @@
use std::io::{self, Write}; use std::io::{self, Write};
use crate::assets::HighlightingAssets; use crate::assets::HighlightingAssets;
use crate::config::Config; use crate::config::{Config, VisibleLines};
#[cfg(feature = "git")]
use crate::diff::{get_git_diff, LineChanges};
use crate::error::*; use crate::error::*;
use crate::input::{Input, InputReader, OpenedInput}; use crate::input::{Input, InputReader, OpenedInput};
#[cfg(feature = "git")]
use crate::line_range::LineRange;
use crate::line_range::{LineRanges, RangeCheckResult}; use crate::line_range::{LineRanges, RangeCheckResult};
use crate::output::OutputType; use crate::output::OutputType;
#[cfg(feature = "paging")] #[cfg(feature = "paging")]
@@ -27,7 +31,7 @@ impl<'b> Controller<'b> {
pub fn run_with_error_handler( pub fn run_with_error_handler(
&self, &self,
inputs: Vec<Input>, inputs: Vec<Input>,
handle_error: impl Fn(&Error), handle_error: impl Fn(&Error, &mut dyn Write),
) -> Result<bool> { ) -> Result<bool> {
let mut output_type; let mut output_type;
@@ -41,9 +45,9 @@ impl<'b> Controller<'b> {
if self.config.paging_mode != PagingMode::Never { if self.config.paging_mode != PagingMode::Never {
let call_pager = inputs.iter().any(|ref input| { let call_pager = inputs.iter().any(|ref input| {
if let InputKind::OrdinaryFile(ref path) = input.kind { if let InputKind::OrdinaryFile(ref path) = input.kind {
return Path::new(path).exists(); Path::new(path).exists()
} else { } else {
return true; true
} }
}); });
if !call_pager { if !call_pager {
@@ -58,16 +62,56 @@ impl<'b> Controller<'b> {
output_type = OutputType::stdout(); output_type = OutputType::stdout();
} }
let attached_to_pager = output_type.is_pager();
let writer = output_type.handle()?; let writer = output_type.handle()?;
let mut no_errors: bool = true; let mut no_errors: bool = true;
let stderr = io::stderr();
let print_error = |error: &Error, write: &mut dyn Write| {
if attached_to_pager {
handle_error(error, write);
} else {
handle_error(error, &mut stderr.lock());
}
};
for input in inputs.into_iter() { for input in inputs.into_iter() {
match input.open(io::stdin().lock()) { match input.open(io::stdin().lock()) {
Err(error) => { Err(error) => {
handle_error(&error); print_error(&error, writer);
no_errors = false; no_errors = false;
} }
Ok(mut opened_input) => { Ok(mut opened_input) => {
#[cfg(feature = "git")]
let line_changes = if self.config.visible_lines.diff_mode()
|| (!self.config.loop_through && self.config.style_components.changes())
{
match opened_input.kind {
crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
let diff = get_git_diff(path);
// Skip files without Git modifications
if self.config.visible_lines.diff_mode()
&& diff
.as_ref()
.map(|changes| changes.is_empty())
.unwrap_or(false)
{
continue;
}
diff
}
_ if self.config.visible_lines.diff_mode() => {
// Skip non-file inputs in diff mode
continue;
}
_ => None,
}
} else {
None
};
let mut printer: Box<dyn Printer> = if self.config.loop_through { let mut printer: Box<dyn Printer> = if self.config.loop_through {
Box::new(SimplePrinter::new()) Box::new(SimplePrinter::new())
} else { } else {
@@ -75,13 +119,21 @@ impl<'b> Controller<'b> {
&self.config, &self.config,
&self.assets, &self.assets,
&mut opened_input, &mut opened_input,
#[cfg(feature = "git")]
&line_changes,
)) ))
}; };
let result = self.print_file(&mut *printer, writer, &mut opened_input); let result = self.print_file(
&mut *printer,
writer,
&mut opened_input,
#[cfg(feature = "git")]
&line_changes,
);
if let Err(error) = result { if let Err(error) = result {
handle_error(&error); print_error(&error, writer);
no_errors = false; no_errors = false;
} }
} }
@@ -96,13 +148,31 @@ impl<'b> Controller<'b> {
printer: &mut dyn Printer, printer: &mut dyn Printer,
writer: &mut dyn Write, writer: &mut dyn Write,
input: &mut OpenedInput, input: &mut OpenedInput,
#[cfg(feature = "git")] line_changes: &Option<LineChanges>,
) -> Result<()> { ) -> Result<()> {
if !input.reader.first_line.is_empty() || self.config.style_components.header() { if !input.reader.first_line.is_empty() || self.config.style_components.header() {
printer.print_header(writer, input)?; printer.print_header(writer, input)?;
} }
if !input.reader.first_line.is_empty() { if !input.reader.first_line.is_empty() {
self.print_file_ranges(printer, writer, &mut input.reader, &self.config.line_ranges)?; let line_ranges = match self.config.visible_lines {
VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(),
#[cfg(feature = "git")]
VisibleLines::DiffContext(context) => {
let mut line_ranges: Vec<LineRange> = vec![];
if let Some(line_changes) = line_changes {
for line in line_changes.keys() {
let line = *line as usize;
line_ranges.push(LineRange::new(line - context, line + context));
}
}
LineRanges::from(line_ranges)
}
};
self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
} }
printer.print_footer(writer, input)?; printer.print_footer(writer, input)?;
+7 -4
View File
@@ -1,4 +1,5 @@
use error_chain::error_chain; use error_chain::error_chain;
use std::io::Write;
error_chain! { error_chain! {
foreign_links { foreign_links {
@@ -11,7 +12,7 @@ error_chain! {
} }
} }
pub fn default_error_handler(error: &Error) { pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
use ansi_term::Colour::Red; use ansi_term::Colour::Red;
match error { match error {
@@ -21,14 +22,16 @@ pub fn default_error_handler(error: &Error) {
::std::process::exit(0); ::std::process::exit(0);
} }
Error(ErrorKind::SerdeYamlError(_), _) => { Error(ErrorKind::SerdeYamlError(_), _) => {
eprintln!( writeln!(
output,
"{}: Error while parsing metadata.yaml file: {}", "{}: Error while parsing metadata.yaml file: {}",
Red.paint("[bat error]"), Red.paint("[bat error]"),
error error
); )
.ok();
} }
_ => { _ => {
eprintln!("{}: {}", Red.paint("[bat error]"), error); writeln!(output, "{}: {}", Red.paint("[bat error]"), error).ok();
} }
}; };
} }
+1 -1
View File
@@ -30,7 +30,7 @@ impl LineRange {
fn parse_range(range_raw: &str) -> Result<LineRange> { fn parse_range(range_raw: &str) -> Result<LineRange> {
let mut new_range = LineRange::default(); let mut new_range = LineRange::default();
if range_raw.bytes().nth(0).ok_or("Empty line range")? == b':' { if range_raw.bytes().next().ok_or("Empty line range")? == b':' {
new_range.upper = range_raw[1..].parse()?; new_range.upper = range_raw[1..].parse()?;
return Ok(new_range); return Ok(new_range);
} else if range_raw.bytes().last().ok_or("Empty line range")? == b':' { } else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
+14
View File
@@ -125,6 +125,20 @@ impl OutputType {
OutputType::Stdout(io::stdout()) OutputType::Stdout(io::stdout())
} }
#[cfg(feature = "paging")]
pub(crate) fn is_pager(&self) -> bool {
if let OutputType::Pager(_) = self {
true
} else {
false
}
}
#[cfg(not(feature = "paging"))]
pub(crate) fn is_pager(&self) -> bool {
false
}
pub fn handle(&mut self) -> Result<&mut dyn Write> { pub fn handle(&mut self) -> Result<&mut dyn Write> {
Ok(match *self { Ok(match *self {
#[cfg(feature = "paging")] #[cfg(feature = "paging")]
+8 -8
View File
@@ -36,15 +36,15 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> { fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
let str_from_utf8 = |seq| std::str::from_utf8(seq).ok(); let str_from_utf8 = |seq| std::str::from_utf8(seq).ok();
let decoded = None let decoded = input
.or(input.get(0..1).and_then(str_from_utf8).map(|c| (c, 1))) .get(0..1)
.or(input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2))) .and_then(str_from_utf8)
.or(input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3))) .map(|c| (c, 1))
.or(input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4))); .or_else(|| input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2)))
.or_else(|| input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3)))
.or_else(|| input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4)));
let decoded_char = decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n)); decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n))
decoded_char
} }
pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
+2 -2
View File
@@ -6,7 +6,7 @@ use syntect::parsing::SyntaxReference;
use crate::{ use crate::{
assets::HighlightingAssets, assets::HighlightingAssets,
config::Config, config::{Config, VisibleLines},
controller::Controller, controller::Controller,
error::Result, error::Result,
input::Input, input::Input,
@@ -205,7 +205,7 @@ impl<'a> PrettyPrinter<'a> {
/// Specify the lines that should be printed (default: all) /// Specify the lines that should be printed (default: all)
pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self { pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
self.config.line_ranges = ranges; self.config.visible_lines = VisibleLines::Ranges(ranges);
self self
} }
+9 -24
View File
@@ -24,7 +24,7 @@ use crate::config::Config;
use crate::decorations::LineChangesDecoration; use crate::decorations::LineChangesDecoration;
use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration}; use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration};
#[cfg(feature = "git")] #[cfg(feature = "git")]
use crate::diff::{get_git_diff, LineChanges}; use crate::diff::LineChanges;
use crate::error::*; use crate::error::*;
use crate::input::OpenedInput; use crate::input::OpenedInput;
use crate::line_range::RangeCheckResult; use crate::line_range::RangeCheckResult;
@@ -90,7 +90,7 @@ pub(crate) struct InteractivePrinter<'a> {
ansi_prefix_sgr: String, ansi_prefix_sgr: String,
content_type: Option<ContentType>, content_type: Option<ContentType>,
#[cfg(feature = "git")] #[cfg(feature = "git")]
pub line_changes: Option<LineChanges>, pub line_changes: &'a Option<LineChanges>,
highlighter: Option<HighlightLines<'a>>, highlighter: Option<HighlightLines<'a>>,
syntax_set: &'a SyntaxSet, syntax_set: &'a SyntaxSet,
background_color_highlight: Option<Color>, background_color_highlight: Option<Color>,
@@ -101,6 +101,8 @@ impl<'a> InteractivePrinter<'a> {
config: &'a Config, config: &'a Config,
assets: &'a HighlightingAssets, assets: &'a HighlightingAssets,
input: &mut OpenedInput, input: &mut OpenedInput,
#[cfg(feature = "git")]
line_changes: &'a Option<LineChanges>,
) -> Self { ) -> Self {
let theme = assets.get_theme(&config.theme); let theme = assets.get_theme(&config.theme);
@@ -145,9 +147,6 @@ impl<'a> InteractivePrinter<'a> {
panel_width = 0; panel_width = 0;
} }
#[cfg(feature = "git")]
let mut line_changes = None;
let highlighter = if input let highlighter = if input
.reader .reader
.content_type .content_type
@@ -155,18 +154,6 @@ impl<'a> InteractivePrinter<'a> {
{ {
None None
} else { } else {
// Get the Git modifications
#[cfg(feature = "git")]
{
use crate::input::OpenedInputKind;
if config.style_components.changes() {
if let OpenedInputKind::OrdinaryFile(ref path) = input.kind {
line_changes = get_git_diff(path);
}
}
}
// Determine the type of syntax for highlighting // Determine the type of syntax for highlighting
let syntax = assets.get_syntax(config.language, input, &config.syntax_mapping); let syntax = assets.get_syntax(config.language, input, &config.syntax_mapping);
Some(HighlightLines::new(syntax, theme)) Some(HighlightLines::new(syntax, theme))
@@ -216,7 +203,7 @@ impl<'a> InteractivePrinter<'a> {
if self.config.style_components.grid() { if self.config.style_components.grid() {
format!("{}", text_filled) format!("{}", text_filled)
} else { } else {
format!("{}", text_filled) text_filled
} }
} }
} }
@@ -243,10 +230,8 @@ impl<'a> Printer for InteractivePrinter<'a> {
Yellow.paint("[bat warning]"), Yellow.paint("[bat warning]"),
input.description().full, input.description().full,
)?; )?;
} else { } else if self.config.style_components.grid() {
if self.config.style_components.grid() { self.print_horizontal_line(handle, '┬')?;
self.print_horizontal_line(handle, '┬')?;
}
} }
return Ok(()); return Ok(());
} }
@@ -318,9 +303,9 @@ impl<'a> Printer for InteractivePrinter<'a> {
let snip_right = let snip_right =
"".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2); "".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2);
write!( writeln!(
handle, handle,
"{}\n", "{}",
self.colors self.colors
.grid .grid
.paint(format!("{}{}{}{}", panel, snip_left, title, snip_right)) .paint(format!("{}{}{}{}", panel, snip_left, title, snip_right))
+4 -1
View File
@@ -30,7 +30,10 @@ impl<'a> SyntaxMapping<'a> {
.insert("**/.ssh/config", MappingTarget::MapTo("SSH Config")) .insert("**/.ssh/config", MappingTarget::MapTo("SSH Config"))
.unwrap(); .unwrap();
mapping mapping
.insert("**/bat/config", MappingTarget::MapTo("Bourne Again Shell (bash)")) .insert(
"**/bat/config",
MappingTarget::MapTo("Bourne Again Shell (bash)"),
)
.unwrap(); .unwrap();
mapping mapping
.insert( .insert(
+1 -1
View File
@@ -30,7 +30,7 @@ pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term:
0x0d => Color::Purple, 0x0d => Color::Purple,
0x0e => Color::Cyan, 0x0e => Color::Cyan,
0x0f => Color::White, 0x0f => Color::White,
_ => unreachable!("The 0x0f color encoding does not allow for codes higher than 0x0f") _ => unreachable!("The 0x0f color encoding does not allow for codes higher than 0x0f"),
} }
} else if true_color { } else if true_color {
RGB(color.r, color.g, color.b) RGB(color.r, color.g, color.b)
+1 -1
View File
@@ -4,7 +4,7 @@ use bat::assets::HighlightingAssets;
#[test] #[test]
fn no_duplicate_extensions() { fn no_duplicate_extensions() {
const KNOWN_EXCEPTIONS: &[&'static str] = &[ const KNOWN_EXCEPTIONS: &[&str] = &[
// The '.h' extension currently appears in multiple syntaxes: C, C++, Objective C, // The '.h' extension currently appears in multiple syntaxes: C, C++, Objective C,
// Objective C++ // Objective C++
"h", "h",