Repository: dandavison/delta
Pull request #479 (2020-12-29 11:13:42)

Hyperlink file and line number in hunk header
Ref https://github.com/dandavison/delta/pull/473#issuecomment-751773752 https://github.com/dandavison/delta/issues/309#issuecomment-703152533




Patch:
diff --git a/Cargo.lock b/Cargo.lock
index c499335..6dd42e4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -210,9 +210,9 @@ dependencies = [
 
 [[package]]
 name = "console"
-version = "0.13.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08"
+checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa"
 dependencies = [
  "encode_unicode",
  "lazy_static",
@@ -221,7 +221,6 @@ dependencies = [
  "terminal_size",
  "unicode-width",
  "winapi",
- "winapi-util",
 ]
 
 [[package]]
@@ -364,9 +363,9 @@ dependencies = [
 
 [[package]]
 name = "git2"
-version = "0.13.14"
+version = "0.13.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "186dd99cc77576e58344ad614fa9bb27bad9d048f85de3ca850c1f4e8b048260"
+checksum = "44f267c9da8a4de3c615b59e23606c75f164f84896e97f4dd6c15a4294de4359"
 dependencies = [
  "bitflags",
  "libc",
@@ -415,9 +414,9 @@ dependencies = [
 
 [[package]]
 name = "itertools"
-version = "0.9.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
 dependencies = [
  "either",
 ]
@@ -457,9 +456,9 @@ checksum = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb"
 
 [[package]]
 name = "libgit2-sys"
-version = "0.12.16+1.1.0"
+version = "0.12.17+1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f91b2f931ee975a98155195be8cd82d02e8e029d7d793d2bac1b8181ac97020"
+checksum = "f4ebdf65ca745126df8824688637aa0535a88900b83362d8ca63893bcf4e8841"
 dependencies = [
  "cc",
  "libc",
@@ -904,9 +903,9 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.1.13"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13"
+checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
 dependencies = [
  "libc",
  "winapi",
diff --git a/Cargo.toml b/Cargo.toml
index 630cf77..80910b3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,9 +21,9 @@ atty = "0.2.14"
 bitflags = "1.2.1"
 box_drawing = "0.1.2"
 bytelines = "2.2.2"
-console = "0.13.0"
+console = "0.14.0"
 dirs-next = "2.0.0"
-itertools = "0.9.0"
+itertools = "0.10.0"
 lazy_static = "1.4"
 regex = "1.4.2"
 shell-words = "1.0.0"
@@ -33,7 +33,7 @@ unicode-width = "0.1.8"
 vte = "0.9.0"
 
 [dependencies.git2]
-version = "0.13.14"
+version = "0.13.15"
 default-features = false
 features = []
 
diff --git a/src/delta.rs b/src/delta.rs
index f72302e..8187106 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -350,7 +350,7 @@ impl<'a> StateMachine<'a> {
         self.painter.set_highlighter();
         self.painter.emit()?;
 
-        let (raw_code_fragment, line_numbers) = parse::parse_hunk_header(&self.line);
+        let (code_fragment, line_numbers) = parse::parse_hunk_header(&self.line);
         if self.config.line_numbers {
             self.painter
                 .line_numbers_data
@@ -367,8 +367,14 @@ impl<'a> StateMachine<'a> {
         } else if self.config.hunk_header_style.is_omitted {
             writeln!(self.painter.writer)?;
         } else {
+            // Add a blank line below the hunk-header-line for readability, unless
+            // color_only mode is active.
+            if !self.config.color_only {
+                writeln!(self.painter.writer)?;
+            }
+
             hunk_header::write_hunk_header(
-                &raw_code_fragment,
+                &code_fragment,
                 &line_numbers,
                 &mut self.painter,
                 &self.line,
diff --git a/src/hunk_header.rs b/src/hunk_header.rs
index b44a633..7b7af31 100644
--- a/src/hunk_header.rs
+++ b/src/hunk_header.rs
@@ -1,4 +1,24 @@
-use std::borrow::Cow;
+//! A module for constructing and writing the hunk header.
+//!
+//! The structure of the hunk header output by delta is
+//! ```
+//! (file):(line-number): (code-fragment)
+//! ```
+//!
+//! The code fragment and line number derive from a line of git/diff output that looks like
+//! ```
+//! @@ -119,12 +119,7 @@ fn write_to_output_buffer(
+//! ```
+//!
+//! Whether or not file and line-number are included is controlled by the presence of the special
+//! style attributes 'file' and 'line-number' in the hunk-header-style string. For example, delta
+//! might output the above hunk header as
+//! ```
+//! ───────────────────────────────────────────────────┐
+//! src/hunk_header.rs:119: fn write_to_output_buffer( │
+//! ───────────────────────────────────────────────────┘
+//! ```
+
 use std::fmt::Write as FmtWrite;
 
 use crate::config::Config;
@@ -31,7 +51,7 @@ pub fn write_hunk_header_raw(
 }
 
 pub fn write_hunk_header(
-    raw_code_fragment: &str,
+    code_fragment: &str,
     line_numbers: &[(usize, usize)],
     painter: &mut Painter,
     line: &str,
@@ -42,42 +62,78 @@ pub fn write_hunk_header(
         draw::get_draw_function(config.hunk_header_style.decoration_style);
     let line = if config.color_only {
         format!(" {}", &line)
-    } else if !raw_code_fragment.is_empty() {
-        format!("{} ", raw_code_fragment)
+    } else if !code_fragment.is_empty() {
+        format!("{} ", code_fragment)
     } else {
         "".to_string()
     };
 
-    // Add a blank line below the hunk-header-line for readability, unless
-    // color_only mode is active.
-    if !config.color_only {
-        writeln!(painter.writer)?;
+    let file_with_line_number = get_painted_file_with_line_number(line_numbers, plus_file, config);
+
+    if !line.is_empty() || !file_with_line_number.is_empty() {
+        write_to_output_buffer(&file_with_line_number, line, painter, config);
+        draw_fn(
+            painter.writer,
+            &painter.output_buffer,
+            &painter.output_buffer,
+            &config.decorations_width,
+            config.null_style,
+            decoration_ansi_term_style,
+        )?;
+        painter.output_buffer.clear();
     }
 
-    let mut have_hunk_header = false;
+    Ok(())
+}
+
+fn get_painted_file_with_line_number(
+    line_numbers: &[(usize, usize)],
+    plus_file: &str,
+    config: &Config,
+) -> String {
+    let mut file_with_line_number = Vec::new();
+    let plus_line_number = line_numbers[line_numbers.len() - 1].0;
     if config.hunk_header_style_include_file_path {
-        let _ = write!(
-            &mut painter.output_buffer,
-            "{}",
-            config.file_style.paint(plus_file),
-        );
-        have_hunk_header = true;
+        file_with_line_number.push(config.file_style.paint(plus_file))
     };
     if !config.line_numbers
         && config.hunk_header_style_include_line_number
         && !config.hunk_header_style.is_raw
         && !config.color_only
     {
-        if have_hunk_header {
-            let _ = write!(&mut painter.output_buffer, ":");
+        if !file_with_line_number.is_empty() {
+            file_with_line_number.push(ansi_term::ANSIString::from(":"));
         }
-        _write_line_number(&line_numbers, painter, plus_file, config)?;
-        have_hunk_header = true;
+        if let Some(style) = config.hunk_header_style.decoration_ansi_term_style() {
+            file_with_line_number.push(style.paint(format!("{}", plus_line_number)))
+        } else {
+            file_with_line_number.push(ansi_term::ANSIString::from(format!("{}", plus_line_number)))
+        }
+    }
+    let file_with_line_number = ansi_term::ANSIStrings(&file_with_line_number).to_string();
+    if config.hyperlinks {
+        features::hyperlinks::format_osc8_file_hyperlink(
+            plus_file,
+            Some(plus_line_number),
+            &file_with_line_number,
+            config,
+        )
+        .into()
+    } else {
+        file_with_line_number
+    }
+}
+
+fn write_to_output_buffer(
+    file_with_line_number: &str,
+    line: String,
+    painter: &mut Painter,
+    config: &Config,
+) {
+    if !file_with_line_number.is_empty() {
+        let _ = write!(&mut painter.output_buffer, "{}: ", file_with_line_number);
     }
     if !line.is_empty() {
-        if have_hunk_header {
-            let _ = write!(&mut painter.output_buffer, ": ");
-        }
         let lines = vec![(line, delta::State::HunkHeader)];
         let syntax_style_sections = Painter::get_syntax_style_sections_for_lines(
             &lines,
@@ -97,53 +153,5 @@ pub fn write_hunk_header(
             Some(false),
         );
         painter.output_buffer.pop(); // trim newline
-        have_hunk_header = true;
-    } else if have_hunk_header {
-        let _ = write!(&mut painter.output_buffer, " ");
     }
-    if have_hunk_header {
-        draw_fn(
-            painter.writer,
-            &painter.output_buffer,
-            &painter.output_buffer,
-            &config.decorations_width,
-            config.null_style,
-            decoration_ansi_term_style,
-        )?;
-        painter.output_buffer.clear();
-    }
-
-    Ok(())
-}
-
-fn _write_line_number(
-    line_numbers: &[(usize, usize)],
-    painter: &mut Painter,
-    plus_file: &str,
-    config: &Config,
-) -> std::io::Result<()> {
-    let plus_line_number = line_numbers[line_numbers.len() - 1].0;
-    let formatted_plus_line_number = if config.hyperlinks {
-        features::hyperlinks::format_osc8_file_hyperlink(
-            plus_file,
-            Some(plus_line_number),
-            &format!("{}", plus_line_number),
-            config,
-        )
-    } else {
-        Cow::from(format!("{}", plus_line_number))
-    };
-    match config.hunk_header_style.decoration_ansi_term_style() {
-        Some(style) => {
-            let _ = write!(
-                &mut painter.output_buffer,
-                "{}",
-                style.paint(formatted_plus_line_number)
-            );
-        }
-        None => {
-            let _ = write!(&mut painter.output_buffer, "{}", formatted_plus_line_number);
-        }
-    }
-    Ok(())
 }


Test patch:
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
index 402a88a..b3a8bf3 100644
--- a/src/tests/test_example_diffs.rs
+++ b/src/tests/test_example_diffs.rs
@@ -1043,13 +1043,13 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
         ]);
         let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK_NO_FRAG, &config);
 
-        ansi_test_utils::assert_line_has_style(&output, 5, "src/delta.rs:1 ", "yellow", &config);
+        ansi_test_utils::assert_line_has_style(&output, 5, "src/delta.rs:1: ", "yellow", &config);
         let output = strip_ansi_codes(&output);
         assert!(output.contains(
             "
-───────────────┐
-src/delta.rs:1 │
-───────────────┘
+────────────────┐
+src/delta.rs:1: │
+────────────────┘
 "
         ));
     }