From 6630202259cf984a8b20d7cb76086986a107390c Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Tue, 20 Apr 2021 19:12:54 -0400 Subject: Output paths relative to current working directory Fixes #552 --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/config.rs | 2 ++ src/delta.rs | 30 ++++++++++++++--- src/parse.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 126 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96844e15..0db780e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,7 @@ dependencies = [ "git2", "itertools", "lazy_static", + "pathdiff", "regex", "shell-words", "structopt", @@ -496,6 +497,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "pathdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34" + [[package]] name = "percent-encoding" version = "2.1.0" diff --git a/Cargo.toml b/Cargo.toml index b4e003e0..55ec3b0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ console = "0.14.1" dirs-next = "2.0.0" itertools = "0.10.0" lazy_static = "1.4" +pathdiff = "0.2.0" regex = "1.4.6" shell-words = "1.0.0" structopt = "0.3.21" diff --git a/src/config.rs b/src/config.rs index f822efc2..c6719839 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,7 @@ pub struct Config { pub background_color_extends_to_terminal_width: bool, pub commit_style: Style, pub color_only: bool, + pub cwd_relative_to_repo_root: Option, pub decorations_width: cli::Width, pub error_exit_code: i32, pub file_added_label: String, @@ -182,6 +183,7 @@ impl From for Config { .background_color_extends_to_terminal_width, commit_style, color_only: opt.color_only, + cwd_relative_to_repo_root: std::env::var("GIT_PREFIX").ok(), decorations_width: opt.computed.decorations_width, error_exit_code: 2, // Use 2 for error because diff uses 0 and 1 for non-error. file_added_label, diff --git a/src/delta.rs b/src/delta.rs index 7d52eae1..869bd4a4 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -113,6 +113,8 @@ impl<'a> StateMachine<'a> { let mut handled_line = if line.starts_with("commit ") { self.handle_commit_meta_header_line()? + } else if self.state == State::CommitMeta && line.starts_with(" ") { + self.handle_diff_stat_line()? } else if line.starts_with("diff ") { self.handle_file_meta_diff_line()? } else if (self.state == State::FileMeta || self.source == Source::DiffUnified) @@ -226,6 +228,20 @@ impl<'a> StateMachine<'a> { Ok(()) } + fn handle_diff_stat_line(&mut self) -> std::io::Result { + let mut handled_line = false; + if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() { + if let Some(replacement_line) = + parse::relativize_path_in_diff_stat_line(&self.raw_line, cwd) + { + self.painter.emit()?; + writeln!(self.painter.writer, "{}", replacement_line)?; + handled_line = true + } + } + Ok(handled_line) + } + #[allow(clippy::unnecessary_wraps)] fn handle_file_meta_diff_line(&mut self) -> std::io::Result { self.painter.paint_buffered_minus_and_plus_lines(); @@ -237,8 +253,11 @@ impl<'a> StateMachine<'a> { fn handle_file_meta_minus_line(&mut self) -> std::io::Result { let mut handled_line = false; - let parsed_file_meta_line = - parse::parse_file_meta_line(&self.line, self.source == Source::GitDiff); + let parsed_file_meta_line = parse::parse_file_meta_line( + &self.line, + self.source == Source::GitDiff, + self.config.cwd_relative_to_repo_root.as_deref(), + ); self.minus_file = parsed_file_meta_line.0; self.file_event = parsed_file_meta_line.1; @@ -271,8 +290,11 @@ impl<'a> StateMachine<'a> { fn handle_file_meta_plus_line(&mut self) -> std::io::Result { let mut handled_line = false; - let parsed_file_meta_line = - parse::parse_file_meta_line(&self.line, self.source == Source::GitDiff); + let parsed_file_meta_line = parse::parse_file_meta_line( + &self.line, + self.source == Source::GitDiff, + self.config.cwd_relative_to_repo_root.as_deref(), + ); self.plus_file = parsed_file_meta_line.0; self.painter .set_syntax(parse::get_file_extension_from_file_meta_line_file_path( diff --git a/src/parse.rs b/src/parse.rs index 4c2cf233..65ad72f1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -28,8 +28,12 @@ pub enum FileEvent { NoEvent, } -pub fn parse_file_meta_line(line: &str, git_diff_name: bool) -> (String, FileEvent) { - match line { +pub fn parse_file_meta_line( + line: &str, + git_diff_name: bool, + cwd_relative_to_repo_root: Option<&str>, +) -> (String, FileEvent) { + let (mut path, file_event) = match line { line if line.starts_with("--- ") || line.starts_with("+++ ") => { let offset = 4; let file = match &line[offset..] { @@ -56,7 +60,44 @@ pub fn parse_file_meta_line(line: &str, git_diff_name: bool) -> (String, FileEve (line[8..].to_string(), FileEvent::Copy) // "copy to ".len() } _ => ("".to_string(), FileEvent::NoEvent), + }; + + if let Some(cwd) = cwd_relative_to_repo_root { + if let Some(relative_path) = pathdiff::diff_paths(&path, cwd) { + if let Some(relative_path) = relative_path.to_str() { + path = relative_path.to_owned(); + } + } + } + + (path, file_event) +} + +// A regex to capture the path, and the content from the pipe onwards, in lines +// like these: +// " src/delta.rs | 14 ++++++++++----" +// " src/config.rs | 2 ++" +lazy_static! { + static ref DIFF_STAT_LINE_REGEX: Regex = + Regex::new(r" ([^\| ][^\|]+[^\| ]) +(\| +[0-9]+ .+)").unwrap(); +} + +pub fn relativize_path_in_diff_stat_line( + line: &str, + cwd_relative_to_repo_root: &str, +) -> Option { + if let Some(caps) = DIFF_STAT_LINE_REGEX.captures(line) { + let path_relative_to_repo_root = caps.get(1).unwrap().as_str(); + if let Some(relative_path) = + pathdiff::diff_paths(path_relative_to_repo_root, cwd_relative_to_repo_root) + { + if let Some(relative_path) = relative_path.to_str() { + let suffix = caps.get(2).unwrap().as_str(); + return Some(format!(" {:<30}{}", relative_path, suffix,)); + } + } } + return None; } pub fn get_file_extension_from_file_meta_line_file_path(path: &str) -> Option<&str> { @@ -245,21 +286,21 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line() { assert_eq!( - parse_file_meta_line("--- /dev/null", true), + parse_file_meta_line("--- /dev/null", true, None), ("/dev/null".to_string(), FileEvent::Change) ); for prefix in &DIFF_PREFIXES { assert_eq!( - parse_file_meta_line(&format!("--- {}src/delta.rs", prefix), true), + parse_file_meta_line(&format!("--- {}src/delta.rs", prefix), true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } assert_eq!( - parse_file_meta_line("--- src/delta.rs", true), + parse_file_meta_line("--- src/delta.rs", true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ src/delta.rs", true), + parse_file_meta_line("+++ src/delta.rs", true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } @@ -267,23 +308,23 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_containing_spaces() { assert_eq!( - parse_file_meta_line("+++ a/my src/delta.rs", true), + parse_file_meta_line("+++ a/my src/delta.rs", true, None), ("my src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ my src/delta.rs", true), + parse_file_meta_line("+++ my src/delta.rs", true, None), ("my src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ a/src/my delta.rs", true), + parse_file_meta_line("+++ a/src/my delta.rs", true, None), ("src/my delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ a/my src/my delta.rs", true), + parse_file_meta_line("+++ a/my src/my delta.rs", true, None), ("my src/my delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ b/my src/my enough/my delta.rs", true), + parse_file_meta_line("+++ b/my src/my enough/my delta.rs", true, None), ( "my src/my enough/my delta.rs".to_string(), FileEvent::Change @@ -294,7 +335,7 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_rename() { assert_eq!( - parse_file_meta_line("rename from nospace/file2.el", true), + parse_file_meta_line("rename from nospace/file2.el", true, None), ("nospace/file2.el".to_string(), FileEvent::Rename) ); } @@ -302,7 +343,7 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_rename_containing_spaces() { assert_eq!( - parse_file_meta_line("rename from with space/file1.el", true), + parse_file_meta_line("rename from with space/file1.el", true, None), ("with space/file1.el".to_string(), FileEvent::Rename) ); } @@ -310,11 +351,11 @@ mod tests { #[test] fn test_parse_file_meta_line() { assert_eq!( - parse_file_meta_line("--- src/delta.rs", false), + parse_file_meta_line("--- src/delta.rs", false, None), ("src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ src/delta.rs", false), + parse_file_meta_line("+++ src/delta.rs", false, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } @@ -369,4 +410,38 @@ mod tests { assert_eq!(line_numbers_and_hunk_lengths[1], (358, 15),); assert_eq!(line_numbers_and_hunk_lengths[2], (358, 16),); } + + #[test] + fn test_relative_path() { + for (path, cwd_relative_to_repo_root, expected) in &[ + ("file.rs", "", "file.rs"), + ("file.rs", "a/", "../file.rs"), + ("a/file.rs", "a/", "file.rs"), + ("a/b/file.rs", "a", "b/file.rs"), + ("c/d/file.rs", "a/b/", "../../c/d/file.rs"), + ] { + assert_eq!( + pathdiff::diff_paths(path, cwd_relative_to_repo_root), + Some(expected.into()) + ) + } + } + + #[test] + fn test_diff_stat_line_regex_1() { + let caps = DIFF_STAT_LINE_REGEX.captures(" src/delta.rs | 14 ++++++++++----"); + assert!(caps.is_some()); + let caps = caps.unwrap(); + assert_eq!(caps.get(1).unwrap().as_str(), "src/delta.rs"); + assert_eq!(caps.get(2).unwrap().as_str(), "| 14 ++++++++++----"); + } + + #[test] + fn test_diff_stat_line_regex_2() { + let caps = DIFF_STAT_LINE_REGEX.captures(" src/config.rs | 2 ++"); + assert!(caps.is_some()); + let caps = caps.unwrap(); + assert_eq!(caps.get(1).unwrap().as_str(), "src/config.rs"); + assert_eq!(caps.get(2).unwrap().as_str(), "| 2 ++"); + } } -- cgit v1.2.3 From 717a63ac9051694efeeccd1c619ac984f1259142 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Tue, 20 Apr 2021 22:06:37 -0400 Subject: Add --diff-stat-align-width to control alignment of relativized paths --- src/cli.rs | 5 +++++ src/config.rs | 2 ++ src/delta.rs | 8 +++++--- src/options/set.rs | 1 + src/parse.rs | 7 ++++++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ca465454..4109269a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -482,6 +482,11 @@ pub struct Opt { #[structopt(short = "w", long = "width")] pub width: Option, + /// Width allocated for file paths in a diff stat section. If a relativized + /// file path exceeds this width then the diff stat will be misaligned. + #[structopt(long = "diff-stat-align-width", default_value = "48")] + pub diff_stat_align_width: usize, + /// The number of spaces to replace tab characters with. Use --tabs=0 to pass tab characters /// through directly, but note that in that case delta will calculate line widths assuming tabs /// occupy one character's width on the screen: if your terminal renders tabs as more than than diff --git a/src/config.rs b/src/config.rs index c6719839..6d949dff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,7 @@ pub struct Config { pub color_only: bool, pub cwd_relative_to_repo_root: Option, pub decorations_width: cli::Width, + pub diff_stat_align_width: usize, pub error_exit_code: i32, pub file_added_label: String, pub file_copied_label: String, @@ -185,6 +186,7 @@ impl From for Config { color_only: opt.color_only, cwd_relative_to_repo_root: std::env::var("GIT_PREFIX").ok(), decorations_width: opt.computed.decorations_width, + diff_stat_align_width: opt.diff_stat_align_width, error_exit_code: 2, // Use 2 for error because diff uses 0 and 1 for non-error. file_added_label, file_copied_label, diff --git a/src/delta.rs b/src/delta.rs index 869bd4a4..608b89c9 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -231,9 +231,11 @@ impl<'a> StateMachine<'a> { fn handle_diff_stat_line(&mut self) -> std::io::Result { let mut handled_line = false; if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() { - if let Some(replacement_line) = - parse::relativize_path_in_diff_stat_line(&self.raw_line, cwd) - { + if let Some(replacement_line) = parse::relativize_path_in_diff_stat_line( + &self.raw_line, + cwd, + self.config.diff_stat_align_width, + ) { self.painter.emit()?; writeln!(self.painter.writer, "{}", replacement_line)?; handled_line = true diff --git a/src/options/set.rs b/src/options/set.rs index 2d112f87..8373e86b 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -128,6 +128,7 @@ pub fn set_options( color_only, commit_decoration_style, commit_style, + diff_stat_align_width, file_added_label, file_copied_label, file_decoration_style, diff --git a/src/parse.rs b/src/parse.rs index 65ad72f1..a90323c3 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -85,6 +85,7 @@ lazy_static! { pub fn relativize_path_in_diff_stat_line( line: &str, cwd_relative_to_repo_root: &str, + diff_stat_align_width: usize, ) -> Option { if let Some(caps) = DIFF_STAT_LINE_REGEX.captures(line) { let path_relative_to_repo_root = caps.get(1).unwrap().as_str(); @@ -93,7 +94,11 @@ pub fn relativize_path_in_diff_stat_line( { if let Some(relative_path) = relative_path.to_str() { let suffix = caps.get(2).unwrap().as_str(); - return Some(format!(" {:<30}{}", relative_path, suffix,)); + let pad_width = diff_stat_align_width + .checked_sub(relative_path.len()) + .unwrap_or(0); + let padding = " ".repeat(pad_width); + return Some(format!(" {}{}{}", relative_path, padding, suffix)); } } } -- cgit v1.2.3 From 2cc9b5d296f902704ab19eed308b3c145f9a89e6 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Wed, 21 Apr 2021 11:55:13 -0400 Subject: Clippy --- src/delta.rs | 2 +- src/parse.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/delta.rs b/src/delta.rs index 608b89c9..ac820de7 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -113,7 +113,7 @@ impl<'a> StateMachine<'a> { let mut handled_line = if line.starts_with("commit ") { self.handle_commit_meta_header_line()? - } else if self.state == State::CommitMeta && line.starts_with(" ") { + } else if self.state == State::CommitMeta && line.starts_with(' ') { self.handle_diff_stat_line()? } else if line.starts_with("diff ") { self.handle_file_meta_diff_line()? diff --git a/src/parse.rs b/src/parse.rs index a90323c3..167ae272 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -94,15 +94,13 @@ pub fn relativize_path_in_diff_stat_line( { if let Some(relative_path) = relative_path.to_str() { let suffix = caps.get(2).unwrap().as_str(); - let pad_width = diff_stat_align_width - .checked_sub(relative_path.len()) - .unwrap_or(0); + let pad_width = diff_stat_align_width.saturating_sub(relative_path.len()); let padding = " ".repeat(pad_width); return Some(format!(" {}{}{}", relative_path, padding, suffix)); } } } - return None; + None } pub fn get_file_extension_from_file_meta_line_file_path(path: &str) -> Option<&str> { -- cgit v1.2.3 From dc8a6fc3d48c16f82402d46cd83106cf94ed83fe Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Sat, 24 Apr 2021 08:43:29 -0400 Subject: Add relative-paths option --- src/cli.rs | 5 +++++ src/config.rs | 2 ++ src/delta.rs | 32 +++++++++++++++++++++----------- src/options/set.rs | 1 + src/parse.rs | 6 +++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 4109269a..d097a878 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -231,6 +231,11 @@ pub struct Opt { /// --file-renamed-label. pub navigate: bool, + #[structopt(long = "relative-paths")] + /// Output all file paths relative to the current directory so that they + /// resolve correctly when clicked on or used in shell commands. + pub relative_paths: bool, + #[structopt(long = "hyperlinks")] /// Render commit hashes, file names, and line numbers as hyperlinks, according to the /// hyperlink spec for terminal emulators: diff --git a/src/config.rs b/src/config.rs index 6d949dff..fbad2e41 100644 --- a/src/config.rs +++ b/src/config.rs @@ -72,6 +72,7 @@ pub struct Config { pub plus_style: Style, pub git_minus_style: Style, pub git_plus_style: Style, + pub relative_paths: bool, pub show_themes: bool, pub side_by_side: bool, pub side_by_side_data: side_by_side::SideBySideData, @@ -239,6 +240,7 @@ impl From for Config { plus_style, git_minus_style, git_plus_style, + relative_paths: opt.relative_paths, show_themes: opt.show_themes, side_by_side: opt.side_by_side, side_by_side_data, diff --git a/src/delta.rs b/src/delta.rs index ac820de7..26dbc687 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -230,15 +230,17 @@ impl<'a> StateMachine<'a> { fn handle_diff_stat_line(&mut self) -> std::io::Result { let mut handled_line = false; - if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() { - if let Some(replacement_line) = parse::relativize_path_in_diff_stat_line( - &self.raw_line, - cwd, - self.config.diff_stat_align_width, - ) { - self.painter.emit()?; - writeln!(self.painter.writer, "{}", replacement_line)?; - handled_line = true + if self.config.relative_paths { + if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() { + if let Some(replacement_line) = parse::relativize_path_in_diff_stat_line( + &self.raw_line, + cwd, + self.config.diff_stat_align_width, + ) { + self.painter.emit()?; + writeln!(self.painter.writer, "{}", replacement_line)?; + handled_line = true + } } } Ok(handled_line) @@ -258,7 +260,11 @@ impl<'a> StateMachine<'a> { let parsed_file_meta_line = parse::parse_file_meta_line( &self.line, self.source == Source::GitDiff, - self.config.cwd_relative_to_repo_root.as_deref(), + if self.config.relative_paths { + self.config.cwd_relative_to_repo_root.as_deref() + } else { + None + }, ); self.minus_file = parsed_file_meta_line.0; self.file_event = parsed_file_meta_line.1; @@ -295,7 +301,11 @@ impl<'a> StateMachine<'a> { let parsed_file_meta_line = parse::parse_file_meta_line( &self.line, self.source == Source::GitDiff, - self.config.cwd_relative_to_repo_root.as_deref(), + if self.config.relative_paths { + self.config.cwd_relative_to_repo_root.as_deref() + } else { + None + }, ); self.plus_file = parsed_file_meta_line.0; self.painter diff --git a/src/options/set.rs b/src/options/set.rs index 8373e86b..6a1a7ad0 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -171,6 +171,7 @@ pub fn set_options( plus_empty_line_marker_style, plus_non_emph_style, raw, + relative_paths, show_themes, side_by_side, tab_width, diff --git a/src/parse.rs b/src/parse.rs index 167ae272..d811fb8a 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -31,7 +31,7 @@ pub enum FileEvent { pub fn parse_file_meta_line( line: &str, git_diff_name: bool, - cwd_relative_to_repo_root: Option<&str>, + relative_path_base: Option<&str>, ) -> (String, FileEvent) { let (mut path, file_event) = match line { line if line.starts_with("--- ") || line.starts_with("+++ ") => { @@ -62,8 +62,8 @@ pub fn parse_file_meta_line( _ => ("".to_string(), FileEvent::NoEvent), }; - if let Some(cwd) = cwd_relative_to_repo_root { - if let Some(relative_path) = pathdiff::diff_paths(&path, cwd) { + if let Some(base) = relative_path_base { + if let Some(relative_path) = pathdiff::diff_paths(&path, base) { if let Some(relative_path) = relative_path.to_str() { path = relative_path.to_owned(); } -- cgit v1.2.3