Display rename information when before and after paths are different

pull/286/head
Wilfred Hughes 2022-05-08 11:50:55 +07:00
parent 2d8e1cf180
commit 1a6c5b8e7f
8 changed files with 156 additions and 101 deletions

@ -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

@ -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 -

@ -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,

@ -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(),

@ -173,10 +173,17 @@ pub enum Mode {
byte_limit: usize,
display_options: DisplayOptions,
missing_as_empty: bool,
display_path: String,
language_override: Option<guess_language::Language>,
/// 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(),
}
}

@ -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",

@ -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)]

@ -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<String>,
pub lhs_src: FileContent,
pub rhs_src: FileContent,