@@ -25,13 +25,15 @@ mod subcommands;
25
25
mod tests;
26
26
27
27
use std:: ffi:: OsString ;
28
- use std:: io:: { self , Cursor , ErrorKind , IsTerminal , Write } ;
29
- use std:: process;
28
+ use std:: io:: { self , BufRead , Cursor , ErrorKind , IsTerminal , Write } ;
29
+ use std:: process:: { self , Command , Stdio } ;
30
30
31
31
use bytelines:: ByteLinesReader ;
32
32
33
33
use crate :: cli:: Call ;
34
+ use crate :: config:: delta_unreachable;
34
35
use crate :: delta:: delta;
36
+ use crate :: subcommands:: { SubCmdKind , SubCommand } ;
35
37
use crate :: utils:: bat:: assets:: list_languages;
36
38
use crate :: utils:: bat:: output:: { OutputType , PagingMode } ;
37
39
@@ -67,7 +69,7 @@ fn main() -> std::io::Result<()> {
67
69
ctrlc:: set_handler ( || { } )
68
70
. unwrap_or_else ( |err| eprintln ! ( "Failed to set ctrl-c handler: {err}" ) ) ;
69
71
let exit_code = run_app ( std:: env:: args_os ( ) . collect :: < Vec < _ > > ( ) , None ) ?;
70
- // when you call process::exit, no destructors are called, so we want to do it only once, here
72
+ // when you call process::exit, no drop impls are called, so we want to do it only once, here
71
73
process:: exit ( exit_code) ;
72
74
}
73
75
@@ -81,19 +83,16 @@ pub fn run_app(
81
83
) -> std:: io:: Result < i32 > {
82
84
let env = env:: DeltaEnv :: init ( ) ;
83
85
let assets = utils:: bat:: assets:: load_highlighting_assets ( ) ;
84
- let opt = cli:: Opt :: from_args_and_git_config ( args, & env, assets) ;
86
+ let ( call , opt) = cli:: Opt :: from_args_and_git_config ( args, & env, assets) ;
85
87
86
- let opt = match opt {
87
- Call :: Version ( msg) => {
88
- writeln ! ( std:: io:: stdout( ) , "{}" , msg. trim_end( ) ) ?;
89
- return Ok ( 0 ) ;
90
- }
91
- Call :: Help ( msg) => {
92
- OutputType :: oneshot_write ( msg) ?;
93
- return Ok ( 0 ) ;
94
- }
95
- Call :: Delta ( opt) => opt,
96
- } ;
88
+ if let Call :: Version ( msg) = call {
89
+ writeln ! ( std:: io:: stdout( ) , "{}" , msg. trim_end( ) ) ?;
90
+ return Ok ( 0 ) ;
91
+ } else if let Call :: Help ( msg) = call {
92
+ OutputType :: oneshot_write ( msg) ?;
93
+ return Ok ( 0 ) ;
94
+ }
95
+ let opt = opt. expect ( "Opt is set" ) ;
97
96
98
97
let subcommand_result = if let Some ( shell) = opt. generate_completion {
99
98
Some ( subcommands:: generate_completion:: generate_completion_file (
@@ -153,26 +152,134 @@ pub fn run_app(
153
152
output_type. handle ( ) . unwrap ( )
154
153
} ;
155
154
156
- if let ( Some ( minus_file) , Some ( plus_file) ) = ( & config. minus_file , & config. plus_file ) {
157
- let exit_code = subcommands:: diff:: diff ( minus_file, plus_file, & config, & mut writer) ;
158
- return Ok ( exit_code) ;
159
- }
155
+ let subcmd = match call {
156
+ Call :: DeltaDiff ( _, minus, plus) => {
157
+ match subcommands:: diff:: build_diff_cmd ( & minus, & plus, & config) {
158
+ Err ( code) => return Ok ( code) ,
159
+ Ok ( val) => val,
160
+ }
161
+ }
162
+ Call :: SubCommand ( _, subcmd) => subcmd,
163
+ Call :: Delta ( _) => SubCommand :: none ( ) ,
164
+ Call :: Help ( _) | Call :: Version ( _) => delta_unreachable ( "help/version handled earlier" ) ,
165
+ } ;
160
166
161
- if io:: stdin ( ) . is_terminal ( ) {
162
- eprintln ! (
163
- "\
164
- The main way to use delta is to configure it as the pager for git: \
165
- see https://github.com/dandavison/delta#get-started. \
166
- You can also use delta to diff two files: `delta file_A file_B`."
167
- ) ;
168
- return Ok ( config. error_exit_code ) ;
169
- }
167
+ if subcmd. is_none ( ) {
168
+ // Default delta run: read input from stdin, write to stdout or pager (pager started already^).
169
+ if io:: stdin ( ) . is_terminal ( ) {
170
+ eprintln ! (
171
+ "\
172
+ The main way to use delta is to configure it as the pager for git: \
173
+ see https://github.com/dandavison/delta#get-started. \
174
+ You can also use delta to diff two files: `delta file_A file_B`."
175
+ ) ;
176
+ return Ok ( config. error_exit_code ) ;
177
+ }
178
+
179
+ let res = delta ( io:: stdin ( ) . lock ( ) . byte_lines ( ) , & mut writer, & config) ;
170
180
171
- if let Err ( error) = delta ( io:: stdin ( ) . lock ( ) . byte_lines ( ) , & mut writer, & config) {
172
- match error. kind ( ) {
173
- ErrorKind :: BrokenPipe => return Ok ( 0 ) ,
174
- _ => eprintln ! ( "{error}" ) ,
181
+ if let Err ( error) = res {
182
+ match error. kind ( ) {
183
+ ErrorKind :: BrokenPipe => return Ok ( 0 ) ,
184
+ _ => {
185
+ eprintln ! ( "{error}" ) ;
186
+ return Ok ( config. error_exit_code ) ;
187
+ }
188
+ }
175
189
}
176
- } ;
177
- Ok ( 0 )
190
+
191
+ Ok ( 0 )
192
+ } else {
193
+ // First start a subcommand, and process input from it to delta(). Also handle
194
+ // subcommand stderr and exit codes, e.g. for git and diff logic.
195
+
196
+ let ( subcmd_bin, subcmd_args) = subcmd. args . split_first ( ) . unwrap ( ) ;
197
+ let subcmd_kind = subcmd. kind ; // for easier {} formatting
198
+
199
+ let subcmd_bin_path = match grep_cli:: resolve_binary ( std:: path:: PathBuf :: from ( subcmd_bin) ) {
200
+ Ok ( path) => path,
201
+ Err ( err) => {
202
+ eprintln ! ( "Failed to resolve command {subcmd_bin:?}: {err}" ) ;
203
+ return Ok ( config. error_exit_code ) ;
204
+ }
205
+ } ;
206
+
207
+ let cmd = Command :: new ( subcmd_bin)
208
+ . args ( subcmd_args. iter ( ) )
209
+ . stdout ( Stdio :: piped ( ) )
210
+ . stderr ( Stdio :: piped ( ) )
211
+ . spawn ( ) ;
212
+
213
+ if let Err ( err) = cmd {
214
+ eprintln ! ( "Failed to execute the command {subcmd_bin:?}: {err}" ) ;
215
+ return Ok ( config. error_exit_code ) ;
216
+ }
217
+ let mut cmd = cmd. unwrap ( ) ;
218
+
219
+ let cmd_stdout = cmd. stdout . as_mut ( ) . expect ( "Failed to open stdout" ) ;
220
+ let cmd_stdout_buf = io:: BufReader :: new ( cmd_stdout) ;
221
+
222
+ let res = delta ( cmd_stdout_buf. byte_lines ( ) , & mut writer, & config) ;
223
+
224
+ if let Err ( error) = res {
225
+ match error. kind ( ) {
226
+ ErrorKind :: BrokenPipe => return Ok ( 0 ) ,
227
+ _ => {
228
+ eprintln ! ( "{error}" ) ;
229
+ return Ok ( config. error_exit_code ) ;
230
+ }
231
+ }
232
+ } ;
233
+
234
+ let subcmd_status = cmd
235
+ . wait ( )
236
+ . unwrap_or_else ( |_| {
237
+ delta_unreachable ( & format ! ( "{subcmd_kind} process not running." ) ) ;
238
+ } )
239
+ . code ( )
240
+ . unwrap_or_else ( || {
241
+ eprintln ! ( "{subcmd_kind} process terminated without exit status." ) ;
242
+ config. error_exit_code
243
+ } ) ;
244
+
245
+ let mut stderr_lines =
246
+ io:: BufReader :: new ( cmd. stderr . expect ( "Failed to open stderr" ) ) . lines ( ) ;
247
+ if let Some ( line1) = stderr_lines. next ( ) {
248
+ // prefix the first error line prefixed with the called subcommand
249
+ eprintln ! (
250
+ "{}: {}" ,
251
+ subcmd_kind,
252
+ line1. unwrap_or( "<delta: could not parse line>" . into( ) )
253
+ ) ;
254
+ }
255
+
256
+ // On `git diff` unknown option: print first line (which is an error message) but not
257
+ // the remainder (which is the entire --help text).
258
+ if !( subcmd_status == 129
259
+ && matches ! ( subcmd_kind, SubCmdKind :: GitDiff | SubCmdKind :: Git ( _) ) )
260
+ {
261
+ for line in stderr_lines {
262
+ eprintln ! ( "{}" , line. unwrap_or( "<delta: could not parse line>" . into( ) ) ) ;
263
+ }
264
+ }
265
+
266
+ if matches ! ( subcmd_kind, SubCmdKind :: GitDiff | SubCmdKind :: Diff ) && subcmd_status >= 2 {
267
+ eprintln ! (
268
+ "{subcmd_kind} process failed with exit status {subcmd_status}. Command was: {}" ,
269
+ format_args!(
270
+ "{} {}" ,
271
+ subcmd_bin_path. display( ) ,
272
+ shell_words:: join(
273
+ subcmd_args
274
+ . iter( )
275
+ . map( |arg0: & OsString | std:: ffi:: OsStr :: to_string_lossy( arg0) )
276
+ ) ,
277
+ )
278
+ ) ;
279
+ }
280
+
281
+ Ok ( subcmd_status)
282
+ }
283
+
284
+ // `output_type` drop impl runs here
178
285
}
0 commit comments