diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 197 |
1 files changed, 162 insertions, 35 deletions
diff --git a/src/main.rs b/src/main.rs index d8372a9..5080dd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,14 +24,16 @@ mod subcommands; mod tests; -use std::ffi::OsString; -use std::io::{self, Cursor, ErrorKind, IsTerminal, Write}; -use std::process; +use std::ffi::{OsStr, OsString}; +use std::io::{self, BufRead, Cursor, ErrorKind, IsTerminal, Write}; +use std::process::{self, Command, Stdio}; use bytelines::ByteLinesReader; use crate::cli::Call; +use crate::config::delta_unreachable; use crate::delta::delta; +use crate::subcommands::{SubCmdKind, SubCommand}; use crate::utils::bat::assets::list_languages; use crate::utils::bat::output::{OutputType, PagingMode}; @@ -67,7 +69,7 @@ fn main() -> std::io::Result<()> { ctrlc::set_handler(|| {}) .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {err}")); let exit_code = run_app(std::env::args_os().collect::<Vec<_>>(), None)?; - // when you call process::exit, no destructors are called, so we want to do it only once, here + // when you call process::exit, no drop impls are called, so we want to do it only once, here process::exit(exit_code); } @@ -81,19 +83,25 @@ pub fn run_app( ) -> std::io::Result<i32> { let env = env::DeltaEnv::init(); let assets = utils::bat::assets::load_highlighting_assets(); - let opt = cli::Opt::from_args_and_git_config(args, &env, assets); + let (call, opt) = cli::Opt::from_args_and_git_config(args, &env, assets); - let opt = match opt { - Call::Version(msg) => { - writeln!(std::io::stdout(), "{}", msg.trim_end())?; - return Ok(0); - } - Call::Help(msg) => { - OutputType::oneshot_write(msg)?; - return Ok(0); - } - Call::Delta(opt) => opt, - }; + if let Call::Version(msg) = call { + writeln!(std::io::stdout(), "{}", msg.trim_end())?; + return Ok(0); + } else if let Call::Help(msg) = call { + OutputType::oneshot_write(msg)?; + return Ok(0); + } else if let Call::SubCommand(_, cmd) = &call { + // Set before creating the Config, which already asks for the calling process + // (not required for Call::DeltaDiff) + utils::process::set_calling_process( + &cmd.args + .iter() + .map(|arg| OsStr::to_string_lossy(arg).to_string()) + .collect::<Vec<_>>(), + ); + } + let opt = opt.unwrap_or_else(|| delta_unreachable("Opt is set")); let subcommand_result = if let Some(shell) = opt.generate_completion { Some(subcommands::generate_completion::generate_completion_file( @@ -153,26 +161,145 @@ pub fn run_app( output_type.handle().unwrap() }; - if let (Some(minus_file), Some(plus_file)) = (&config.minus_file, &config.plus_file) { - let exit_code = subcommands::diff::diff(minus_file, plus_file, &config, &mut writer); - return Ok(exit_code); - } + let subcmd = match call { + Call::DeltaDiff(_, minus, plus) => { + match subcommands::diff::build_diff_cmd(&minus, &plus, &config) { + Err(code) => return Ok(code), + Ok(val) => val, + } + } + Call::SubCommand(_, subcmd) => subcmd, + Call::Delta(_) => SubCommand::none(), + Call::Help(_) | Call::Version(_) => delta_unreachable("help/version handled earlier"), + }; - if io::stdin().is_terminal() { - eprintln!( - "\ - The main way to use delta is to configure it as the pager for git: \ - see https://github.com/dandavison/delta#get-started. \ - You can also use delta to diff two files: `delta file_A file_B`." - ); - return Ok(config.error_exit_code); - } + if subcmd.is_none() { + // Default delta run: read input from stdin, write to stdout or pager (pager started already^). - if let Err(error) = delta(io::stdin().lock().byte_lines(), &mut writer, &config) { - match error.kind() { - ErrorKind::BrokenPipe => return Ok(0), - _ => eprintln!("{error}"), + if io::stdin().is_terminal() { + eprintln!( + "\ + The main way to use delta is to configure it as the pager for git: \ + see https://github.com/dandavison/delta#get-started. \ + You can also use delta to diff two files: `delta file_A file_B`." + ); + return Ok(config.error_exit_code); } - }; - Ok(0) + + let res = delta(io::stdin().lock().byte_lines(), &mut writer, &config); + + if let Err(error) = res { + match error.kind() { + ErrorKind::BrokenPipe => return Ok(0), + _ => { + eprintln!("{error}"); + return Ok(config.error_exit_code); + } + } + } + + Ok(0) + } else { + // First start a subcommand, and pipe input from it to delta(). Also handle + // subcommand exit code and stderr (maybe truncate it, e.g. for git and diff logic). + + let (subcmd_bin, subcmd_args) = subcmd.args.split_first().unwrap(); + let subcmd_kind = subcmd.kind; // for easier {} formatting + + let subcmd_bin_path = match grep_cli::resolve_binary(std::path::PathBuf::from(subcmd_bin)) { + Ok(path) => path, + Err(err) => { + eprintln!("Failed to resolve command {subcmd_bin:?}: {err}"); + return Ok(config.error_exit_code); + } + }; + + let cmd = Command::new(subcmd_bin) + .args(subcmd_args.iter()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(); + + if let Err(err) = cmd { + eprintln!("Failed to execute the command {subcmd_bin:?}: {err}"); + return Ok(config.error_exit_code); + } + let mut cmd = cmd.unwrap(); + + let cmd_stdout = cmd + .stdout + .as_mut() + .unwrap_or_else(|| panic!("Failed to open stdout")); + let cmd_stdout_buf = io::BufReader::new(cmd_stdout); + + let res = delta(cmd_stdout_buf.byte_lines(), &mut writer, &config); + + if let Err(error) = res { + let _ = cmd.wait(); // for clippy::zombie_processes + match error.kind() { + ErrorKind::BrokenPipe => return Ok(0), + _ => { + eprintln!("{error}"); + return Ok(config.error_exit_code); + } + } + }; + + let subcmd_status = cmd + .wait() + .unwrap_or_else(|_| { + delta_unreachable(&format!("{subcmd_kind:?} process not running.")); + }) + .code() + .unwrap_or_else(|| { + eprintln!("delta: {subcmd_kind:?} process terminated without exit status."); + config.error_exit_code + }); + + let mut stderr_lines = io::BufReader::new( + cmd.stderr + .unwrap_or_else(|| panic!("Failed to open stderr")), + ) + .lines(); + if let Some(line1) = stderr_lines.next() { + // prefix the first error line with the called subcommand + eprintln!( + "{}: {}", + subcmd_kind, + line1.unwrap_or("<delta: could not parse stderr line>".into()) + ); + } + + // On `git diff` unknown option error: stop after printing the first line above (which is + // an error message), because the entire --help text follows. + if !(subcmd_status == 129 + && matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Git(_))) + { + for line in stderr_lines { + eprintln!( + "{}", + line.unwrap_or("<delta: could not parse stderr line>".into()) + ); + } + } + + if matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Diff) && subcmd_status >= 2 { + eprintln!( + "{subcmd_kind:?} process failed with exit status {subcmd_status}. Command was: {}", + format_args!( + "{} {}", + subcmd_bin_path.display(), + shell_words::join( + subcmd_args + .iter() + .map(|arg0: &OsString| std::ffi::OsStr::to_string_lossy(arg0)) + ), + ) + ); + } + + Ok(subcmd_status) + } + + // `output_type` drop impl runs here } |
