summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2021-08-29 20:25:28 -0400
committerDan Davison <dandavison7@gmail.com>2021-08-29 22:51:06 -0400
commit8db8e908432539543341109f94a19dc2c6f4af7f (patch)
treefa8a283f1b908ff80ae39b892842c63dc85caede
parent97203ce2338a54b62080116f17b9b928279ae8c8 (diff)
Refactor: subcommands module
-rw-r--r--src/main.rs567
-rw-r--r--src/subcommands/diff.rs130
-rw-r--r--src/subcommands/list_syntax_themes.rs88
-rw-r--r--src/subcommands/mod.rs6
-rw-r--r--src/subcommands/sample_diff.rs (renamed from src/sample_diff.rs)0
-rw-r--r--src/subcommands/show_config.rs178
-rw-r--r--src/subcommands/show_syntax_themes.rs135
-rw-r--r--src/subcommands/show_themes.rs60
8 files changed, 616 insertions, 548 deletions
diff --git a/src/main.rs b/src/main.rs
index 3b844767..08d31fd8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,25 +20,19 @@ mod handlers;
mod options;
mod paint;
mod parse_style;
-mod sample_diff;
mod style;
+mod subcommands;
mod syntect_color;
mod tests;
-use std::io::{self, ErrorKind, Read, Write};
-use std::path::PathBuf;
+use std::io::{self, ErrorKind};
use std::process;
use bytelines::ByteLinesReader;
-use itertools::Itertools;
-use structopt::StructOpt;
use crate::bat_utils::assets::{list_languages, HighlightingAssets};
-use crate::bat_utils::output::{OutputType, PagingMode};
-use crate::config::delta_unreachable;
+use crate::bat_utils::output::OutputType;
use crate::delta::delta;
-use crate::options::get::get_themes;
-use crate::options::theme::is_light_syntax_theme;
pub mod errors {
error_chain! {
@@ -51,6 +45,17 @@ pub mod errors {
}
#[cfg(not(tarpaulin_include))]
+fn main() -> std::io::Result<()> {
+ // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
+ // See https://github.com/dandavison/delta/issues/681
+ ctrlc::set_handler(|| {})
+ .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {}", err));
+ let exit_code = run_app()?;
+ // when you call process::exit, no destructors are called, so we want to do it only once, here
+ process::exit(exit_code);
+}
+
+#[cfg(not(tarpaulin_include))]
// An Ok result contains the desired process exit code. Note that 1 is used to
// report that two files differ when delta is called with two positional
// arguments and without standard input; 2 is used to report a real problem.
@@ -62,13 +67,13 @@ fn run_app() -> std::io::Result<i32> {
list_languages()?;
return Ok(0);
} else if opt.list_syntax_themes {
- list_syntax_themes()?;
+ subcommands::list_syntax_themes::list_syntax_themes()?;
return Ok(0);
} else if opt.show_syntax_themes {
- show_syntax_themes()?;
+ subcommands::show_syntax_themes::show_syntax_themes()?;
return Ok(0);
} else if opt.show_themes {
- show_themes(opt.dark, opt.light, opt.computed.is_light_mode)?;
+ subcommands::show_themes::show_themes(opt.dark, opt.light, opt.computed.is_light_mode)?;
return Ok(0);
}
@@ -78,7 +83,7 @@ fn run_app() -> std::io::Result<i32> {
if _show_config {
let stdout = io::stdout();
let mut stdout = stdout.lock();
- show_config(&config, &mut stdout)?;
+ subcommands::show_config::show_config(&config, &mut stdout)?;
return Ok(0);
}
@@ -87,7 +92,7 @@ fn run_app() -> std::io::Result<i32> {
let mut writer = output_type.handle().unwrap();
if atty::is(atty::Stream::Stdin) {
- let exit_code = diff(
+ let exit_code = subcommands::diff::diff(
config.minus_file.as_ref(),
config.plus_file.as_ref(),
&config,
@@ -104,537 +109,3 @@ fn run_app() -> std::io::Result<i32> {
};
Ok(0)
}
-
-#[cfg(not(tarpaulin_include))]
-fn main() -> std::io::Result<()> {
- // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
- // See https://github.com/dandavison/delta/issues/681
- ctrlc::set_handler(|| {})
- .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {}", err));
- let exit_code = run_app()?;
- // when you call process::exit, no destructors are called, so we want to do it only once, here
- process::exit(exit_code);
-}
-
-/// Run `git diff` on the files provided on the command line and display the output.
-fn diff(
- minus_file: Option<&PathBuf>,
- plus_file: Option<&PathBuf>,
- config: &config::Config,
- writer: &mut dyn Write,
-) -> i32 {
- use std::io::BufReader;
- if minus_file.is_none() || plus_file.is_none() {
- eprintln!(
- "\
-The main way to use delta is to configure it as the pager for git: \
-see https://github.com/dandavison/delta#configuration. \
-You can also use delta to diff two files: `delta file_A file_B`."
- );
- return config.error_exit_code;
- }
- let minus_file = minus_file.unwrap();
- let plus_file = plus_file.unwrap();
-
- let diff_command = "git";
- let diff_command_path = match grep_cli::resolve_binary(PathBuf::from(diff_command)) {
- Ok(path) => path,
- Err(err) => {
- eprintln!("Failed to resolve command '{}': {}", diff_command, err);
- return config.error_exit_code;
- }
- };
-
- let diff_process = process::Command::new(diff_command_path)
- .args(&["diff", "--no-index"])
- .args(&[minus_file, plus_file])
- .stdout(process::Stdio::piped())
- .spawn();
-
- if let Err(err) = diff_process {
- eprintln!("Failed to execute the command '{}': {}", diff_command, err);
- return config.error_exit_code;
- }
- let mut diff_process = diff_process.unwrap();
-
- if let Err(error) = delta(
- BufReader::new(diff_process.stdout.take().unwrap()).byte_lines(),
- writer,
- config,
- ) {
- match error.kind() {
- ErrorKind::BrokenPipe => return 0,
- _ => {
- eprintln!("{}", error);
- return config.error_exit_code;
- }
- }
- };
-
- // Return the exit code from the `git diff` processl, so that the exit code
- // contract of `delta file_A file_B` is the same as that of `diff file_A
- // file_B` (i.e. 0 if same, 1 if different, 2 if error).
- diff_process
- .wait()
- .unwrap_or_else(|_| {
- delta_unreachable(&format!("'{}' process not running.", diff_command));
- })
- .code()
- .unwrap_or_else(|| {
- eprintln!("'{}' process terminated without exit status.", diff_command);
- config.error_exit_code
- })
-}
-
-fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
- // styles first
- writeln!(
- writer,
- " commit-style = {commit_style}
- file-style = {file_style}
- hunk-header-style = {hunk_header_style}
- minus-style = {minus_style}
- minus-non-emph-style = {minus_non_emph_style}
- minus-emph-style = {minus_emph_style}
- minus-empty-line-marker-style = {minus_empty_line_marker_style}
- zero-style = {zero_style}
- plus-style = {plus_style}
- plus-non-emph-style = {plus_non_emph_style}
- plus-emph-style = {plus_emph_style}
- plus-empty-line-marker-style = {plus_empty_line_marker_style}
- whitespace-error-style = {whitespace_error_style}",
- commit_style = config.commit_style.to_painted_string(),
- file_style = config.file_style.to_painted_string(),
- hunk_header_style = config.hunk_header_style.to_painted_string(),
- minus_emph_style = config.minus_emph_style.to_painted_string(),
- minus_empty_line_marker_style = config.minus_empty_line_marker_style.to_painted_string(),
- minus_non_emph_style = config.minus_non_emph_style.to_painted_string(),
- minus_style = config.minus_style.to_painted_string(),
- plus_emph_style = config.plus_emph_style.to_painted_string(),
- plus_empty_line_marker_style = config.plus_empty_line_marker_style.to_painted_string(),
- plus_non_emph_style = config.plus_non_emph_style.to_painted_string(),
- plus_style = config.plus_style.to_painted_string(),
- whitespace_error_style = config.whitespace_error_style.to_painted_string(),
- zero_style = config.zero_style.to_painted_string(),
- )?;
- // Everything else
- writeln!(
- writer,
- " true-color = {true_color}
- file-added-label = {file_added_label}
- file-modified-label = {file_modified_label}
- file-removed-label = {file_removed_label}
- file-renamed-label = {file_renamed_label}",
- true_color = config.true_color,
- file_added_label = format_option_value(&config.file_added_label),
- file_modified_label = format_option_value(&config.file_modified_label),
- file_removed_label = format_option_value(&config.file_removed_label),
- file_renamed_label = format_option_value(&config.file_renamed_label),
- )?;
- writeln!(
- writer,
- " hyperlinks = {hyperlinks}",
- hyperlinks = config.hyperlinks
- )?;
- if config.hyperlinks {
- writeln!(
- writer,
- " hyperlinks-file-link-format = {hyperlinks_file_link_format}",
- hyperlinks_file_link_format = format_option_value(&config.hyperlinks_file_link_format),
- )?
- }
- writeln!(
- writer,
- " inspect-raw-lines = {inspect_raw_lines}
- keep-plus-minus-markers = {keep_plus_minus_markers}",
- inspect_raw_lines = match config.inspect_raw_lines {
- cli::InspectRawLines::True => "true",
- cli::InspectRawLines::False => "false",
- },
- keep_plus_minus_markers = config.keep_plus_minus_markers,
- )?;
- writeln!(
- writer,
- " line-numbers = {line_numbers}",
- line_numbers = config.line_numbers
- )?;
- if config.line_numbers {
- writeln!(
- writer,
- " line-numbers-minus-style = {line_numbers_minus_style}
- line-numbers-zero-style = {line_numbers_zero_style}
- line-numbers-plus-style = {line_numbers_plus_style}
- line-numbers-left-style = {line_numbers_left_style}
- line-numbers-right-style = {line_numbers_right_style}
- line-numbers-left-format = {line_numbers_left_format}
- line-numbers-right-format = {line_numbers_right_format}",
- line_numbers_minus_style = config.line_numbers_minus_style.to_painted_string(),
- line_numbers_zero_style = config.line_numbers_zero_style.to_painted_string(),
- line_numbers_plus_style = config.line_numbers_plus_style.to_painted_string(),
- line_numbers_left_style = config.line_numbers_left_style.to_painted_string(),
- line_numbers_right_style = config.line_numbers_right_style.to_painted_string(),
- line_numbers_left_format = format_option_value(&config.line_numbers_left_format),
- line_numbers_right_format = format_option_value(&config.line_numbers_right_format),
- )?
- }
- writeln!(
- writer,
- " max-line-distance = {max_line_distance}
- max-line-length = {max_line_length}
- navigate = {navigate}
- navigate-regexp = {navigate_regexp}
- pager = {pager}
- paging = {paging_mode}
- side-by-side = {side_by_side}
- syntax-theme = {syntax_theme}
- width = {width}
- tabs = {tab_width}
- word-diff-regex = {tokenization_regex}",
- max_line_distance = config.max_line_distance,
- max_line_length = config.max_line_length,
- navigate = config.navigate,
- navigate_regexp = match &config.navigate_regexp {
- None => "".to_string(),
- Some(s) => s.to_string(),
- },
- pager = config.pager.clone().unwrap_or_else(|| "none".to_string()),
- paging_mode = match config.paging_mode {
- PagingMode::Always => "always",
- PagingMode::Never => "never",
- PagingMode::QuitIfOneScreen => "auto",
- },
- side_by_side = config.side_by_side,
- syntax_theme = config
- .syntax_theme
- .clone()
- .map(|t| t.name.unwrap_or_else(|| "none".to_string()))
- .unwrap_or_else(|| "none".to_string()),
- width = match config.decorations_width {
- cli::Width::Fixed(width) => width.to_string(),
- cli::Width::Variable => "variable".to_string(),
- },
- tab_width = config.tab_width,
- tokenization_regex = format_option_value(&config.tokenization_regex.to_string()),
- )?;
- Ok(())
-}
-
-// Heuristics determining whether to quote string option values when printing values intended for
-// git config.
-fn format_option_value<S>(s: S) -> String
-where
- S: AsRef<str>,
-{
- let s = s.as_ref();
- if s.ends_with(' ')
- || s.starts_with(' ')
- || s.contains(&['\\', '{', '}', ':'][..])
- || s.is_empty()
- {
- format!("'{}'", s)
- } else {
- s.to_string()
- }
-}
-
-fn show_themes(dark: bool, light: bool, computed_theme_is_light: bool) -> std::io::Result<()> {
- use bytelines::ByteLines;
- use sample_diff::DIFF;
- use std::io::BufReader;
- let mut input = DIFF.to_vec();
-
- if !atty::is(atty::Stream::Stdin) {
- let mut buf = Vec::new();
- io::stdin().lock().read_to_end(&mut buf)?;
- if !buf.is_empty() {
- input = buf;
- }
- };
-
- let git_config = git_config::GitConfig::try_create();
- let opt =
- cli::Opt::from_iter_and_git_config(&["", "", "--navigate", "--show-themes"], git_config);
- let mut output_type =
- OutputType::from_mode(PagingMode::Always, None, &config::Config::from(opt)).unwrap();
- let title_style = ansi_term::Style::new().bold();
- let writer = output_type.handle().unwrap();
-
- for theme in &get_themes(git_config::GitConfig::try_create()) {
- let git_config = git_config::GitConfig::try_create();
- let opt = cli::Opt::from_iter_and_git_config(&["", "", "--features", theme], git_config);
- let is_dark_theme = opt.dark;
- let is_light_theme = opt.light;
- let config = config::Config::from(opt);
-
- if (!computed_theme_is_light && is_dark_theme)
- || (computed_theme_is_light && is_light_theme)
- || (dark && light)
- {
- writeln!(writer, "\n\nTheme: {}\n", title_style.paint(theme))?;
-
- if let Err(error) = delta(ByteLines::new(BufReader::new(&input[0..])), writer, &config)
- {
- match error.kind() {
- ErrorKind::BrokenPipe => process::exit(0),
- _ => eprintln!("{}", error),
- }
- }
- }
- }
-
- Ok(())
-}
-
-#[cfg(not(tarpaulin_include))]
-fn show_syntax_themes() -> std::io::Result<()> {
- let assets = HighlightingAssets::new();
- let mut output_type = OutputType::from_mode(
- PagingMode::QuitIfOneScreen,
- None,
- &config::Config::from(cli::Opt::default()),
- )
- .unwrap();
- let mut writer = output_type.handle().unwrap();
-
- let stdin_data = if !atty::is(atty::Stream::Stdin) {
- let mut buf = Vec::new();
- io::stdin().lock().read_to_end(&mut buf)?;
- if !buf.is_empty() {
- Some(buf)
- } else {
- None
- }
- } else {
- None
- };
-
- let make_opt = || {
- let mut opt = cli::Opt::from_args();
- opt.computed.syntax_set = assets.syntax_set.clone();
- opt
- };
- let opt = make_opt();
-
- if !(opt.dark || opt.light) {
- _show_syntax_themes(opt, false, &mut writer, stdin_data.as_ref())?;
- _show_syntax_themes(make_opt(), true, &mut writer, stdin_data.as_ref())?;
- } else if opt.light {
- _show_syntax_themes(opt, true, &mut writer, stdin_data.as_ref())?;
- } else {
- _show_syntax_themes(opt, false, &mut writer, stdin_data.as_ref())?
- };
- Ok(())
-}
-
-fn _show_syntax_themes(
- mut opt: cli::Opt,
- is_light_mode: bool,
- writer: &mut dyn Write,
- stdin: Option<&Vec<u8>>,
-) -> std::io::Result<()> {
- use bytelines::ByteLines;
- use std::io::BufReader;
- let input = match stdin {
- Some(stdin_data) => &stdin_data[..],
- None => {
- b"\
-diff --git a/example.rs b/example.rs
-index f38589a..0f1bb83 100644
---- a/example.rs
-+++ b/example.rs
-@@ -1,5 +1,5 @@
--// Output the square of a number.
--fn print_square(num: f64) {
-- let result = f64::powf(num, 2.0);
-- println!(\"The square of {:.2} is {:.2}.\", num, result);
-+// Output the cube of a number.
-+fn print_cube(num: f64) {
-+ let result = f64::powf(num, 3.0);
-+ println!(\"The cube of {:.2} is {:.2}.\", num, result);
-"
- }
- };
-
- opt.computed.is_light_mode = is_light_mode;
- let mut config = config::Config::from(opt);
- let title_style = ansi_term::Style::new().bold();
- let assets = HighlightingAssets::new();
-
- for syntax_theme in assets
- .theme_set
- .themes
- .iter()
- .filter(|(t, _)| is_light_syntax_theme(t) == is_light_mode)
- .map(|(t, _)| t)
- {
- writeln!(
- writer,
- "\n\nSyntax theme: {}\n",
- title_style.paint(syntax_theme)
- )?;
- config.syntax_theme = Some(assets.theme_set.themes[syntax_theme.as_str()].clone());
- if let Err(error) = delta(ByteLines::new(BufReader::new(&input[0..])), writer, &config) {
- match error.kind() {
- ErrorKind::BrokenPipe => process::exit(0),
- _ => eprintln!("{}", error),
- }
- };
- }
- Ok(())
-}
-
-#[cfg(not(tarpaulin_include))]
-pub fn list_syntax_themes() -> std::io::Result<()> {
- let stdout = io::stdout();
- let mut stdout = stdout.lock();
- if atty::is(atty::Stream::Stdout) {
- _list_syntax_themes_for_humans(&mut stdout)
- } else {
- _list_syntax_themes_for_machines(&mut stdout)
- }
-}
-
-pub fn _list_syntax_themes_for_humans(writer: &mut dyn Write) -> std::io::Result<()> {
- let assets = HighlightingAssets::new();
- let themes = &assets.theme_set.themes;
-
- writeln!(writer, "Light syntax themes:")?;
- for (theme, _) in themes.iter().filter(|(t, _)| is_light_syntax_theme(*t)) {
- writeln!(writer, " {}", theme)?;
- }
- writeln!(writer, "\nDark syntax themes:")?;
- for (theme, _) in themes.iter().filter(|(t, _)| !is_light_syntax_theme(*t)) {
- writeln!(writer, " {}", theme)?;
- }
- writeln!(
- writer,
- "\nUse delta --show-syntax-themes to demo the themes."
- )?;
- Ok(())
-}
-
-pub fn _list_syntax_themes_for_machines(writer: &mut dyn Write) -> std::io::Result<()> {
- let assets = HighlightingAssets::new();
- let themes = &assets.theme_set.themes;
- for (theme, _) in themes
- .iter()
- .sorted_by_key(|(t, _)| is_light_syntax_theme(*t))
- {
- writeln!(
- writer,
- "{}\t{}",
- if is_light_syntax_theme(theme) {
- "light"
- } else {
- "dark"
- },
- theme
- )?;
- }
- Ok(())
-}
-
-#[cfg(test)]
-mod main_tests {
- use super::*;
- use std::io::{Cursor, Seek, SeekFrom};
-
- use crate::ansi;
- use crate::tests::integration_test_utils;
-
- #[test]
- fn test_show_config() {
- let config = integration_test_utils::make_config_from_args(&[]);
- let mut writer = Cursor::new(vec![0; 1024]);
- show_config(&config, &mut writer).unwrap();
- let mut s = String::new();
- writer.seek(SeekFrom::Start(0)).unwrap();
- writer.read_to_string(&mut s).unwrap();
- let s = ansi::strip_ansi_codes(&s);
- assert!(s.contains(" commit-style = raw\n"));
- assert!(s.contains(r" word-diff-regex = '\w+'"));
- }
-
- #[test]
- #[ignore] // Not working (timing out) when run by tarpaulin, presumably due to stdin detection.
- fn test_show_syntax_themes() {
- let opt = integration_test_utils::make_options_from_args(&[]);
-
- let mut writer = Cursor::new(vec![0; 1024]);
- _show_syntax_themes(opt, true, &mut writer, None).unwrap();
- let mut s = String::new();
- writer.seek(SeekFrom::Start(0)).unwrap();
- writer.read_to_string(&mut s).unwrap();
- let s = ansi::strip_ansi_codes(&s);
- assert!(s.contains("\nSyntax theme: gruvbox-light\n"));
- println!("{}", s);
- assert!(s.contains("\nfn print_cube(num: f64) {\n"));
- }
-
- #[test]
- fn test_list_syntax_themes_for_humans() {
- let mut writer = Cursor::new(vec![0; 512]);
- _list_syntax_themes_for_humans(&mut writer).unwrap();
- let mut s = String::new();
- writer.seek(SeekFrom::Start(0)).unwrap();
- writer.read_to_string(&mut s).unwrap();
- assert!(s.contains("Light syntax themes:\n"));
- assert!(s.contains(" GitHub\n"));
- assert!(s.contains("Dark syntax themes:\n"));
- assert!(s.contains(" Dracula\n"));
- }
-
- #[test]
- fn test_list_syntax_themes_for_machines() {
- let mut writer = Cursor::new(vec![0; 512]);
- _list_syntax_themes_for_machines(&mut writer).unwrap();
- let mut s = String::new();
- writer.seek(SeekFrom::Start(0)).unwrap();
- writer.read_to_string(&mut s).unwrap();
- assert!(s.contains("light GitHub\n"));
- assert!(s.contains("dark Dracula\n"));
- }
-
- #[test]
- #[ignore] // https://github.com/dandavison/delta/pull/546
- fn test_diff_same_empty_file() {
- _do_diff_test("/dev/null", "/dev/null", false);
- }
-
- #[test]
- #[cfg_attr(target_os = "windows", ignore)]
- fn test_diff_same_non_empty_file() {
- _do_diff_test("/etc/passwd", "/etc/passwd", false);
- }
-
- #[test]
- #[cfg_attr(target_os = "windows", ignore)]
- fn test_diff_empty_vs_non_empty_file() {
- _do_diff_test("/dev/null", "/etc/passwd", true);
- }
-
- #[test]
- #[cfg_attr(target_os = "windows", ignore)]
- fn test_diff_two_non_empty_files() {
- _do_diff_test("/etc/group", "/etc/passwd", true);
- }
-
- fn _do_diff_test(file_a: &str, file_b: &str, expect_diff: bool) {
- let config = integration_test_utils::make_config_from_args(&[]);
- let mut writer = Cursor::new(vec![]);
- let exit_code = diff(
- Some(&PathBuf::from(file_a)),
- Some(&PathBuf::from(file_b)),
- &config,
- &mut writer,
- );
- assert_eq!(exit_code, if expect_diff { 1 } else { 0 });
- }
-
- fn _read_to_string(cursor: &mut Cursor<Vec<u8>>) -> String {
- let mut s = String::new();
- cursor.seek(SeekFrom::Start(0)).unwrap();
- cursor.read_to_string(&mut s).unwrap();
- s
- }
-}
diff --git a/src/subcommands/diff.rs b/src/subcommands/diff.rs
new file mode 100644
index 00000000..21c7fb07
--- /dev/null
+++ b/src/subcommands/diff.rs
@@ -0,0 +1,130 @@
+use std::io::{ErrorKind, Write};
+use std::path::PathBuf;
+use std::process;
+
+use bytelines::ByteLinesReader;
+
+use crate::config::{self, delta_unreachable};
+use crate::delta;
+
+/// Run `git diff` on the files provided on the command line and display the output.
+pub fn diff(
+ minus_file: Option<&PathBuf>,
+ plus_file: Option<&PathBuf>,
+ config: &config::Config,
+ writer: &mut dyn Write,
+) -> i32 {
+ use std::io::BufReader;
+ if minus_file.is_none() || plus_file.is_none() {
+ eprintln!(
+ "\
+The main way to use delta is to configure it as the pager for git: \
+see https://github.com/dandavison/delta#configuration. \
+You can also use delta to diff two files: `delta file_A file_B`."
+ );
+ return config.error_exit_code;
+ }
+ let minus_file = minus_file.unwrap();
+ let plus_file = plus_file.unwrap();
+
+ let diff_command = "git";
+ let diff_command_path = match grep_cli::resolve_binary(PathBuf::from(diff_command)) {
+ Ok(path) => path,
+ Err(err) => {
+ eprintln!("Failed to resolve command '{}': {}", diff_command, err);
+ return config.error_exit_code;
+ }
+ };
+
+ let diff_process = process::Command::new(diff_command_path)
+ .args(&["diff", "--no-index"])
+ .args(&[minus_file, plus_file])
+ .stdout(process::Stdio::piped())
+ .spawn();
+
+ if let Err(err) = diff_process {
+ eprintln!("Failed to execute the command '{}': {}", diff_command, err);
+ return config.error_exit_code;
+ }
+ let mut diff_process = diff_process.unwrap();
+
+ if let Err(error) = delta::delta(
+ BufReader::new(diff_process.stdout.take().unwrap()).byte_lines(),
+ writer,
+ config,
+ ) {
+ match error.kind() {
+ ErrorKind::BrokenPipe => return 0,
+ _ => {
+ eprintln!("{}", error);
+ return config.error_exit_code;
+ }
+ }
+ };
+
+ // Return the exit code from the `git diff` processl, so that the exit code
+ // contract of `delta file_A file_B` is the same as that of `diff file_A
+ // file_B` (i.e. 0 if same, 1 if different, 2 if error).
+ diff_process
+ .wait()
+ .unwrap_or_else(|_| {
+ delta_unreachable(&format!("'{}' process not running.", diff_command));
+ })
+ .code()
+ .unwrap_or_else(|| {
+ eprintln!("'{}' process terminated without exit status.", diff_command);
+ config.error_exit_code
+ })
+}
+
+#[cfg(test)]
+mod main_tests {
+ use std::io::{Cursor, Read, Seek, SeekFrom};
+ use std::path::PathBuf;
+
+ use super::diff;
+ use crate::tests::integration_test_utils;
+
+ #[test]
+ #[ignore] // https://github.com/dandavison/delta/pull/546
+ fn test_diff_same_empty_file() {
+ _do_diff_test("/dev/null", "/dev/null", false);
+ }
+
+ #[test]
+ #[cfg_attr(target_os = "windows", ignore)]
+ fn test_diff_same_non_empty_file() {
+ _do_diff_test("/etc/passwd", "/etc/passwd", false);
+ }
+
+ #[test]
+ #[cfg_attr(target_os = "windows", ignore)]
+ fn test_diff_empty_vs_non_empty_file() {
+ _do_diff_test("/dev/null", "/etc/passwd", true);
+ }
+
+ #[test]
+ #[cfg_attr(target_os = "windows", ignore)]
+ fn test_diff_two_non_empty_files() {
+ _do_diff_test("/etc/group", "/etc/passwd", true);
+ }
+
+ fn _do_diff_test(file_a: &str, file_b: &str, expect_diff: bool) {
+ let config = integration_test_utils::make_config_from_args(&[]);
+ let mut writer = Cursor::new(vec![]);
+ let exit_code = diff(
+ Some(&PathBuf::from(file_a)),
+ Some(&PathBuf::from(file_b)),
+ &config,
+ &mut writer,
+ );
+ assert_eq!(exit_code, if expect_diff { 1 } else { 0 });
+ }
+
+ fn _read_to_string(cursor: &mut Cursor<Vec<u8>>) -> String {
+ let mut s = String::new();
+ cursor.seek(SeekFrom::Start(0)).unwrap();
+ cursor.read_to_string(&mut s).unwrap();
+ s
+ }
+}
diff --git a/src/subcommands/list_syntax_themes.rs b/src/subcommands/list_syntax_themes.rs
new file mode 100644
index 00000000..fe485dc5
--- /dev/null
+++ b/src/subcommands/list_syntax_themes.rs
@@ -0,0 +1,88 @@
+use std::io::{self, Write};
+
+use itertools::Itertools;
+
+use crate::bat_utils::assets::HighlightingAssets;
+use crate::options::theme::is_light_syntax_theme;
+
+#[cfg(not(tarpaulin_include))]
+pub fn list_syntax_themes() -> std::io::Result<()> {
+ let stdout = io::stdout();
+ let mut stdout = stdout.lock();
+ if atty::is(atty::Stream::Stdout) {
+ _list_syntax_themes_for_humans(&mut stdout)
+ } else {
+ _list_syntax_themes_for_machines(&mut stdout)
+ }
+}
+
+pub fn _list_syntax_themes_for_humans(writer: &mut dyn Write) -> std::io::Result<()> {
+ let assets = HighlightingAssets::new();
+ let themes = &assets.theme_set.themes;
+
+ writeln!(writer, "Light syntax themes:")?;
+ for (theme, _) in themes.iter().filter(|(t, _)| is_light_syntax_theme(*t)) {
+ writeln!(writer, " {}", theme)?;
+ }
+ writeln!(writer, "\nDark syntax themes:")?;
+ for (theme, _) in themes.iter().filter(|(t, _)| !is_light_syntax_theme(*t)) {
+ writeln!(writer, " {}", theme)?;
+ }
+ writeln!(
+ writer,
+ "\nUse delta --show-syntax-themes to demo the themes."
+ )?;
+ Ok(())
+}
+
+pub fn _list_syntax_themes_for_machines(writer: &mut dyn Write) -> std::io::Result<()> {
+ let assets = HighlightingAssets::new();
+ let themes = &assets.theme_set.themes;
+ for (theme, _) in themes
+ .iter()
+ .sorted_by_key(