diff --git a/CHANGELOG.md b/CHANGELOG.md index c4948bfb9..36c463dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Improved handling of comments and regexp literals in Perl. Added Elvish support. +### Display + +Difftastic now displays information about file renames. Previously, it +would only show the new name. + ## 0.28 (released 29th April 2022) ### Parsing diff --git a/sample_files/compare.expected b/sample_files/compare.expected index ff6a91f0e..2b8de908c 100644 --- a/sample_files/compare.expected +++ b/sample_files/compare.expected @@ -1,174 +1,174 @@ sample_files/b2_math_before.h sample_files/b2_math_after.h -637e91c1528894ec0529bc845ef79c88 - +716834c475a1e1ca964757100e85ac88 - sample_files/bad_combine_before.rs sample_files/bad_combine_after.rs -9f61f6c7e485a2726685f718fef1f9bd - +0371c894b0dff01ee2c3e0bc7fdbeba0 - sample_files/change_outer_before.el sample_files/change_outer_after.el -1857b63ba1bfa0ccc0a4243db6b1c5c2 - +d211879a64cd8dfb087d4a43ffe1a91c - sample_files/clojure_before.clj sample_files/clojure_after.clj -b916e224f289888252cd7597bab339e6 - +8783be478554d6948894fd5d441e1089 - sample_files/comments_before.rs sample_files/comments_after.rs -f7b56285b9db37d84405f647fb15412f - +1172d43fba1610ca9bf7fbac0129a344 - sample_files/context_before.rs sample_files/context_after.rs -ef267b3bbea4b56a111427a11b24cc6a - +be9622d48c4d99f74e5ac7b45b806826 - sample_files/contiguous_before.js sample_files/contiguous_after.js -9d7bc73c3551064e67f40155abc84798 - +3d7e5e8f2782544f8650ed8cf7f54296 - sample_files/css_before.css sample_files/css_after.css -76e37e865774d0d17e73b71f627b62d7 - +d70196243265851ab9c9d2b747a79644 - sample_files/dart_before.dart sample_files/dart_after.dart -7a18450edf0db2ff3868f46a2d4e277c - +e9b21a630927910cc003ab283a11742d - sample_files/elisp_before.el sample_files/elisp_after.el -f4233ebbe6c46a7c07bc88eca20e4856 - +11648795d1fad1dd54c7c20a36e0229c - sample_files/elisp_contiguous_before.el sample_files/elisp_contiguous_after.el -e3946aef566a707c718edd7a86340566 - +1045d696fb8e2157f93ac325402fd098 - sample_files/elm_before.elm sample_files/elm_after.elm -1fe758b3148056d9c744f75640e7cd83 - +f42633bd95ab837f77ff34ebcd9387f7 - sample_files/elvish_before.elv sample_files/elvish_after.elv -0107abd4d8c25034207bedd407a499ac - +b7750d685b47f9430e6be41ce7bf5b5c - sample_files/hack_before.php sample_files/hack_after.php -b8c51005df7e1eaaeaf738a4353ac88f - +40d122bf3a88e1cf7825bc0b44be3312 - sample_files/haskell_before.hs sample_files/haskell_after.hs -9c668c79e56f1e1cecf1c02759c195a9 - +61c34a0e256be17fa665bdee58e0d30c - sample_files/hcl_before.hcl sample_files/hcl_after.hcl -f8a18b1f0b50c1a1736e13306cf3db52 - +bb8c254fc96a1c173d2604f5442eb9b5 - sample_files/helpful_before.el sample_files/helpful_after.el -bce74573e003cc6b729a63a4bc34c4af - +70fce536c4a7c020a47ad23fdeaaa6bd - sample_files/helpful-unit-test-before.el sample_files/helpful-unit-test-after.el -79597af48ff80bcf9f5d02d20c51606d - +c27fa4159c25bd3da6bef231b51a6e42 - sample_files/identical_before.scala sample_files/identical_after.scala -9c7319f61833e46a0a8cb6c01cc997c9 - +53b03b5e718e9d6b366d3762fcbeff15 - sample_files/if_before.py sample_files/if_after.py -ec9c3b52643b5fde34ea7432b9d537ac - +bdc08d613f781b410f3af9145dae64c9 - sample_files/janet_before.janet sample_files/janet_after.janet -677604a16ef62f6b6252d76d76e86265 - +f9805273f9fb89a74e018d79673d4a39 - sample_files/java_before.java sample_files/java_after.java -22c27b91fd67d2b894de9a620bcf5c35 - +9c4d96d145e64d7b9db802f59e480081 - sample_files/javascript_before.js sample_files/javascript_after.js -f4bfe92df94f89942bacc73e4a9db882 - +d3a51b2e55b6ce0ed0a1c73a8e12abe8 - sample_files/javascript_simple_before.js sample_files/javascript_simple_after.js -d0e0bb7b9e78643cecbfc9217241aafe - +4f8ba3289eb678dd871eef91707a97b1 - sample_files/json_before.json sample_files/json_after.json -3b9ee16f0ff09a830e5769da05bd8b95 - +c14c170dfe0eabc9d1c8853a4b6ae638 - sample_files/jsx_before.jsx sample_files/jsx_after.jsx -f6211fad4ccff5b7a92dbe52d25470e8 - +24d5929272f73f63b330234e6abed6ac - sample_files/load_before.js sample_files/load_after.js -58df6bfac4f237d3a1dd9201e7873f1c - +e0390cda57dc05b539eb6214dc6b87d1 - sample_files/lua_before.lua sample_files/lua_after.lua -c3d81271c060bd97dd246c1c5ea6a138 - +709bce8626215c1f207570420487abac - sample_files/metadata_before.clj sample_files/metadata_after.clj -b71577801352071fd1c6a9079f2d9dbc - +39c353f9fe498a9886ca1b01778159e3 - sample_files/modules_before.ml sample_files/modules_after.ml -694bcfa17f8adf5ed6297994287e0841 - +b51ac4a770a939d5c049dcaa5eb698b1 - sample_files/multibyte_before.py sample_files/multibyte_after.py -9287243986455b75e560080f3fd16ced - +b18d41620a3c5738ccf23e1c8739ed22 - sample_files/multiline_string_before.ml sample_files/multiline_string_after.ml -170c55099a9fdbecd39352905a691819 - +ee07940f955a13caabb9b06a3152b010 - sample_files/nest_before.rs sample_files/nest_after.rs -811805002ed9196d1156388785a1f09d - +5028e773b7845afb50a9de3cc38ed448 - sample_files/nested_slider_before.rs sample_files/nested_slider_after.rs -3a901b805dd8b541c43edb96c7e4e148 - +8c0dc12d8b13af7b4839af09f2f5dcaf - sample_files/nesting_before.el sample_files/nesting_after.el -16639761819b53b9216a9031ae94455c - +ddf6f790193312ac1d78e040d3d343bf - sample_files/nix_before.nix sample_files/nix_after.nix -337430bc90562b18dbaec9b53c0f950e - +e0e3cb59c91d1b21701c1b44fd4571ba - sample_files/ocaml_before.ml sample_files/ocaml_after.ml -1fffa5fa9392f8b46eb8b4f90c938dc2 - +4a7b5c6286aa8cfeafb04d0711495b6b - sample_files/outer_delimiter_before.el sample_files/outer_delimiter_after.el -73130b8572a4f17fa6cf828f74e226ce - +4bf8de79905ffaafb49c3e563ba1468f - sample_files/perl_before.pl sample_files/perl_after.pl -ae10c90122289e0f4298c1b962a74c2e - +778488458f4705ce21cd3211d0ce63da - sample_files/preprocesor_before.h sample_files/preprocesor_after.h -3e4331cb935cbe735a79ebc43786cd3a - +aa90bac79c8b7a5d04846c563edcf92e - sample_files/ruby_before.rb sample_files/ruby_after.rb -4a9847a91e32ec6afdc2f0b01e28d2d6 - +6bbf0b8a71bb1233550d22b37c77674a - sample_files/scala_before.scala sample_files/scala_after.scala -b276168d30704b4242fc82dc1f8382d8 - +1ff2abc236ce112f663a1add9eebcdd7 - sample_files/Session_before.kt sample_files/Session_after.kt -46994b58bb24200f82866951013f03ce - +1e3f28302745aa4b83564aad1ddb7150 - sample_files/simple_before.js sample_files/simple_after.js -4fa7016f884e76dddba4cab12cab8c61 - +e7a89d7a63ce6d2138224a0dbd66ba6c - sample_files/simple_before.txt sample_files/simple_after.txt -4b653ebe89321835c35722dd065cf6a2 - +f216c97d25554d2deac8cdb3c064db14 - sample_files/slider_before.rs sample_files/slider_after.rs -50e1df5af0bf4a1fa7211e079196f1a9 - +e5b0260017f9b80debd71953984c3ae6 - sample_files/slow_before.rs sample_files/slow_after.rs -a1d8070fda3b8fa65886a90bde64a2ab - +280716322c80218eec16062a5c4e8115 - sample_files/small_before.js sample_files/small_after.js -27bcac13aa17141718a3e6b8c0ac8f47 - +1110121ff2743f3d89af9cf49f0089db - sample_files/swift_before.swift sample_files/swift_after.swift -eeab25a68552f051a6392b5e713fbd29 - +85dc32d2a38ea4dceca5050b71d66ad5 - sample_files/syntax_error_before.js sample_files/syntax_error_after.js -fe636ad27b1aa75e0b153dfe248023bb - +a68c7499bc627b396dd9e0ab5377a3f0 - sample_files/tab_before.c sample_files/tab_after.c -36ba3231eeba6f0b67a6be9db454de19 - +1074b346606db234a158740b16e993e8 - sample_files/text_before.txt sample_files/text_after.txt -dfc3495b8d5931029b479f0c878a3219 - +173ee2a30fe72380cc448ca376d47825 - sample_files/todomvc_before.gleam sample_files/todomvc_after.gleam -c1d8b44875121d81c583dd3a8fb43232 - +83d3d2bfbb9093623970a07589f131c2 - sample_files/toml_before.toml sample_files/toml_after.toml -1e2de7235c339b07a0784498453e896c - +f6110340ed334ca58815194806e3e0ab - sample_files/typing_before.ml sample_files/typing_after.ml -ceba89e4ffc8406454d337638c7d45e6 - +b512629fd282fa116ddec72c72ea8ad2 - sample_files/whitespace_before.tsx sample_files/whitespace_after.tsx -c4151c5a44b11e04fd11c2594597ed33 - +fe09099c5fa63b5e1ca6f4b30fe5d2e6 - sample_files/yaml_before.yaml sample_files/yaml_after.yaml -8339ac699789fb3d17becce27dd3af6b - +c1d93254974e832b9407c47074479e37 - sample_files/zig_before.zig sample_files/zig_after.zig -fe7f694c4223c83ecadbbf96f791ccad - +4d0698e869f23d86fa8f3f5fc4601fc8 - diff --git a/src/inline.rs b/src/inline.rs index f63d01c3e..21b824ca9 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -17,7 +17,8 @@ pub fn print( lhs_positions: &[MatchedPos], rhs_positions: &[MatchedPos], hunks: &[Hunk], - display_path: &str, + lhs_display_path: &str, + rhs_display_path: &str, lang_name: &str, ) { let (lhs_colored, rhs_colored) = if display_options.use_color { @@ -51,7 +52,8 @@ pub fn print( println!( "{}", style::header( - display_path, + lhs_display_path, + rhs_display_path, i + 1, hunks.len(), lang_name, diff --git a/src/main.rs b/src/main.rs index f1171c0e5..01fbca184 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,10 +133,11 @@ fn main() { byte_limit, display_options, missing_as_empty, - display_path, language_override, lhs_path, rhs_path, + lhs_display_path, + rhs_display_path, } => { let lhs_path = Path::new(&lhs_path); let rhs_path = Path::new(&rhs_path); @@ -166,7 +167,8 @@ fn main() { }); } else { let diff_result = diff_file( - &display_path, + &lhs_display_path, + &rhs_display_path, lhs_path, rhs_path, &display_options, @@ -183,7 +185,8 @@ fn main() { /// Print a diff between two files. fn diff_file( - display_path: &str, + lhs_display_path: &str, + rhs_display_path: &str, lhs_path: &Path, rhs_path: &Path, display_options: &DisplayOptions, @@ -194,7 +197,8 @@ fn diff_file( ) -> DiffResult { let (lhs_bytes, rhs_bytes) = read_files_or_die(lhs_path, rhs_path, missing_as_empty); diff_file_content( - display_path, + lhs_display_path, + rhs_display_path, &lhs_bytes, &rhs_bytes, display_options.tab_width, @@ -205,7 +209,8 @@ fn diff_file( } fn diff_file_content( - display_path: &str, + lhs_display_path: &str, + rhs_display_path: &str, lhs_bytes: &[u8], rhs_bytes: &[u8], tab_width: usize, @@ -215,7 +220,8 @@ fn diff_file_content( ) -> DiffResult { if is_probably_binary(lhs_bytes) || is_probably_binary(rhs_bytes) { return DiffResult { - path: display_path.into(), + lhs_display_path: lhs_display_path.into(), + rhs_display_path: rhs_display_path.into(), language: None, lhs_src: FileContent::Binary(lhs_bytes.to_vec()), rhs_src: FileContent::Binary(rhs_bytes.to_vec()), @@ -244,7 +250,7 @@ fn diff_file_content( } // TODO: take a Path directly instead. - let path = Path::new(&display_path); + let guess_path = Path::new(&rhs_display_path); // Take the larger of the two files when guessing the // language. This is useful when we've added or removed a whole @@ -254,14 +260,15 @@ fn diff_file_content( } else { &rhs_src }; - let language = language_override.or_else(|| guess(path, guess_src)); + let language = language_override.or_else(|| guess(guess_path, guess_src)); let lang_config = language.map(tsp::from_language); if lhs_bytes == rhs_bytes { // If the two files are completely identical, return early // rather than doing any more work. return DiffResult { - path: display_path.into(), + lhs_display_path: lhs_display_path.into(), + rhs_display_path: rhs_display_path.into(), language: lang_config.map(|l| l.name.into()), lhs_src: FileContent::Text("".into()), rhs_src: FileContent::Text("".into()), @@ -337,7 +344,8 @@ fn diff_file_content( }; DiffResult { - path: display_path.into(), + lhs_display_path: lhs_display_path.into(), + rhs_display_path: rhs_display_path.into(), language: lang_name, lhs_src: FileContent::Text(lhs_src), rhs_src: FileContent::Text(rhs_src), @@ -375,6 +383,7 @@ fn diff_directories<'a>( diff_file( &rel_path.to_string_lossy(), + &rel_path.to_string_lossy(), // todo &lhs_path, &rhs_path, &display_options, @@ -407,7 +416,8 @@ fn print_diff_result(display_options: &DisplayOptions, summary: &DiffResult) { println!( "{}", style::header( - &summary.path, + &summary.lhs_display_path, + &summary.rhs_display_path, 1, 1, &lang_name, @@ -435,7 +445,8 @@ fn print_diff_result(display_options: &DisplayOptions, summary: &DiffResult) { &summary.lhs_positions, &summary.rhs_positions, &hunks, - &summary.path, + &summary.lhs_display_path, + &summary.rhs_display_path, &lang_name, ); } @@ -443,7 +454,8 @@ fn print_diff_result(display_options: &DisplayOptions, summary: &DiffResult) { side_by_side::print( &hunks, display_options, - &summary.path, + &summary.lhs_display_path, + &summary.rhs_display_path, &lang_name, lhs_src, rhs_src, @@ -459,7 +471,8 @@ fn print_diff_result(display_options: &DisplayOptions, summary: &DiffResult) { println!( "{}", style::header( - &summary.path, + &summary.lhs_display_path, + &summary.rhs_display_path, 1, 1, "binary", @@ -479,7 +492,8 @@ fn print_diff_result(display_options: &DisplayOptions, summary: &DiffResult) { println!( "{}", style::header( - &summary.path, + &summary.lhs_display_path, + &summary.rhs_display_path, 1, 1, "binary", @@ -524,6 +538,7 @@ mod tests { fn test_diff_identical_content() { let s = "foo"; let res = diff_file_content( + "foo.el", "foo.el", s.as_bytes(), s.as_bytes(), diff --git a/src/options.rs b/src/options.rs index d7d970b0f..b0e97d35f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -173,10 +173,17 @@ pub enum Mode { byte_limit: usize, display_options: DisplayOptions, missing_as_empty: bool, - display_path: String, language_override: Option, + /// The path where we can read the LHS file. This is often a + /// temporary file generated by source control. lhs_path: OsString, + /// The path where we can read the RHS file. This is often a + /// temporary file generated by source control. rhs_path: OsString, + /// The path that we should display for the LHS file. + lhs_display_path: String, + /// The path that we should display for the RHS file. + rhs_display_path: String, }, DumpTreeSitter { path: String, @@ -225,8 +232,9 @@ pub fn parse_args() -> Mode { info!("CLI arguments: {:?}", args); // TODO: document these different ways of calling difftastic. - let (display_path, lhs_path, rhs_path) = match &args[..] { + let (lhs_display_path, rhs_display_path, lhs_path, rhs_path) = match &args[..] { [lhs_path, rhs_path] => ( + lhs_path.to_owned(), rhs_path.to_owned(), lhs_path.to_owned(), rhs_path.to_owned(), @@ -234,17 +242,18 @@ pub fn parse_args() -> Mode { [display_path, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode] => { // https://git-scm.com/docs/git#Documentation/git.txt-codeGITEXTERNALDIFFcode ( + display_path.to_owned(), display_path.to_owned(), lhs_tmp_file.to_owned(), rhs_tmp_file.to_owned(), ) } - [_old_name, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode, new_name, _similarity] => + [old_name, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode, new_name, _similarity] => { // Rename file. - // TODO: mention old name as well as diffing. // TODO: where does git document these 9 arguments? ( + old_name.to_owned(), new_name.to_owned(), lhs_tmp_file.to_owned(), rhs_tmp_file.to_owned(), @@ -347,10 +356,11 @@ pub fn parse_args() -> Mode { byte_limit, display_options, missing_as_empty, - display_path: display_path.to_string_lossy().to_string(), language_override, lhs_path: lhs_path.to_owned(), rhs_path: rhs_path.to_owned(), + lhs_display_path: lhs_display_path.to_string_lossy().to_string(), + rhs_display_path: rhs_display_path.to_string_lossy().to_string(), } } diff --git a/src/side_by_side.rs b/src/side_by_side.rs index 0b24ba7e6..392139585 100644 --- a/src/side_by_side.rs +++ b/src/side_by_side.rs @@ -78,7 +78,8 @@ fn format_missing_line_num( /// Display `src` in a single column (e.g. a file removal or addition). fn display_single_column( - display_path: &str, + lhs_display_path: &str, + rhs_display_path: &str, lang_name: &str, src: &str, is_lhs: bool, @@ -89,7 +90,8 @@ fn display_single_column( let mut result = String::with_capacity(src.len()); result.push_str(&style::header( - display_path, + lhs_display_path, + rhs_display_path, 1, 1, lang_name, @@ -300,7 +302,8 @@ fn highlight_as_novel( pub fn print( hunks: &[Hunk], display_options: &DisplayOptions, - display_path: &str, + lhs_display_path: &str, + rhs_display_path: &str, lang_name: &str, lhs_src: &str, rhs_src: &str, @@ -332,7 +335,8 @@ pub fn print( println!( "{}", display_single_column( - display_path, + lhs_display_path, + rhs_display_path, lang_name, &rhs_colored_src, false, @@ -346,7 +350,8 @@ pub fn print( println!( "{}", display_single_column( - display_path, + lhs_display_path, + rhs_display_path, lang_name, &lhs_colored_src, true, @@ -385,7 +390,8 @@ pub fn print( println!( "{}", style::header( - display_path, + lhs_display_path, + rhs_display_path, i + 1, hunks.len(), lang_name, @@ -628,6 +634,7 @@ mod tests { fn test_display_single_column() { // Basic smoke test. let res = display_single_column( + "foo.py", "foo.py", "Python", "print(123)\n", @@ -719,7 +726,8 @@ mod tests { print( &hunks, &display_options, - "foo.el", + "foo-old.el", + "foo-new.el", "Emacs Lisp", "foo", "bar", diff --git a/src/style.rs b/src/style.rs index c38930b04..bf54cebaa 100644 --- a/src/style.rs +++ b/src/style.rs @@ -323,32 +323,46 @@ pub fn apply_colors( apply(s, &styles) } +fn apply_header_color(s: &str, use_color: bool, background: BackgroundColor) -> String { + if use_color { + if background.is_dark() { + s.bright_yellow().to_string() + } else { + s.yellow().to_string() + } + .bold() + .to_string() + } else { + s.to_string() + } +} + pub fn header( - file_name: &str, + lhs_display_path: &str, + rhs_display_path: &str, hunk_num: usize, hunk_total: usize, language_name: &str, use_color: bool, background: BackgroundColor, ) -> String { - let file_name_pretty = if use_color { - if background.is_dark() { - file_name.bright_yellow().to_string() - } else { - file_name.yellow().to_string() - } - .bold() - .to_string() - } else { - file_name.to_string() - }; let divider = if hunk_total == 1 { "".to_owned() } else { format!("{}/{} --- ", hunk_num, hunk_total) }; - format!("{} --- {}{}", file_name_pretty, divider, language_name) + let rhs_path_pretty = apply_header_color(rhs_display_path, use_color, background); + if hunk_num == 1 && lhs_display_path != rhs_display_path { + let lhs_path_pretty = apply_header_color(lhs_display_path, use_color, background); + let renamed = format!("Renamed {} to {}", lhs_path_pretty, rhs_path_pretty,); + format!( + "{}\n{} --- {}{}", + renamed, rhs_path_pretty, divider, language_name + ) + } else { + format!("{} --- {}{}", rhs_path_pretty, divider, language_name) + } } #[cfg(test)] diff --git a/src/summary.rs b/src/summary.rs index e7e7273cd..6a76ce5e8 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -8,7 +8,8 @@ pub enum FileContent { #[derive(Debug)] pub struct DiffResult { - pub path: String, + pub lhs_display_path: String, + pub rhs_display_path: String, pub language: Option, pub lhs_src: FileContent, pub rhs_src: FileContent,