diff options
Diffstat (limited to 'src/handlers/hunk_header.rs')
-rw-r--r-- | src/handlers/hunk_header.rs | 597 |
1 files changed, 0 insertions, 597 deletions
diff --git a/src/handlers/hunk_header.rs b/src/handlers/hunk_header.rs deleted file mode 100644 index f06dcc52..00000000 --- a/src/handlers/hunk_header.rs +++ /dev/null @@ -1,597 +0,0 @@ -// 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 lazy_static::lazy_static; -use regex::Regex; - -use super::draw; -use crate::config::{Config, HunkHeaderIncludeFilePath, HunkHeaderIncludeLineNumber}; -use crate::delta::{self, DiffType, InMergeConflict, MergeParents, State, StateMachine}; -use crate::paint::{self, BgShouldFill, Painter, StyleSectionSpecifier}; -use crate::style::{DecorationStyle, Style}; - -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct ParsedHunkHeader { - code_fragment: String, - line_numbers_and_hunk_lengths: Vec<(usize, usize)>, -} - -pub enum HunkHeaderIncludeHunkLabel { - Yes, - No, -} - -impl<'a> StateMachine<'a> { - #[inline] - fn test_hunk_header_line(&self) -> bool { - self.line.starts_with("@@") && - // A hunk header can occur within a merge conflict region, but we don't attempt to handle - // that. See #822. - !matches!(self.state, State::MergeConflict(_, _)) - } - - pub fn handle_hunk_header_line(&mut self) -> std::io::Result<bool> { - use DiffType::*; - use State::*; - if !self.test_hunk_header_line() { - return Ok(false); - } - let mut handled_line = false; - if let Some(parsed_hunk_header) = parse_hunk_header(&self.line) { - let diff_type = match &self.state { - DiffHeader(Combined(MergeParents::Unknown, InMergeConflict::No)) => { - // https://git-scm.com/docs/git-diff#_combined_diff_format - let n_parents = self.line.chars().take_while(|c| c == &'@').count() - 1; - Combined(MergeParents::Number(n_parents), InMergeConflict::No) - } - DiffHeader(diff_type) - | HunkMinus(diff_type, _) - | HunkZero(diff_type, _) - | HunkPlus(diff_type, _) => diff_type.clone(), - _ => Unified, - }; - self.state = HunkHeader( - diff_type, - parsed_hunk_header, - self.line.clone(), - self.raw_line.clone(), - ); - handled_line = true; - } - Ok(handled_line) - } - - /// Emit the hunk header, with any requested decoration. - pub fn emit_hunk_header_line( - &mut self, - parsed_hunk_header: &ParsedHunkHeader, - line: &str, - raw_line: &str, - ) -> std::io::Result<bool> { - self.painter.paint_buffered_minus_and_plus_lines(); - self.painter.set_highlighter(); - self.painter.emit()?; - - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parsed_hunk_header; - - if self.config.line_numbers { - self.painter - .line_numbers_data - .as_mut() - .unwrap() - .initialize_hunk(line_numbers_and_hunk_lengths, self.plus_file.to_string()); - } - - if self.config.hunk_header_style.is_raw { - write_hunk_header_raw(&mut self.painter, line, raw_line, self.config)?; - } 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)?; - } - - write_line_of_code_with_optional_path_and_line_number( - code_fragment, - line_numbers_and_hunk_lengths, - None, - &mut self.painter, - line, - if self.plus_file == "/dev/null" { - &self.minus_file - } else { - &self.plus_file - }, - self.config.hunk_header_style.decoration_style, - &self.config.hunk_header_file_style, - &self.config.hunk_header_line_number_style, - &self.config.hunk_header_style_include_file_path, - &self.config.hunk_header_style_include_line_number, - &HunkHeaderIncludeHunkLabel::Yes, - ":", - self.config, - )?; - }; - self.painter.set_highlighter(); - Ok(true) - } -} - -lazy_static! { - static ref HUNK_HEADER_REGEX: Regex = Regex::new(r"@+ ([^@]+)@+(.*\s?)").unwrap(); -} - -// Parse unified diff hunk header format. See -// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html -// https://www.artima.com/weblogs/viewpost.jsp?thread=164293 -lazy_static! { - static ref HUNK_HEADER_FILE_COORDINATE_REGEX: Regex = Regex::new( - r"(?x) -[-+] -(\d+) # 1. Hunk start line number -(?: # Start optional hunk length section (non-capturing) - , # Literal comma - (\d+) # 2. Optional hunk length (defaults to 1) -)?" - ) - .unwrap(); -} - -/// Given input like -/// "@@ -74,15 +74,14 @@ pub fn delta(" -/// Return " pub fn delta(" and a vector of (line_number, hunk_length) tuples. -fn parse_hunk_header(line: &str) -> Option<ParsedHunkHeader> { - if let Some(caps) = HUNK_HEADER_REGEX.captures(line) { - let file_coordinates = &caps[1]; - let line_numbers_and_hunk_lengths = HUNK_HEADER_FILE_COORDINATE_REGEX - .captures_iter(file_coordinates) - .map(|caps| { - ( - caps[1].parse::<usize>().unwrap(), - caps.get(2) - .map(|m| m.as_str()) - // Per the specs linked above, if the hunk length is absent then it is 1. - .unwrap_or("1") - .parse::<usize>() - .unwrap(), - ) - }) - .collect(); - let code_fragment = caps[2].to_string(); - Some(ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - }) - } else { - None - } -} - -fn write_hunk_header_raw( - painter: &mut Painter, - line: &str, - raw_line: &str, - config: &Config, -) -> std::io::Result<()> { - let (mut draw_fn, pad, decoration_ansi_term_style) = - draw::get_draw_function(config.hunk_header_style.decoration_style); - if config.hunk_header_style.decoration_style != DecorationStyle::NoDecoration { - writeln!(painter.writer)?; - } - draw_fn( - painter.writer, - &format!("{}{}", line, if pad { " " } else { "" }), - &format!("{}{}", raw_line, if pad { " " } else { "" }), - "", - &config.decorations_width, - config.hunk_header_style, - decoration_ansi_term_style, - )?; - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -pub fn write_line_of_code_with_optional_path_and_line_number( - code_fragment: &str, - line_numbers_and_hunk_lengths: &[(usize, usize)], - style_sections: Option<StyleSectionSpecifier>, - painter: &mut Painter, - line: &str, - plus_file: &str, - decoration_style: DecorationStyle, - file_style: &Style, - line_number_style: &Style, - include_file_path: &HunkHeaderIncludeFilePath, - include_line_number: &HunkHeaderIncludeLineNumber, - include_hunk_label: &HunkHeaderIncludeHunkLabel, - file_path_separator: &str, - config: &Config, -) -> std::io::Result<()> { - let (mut draw_fn, _, decoration_ansi_term_style) = draw::get_draw_function(decoration_style); - let line = if config.color_only { - line.to_string() - } else if !code_fragment.is_empty() { - format!("{code_fragment} ") - } else { - "".to_string() - }; - - let plus_line_number = line_numbers_and_hunk_lengths[line_numbers_and_hunk_lengths.len() - 1].0; - let file_with_line_number = paint_file_path_with_line_number( - Some(plus_line_number), - plus_file, - file_style, - line_number_style, - include_file_path, - include_line_number, - file_path_separator, - config, - ); - - if !line.is_empty() || !file_with_line_number.is_empty() { - write_to_output_buffer( - &file_with_line_number, - file_path_separator, - line, - style_sections, - include_hunk_label, - 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(); - } - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn paint_file_path_with_line_number( - line_number: Option<usize>, - plus_file: &str, - file_style: &Style, - line_number_style: &Style, - include_file_path: &HunkHeaderIncludeFilePath, - include_line_number: &HunkHeaderIncludeLineNumber, - separator: &str, - config: &Config, -) -> String { - let file_style = match include_file_path { - HunkHeaderIncludeFilePath::Yes => Some(*file_style), - HunkHeaderIncludeFilePath::No => None, - }; - let line_number_style = if matches!(include_line_number, HunkHeaderIncludeLineNumber::Yes) - && line_number.is_some() - && !config.hunk_header_style.is_raw - && !config.color_only - { - Some(*line_number_style) - } else { - None - }; - - paint::paint_file_path_with_line_number( - line_number, - plus_file, - false, - separator, - false, - file_style, - line_number_style, - config, - ) -} - -fn write_to_output_buffer( - file_with_line_number: &str, - file_path_separator: &str, - line: String, - style_sections: Option<StyleSectionSpecifier>, - include_hunk_label: &HunkHeaderIncludeHunkLabel, - painter: &mut Painter, - config: &Config, -) { - if matches!(include_hunk_label, HunkHeaderIncludeHunkLabel::Yes) - && !config.hunk_label.is_empty() - { - let _ = write!( - &mut painter.output_buffer, - "{} ", - config.hunk_header_file_style.paint(&config.hunk_label) - ); - } - if !file_with_line_number.is_empty() { - // The code fragment in "line" adds whitespace, but if only a line number is printed - // then the trailing space must be added. - let space = if line.is_empty() { " " } else { "" }; - let _ = write!( - &mut painter.output_buffer, - "{file_with_line_number}{file_path_separator}{space}", - ); - } - if !line.is_empty() { - painter.syntax_highlight_and_paint_line( - &line, - style_sections.unwrap_or(StyleSectionSpecifier::Style(config.hunk_header_style)), - delta::State::HunkHeader( - DiffType::Unified, - ParsedHunkHeader::default(), - "".to_owned(), - "".to_owned(), - ), - BgShouldFill::No, - ); - painter.output_buffer.pop(); // trim newline - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::ansi::strip_ansi_codes; - use crate::tests::integration_test_utils; - - #[test] - fn test_parse_hunk_header() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@ -74,15 +75,14 @@ pub fn delta(\n").unwrap(); - assert_eq!(code_fragment, " pub fn delta(\n"); - assert_eq!(line_numbers_and_hunk_lengths[0], (74, 15),); - assert_eq!(line_numbers_and_hunk_lengths[1], (75, 14),); - } - - #[test] - fn test_parse_hunk_header_with_omitted_hunk_lengths() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@ -74 +75,2 @@ pub fn delta(\n").unwrap(); - assert_eq!(code_fragment, " pub fn delta(\n"); - assert_eq!(line_numbers_and_hunk_lengths[0], (74, 1),); - assert_eq!(line_numbers_and_hunk_lengths[1], (75, 2),); - } - - #[test] - fn test_parse_hunk_header_added_file() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@ -1,22 +0,0 @@").unwrap(); - assert_eq!(code_fragment, "",); - assert_eq!(line_numbers_and_hunk_lengths[0], (1, 22),); - assert_eq!(line_numbers_and_hunk_lengths[1], (0, 0),); - } - - #[test] - fn test_parse_hunk_header_deleted_file() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@ -0,0 +1,3 @@").unwrap(); - assert_eq!(code_fragment, "",); - assert_eq!(line_numbers_and_hunk_lengths[0], (0, 0),); - assert_eq!(line_numbers_and_hunk_lengths[1], (1, 3),); - } - - #[test] - fn test_parse_hunk_header_merge() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@@ -293,11 -358,15 +358,16 @@@ dependencies =").unwrap(); - assert_eq!(code_fragment, " dependencies ="); - assert_eq!(line_numbers_and_hunk_lengths[0], (293, 11),); - assert_eq!(line_numbers_and_hunk_lengths[1], (358, 15),); - assert_eq!(line_numbers_and_hunk_lengths[2], (358, 16),); - } - - #[test] - fn test_parse_hunk_header_cthulhu() { - let ParsedHunkHeader { - code_fragment, - line_numbers_and_hunk_lengths, - } = parse_hunk_header("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -444,17 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 -446,6 +444,17 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ int snd_soc_jack_add_gpios(struct snd_s").unwrap(); - assert_eq!(code_fragment, " int snd_soc_jack_add_gpios(struct snd_s"); - assert_eq!(line_numbers_and_hunk_lengths[0], (446, 6),); - assert_eq!(line_numbers_and_hunk_lengths[1], (446, 6),); - assert_eq!(line_numbers_and_hunk_lengths[2], (446, 6),); - assert_eq!(line_numbers_and_hunk_lengths[65], (446, 6),); - } - - #[test] - fn test_paint_file_path_with_line_number_default() { - // hunk-header-style (by default) includes 'line-number' but not 'file'. - // This test confirms that `paint_file_path_with_line_number` returns a painted line number. - let config = integration_test_utils::make_config_from_args(&[]); - - let result = paint_file_path_with_line_number( - Some(3), - "some-file", - &config.hunk_header_style, - &config.hunk_header_line_number_style, - &config.hunk_header_style_include_file_path, - &config.hunk_header_style_include_line_number, - ":", - &config, - ); - - assert_eq!(result, "\u{1b}[34m3\u{1b}[0m"); - } - - #[test] - fn test_paint_file_path_with_line_number_hyperlinks() { - use std::{iter::FromIterator, path::PathBuf}; - - use crate::utils; - - // hunk-header-style (by default) includes 'line-number' but not 'file'. - // Normally, `paint_file_path_with_line_number` would return a painted line number. - // But in this test hyperlinks are activated, and the test ensures that delta.__workdir__ is - // present in git_config_entries. - // This test confirms that, under those circumstances, `paint_file_path_with_line_number` - // returns a hyperlinked file path with line number. - - let config = integration_test_utils::make_config_from_args(&["--features", "hyperlinks"]); - let relative_path = PathBuf::from_iter(["some-dir", "some-file"]); - - let result = paint_file_path_with_line_number( - Some(3), - &relative_path.to_string_lossy(), - &config.hunk_header_style, - &config.hunk_header_line_number_style, - &config.hunk_header_style_include_file_path, - &config.hunk_header_style_include_line_number, - ":", - &config, - ); - - assert_eq!( - result, - format!( - "\u{1b}]8;;file://{}\u{1b}\\\u{1b}[34m3\u{1b}[0m\u{1b}]8;;\u{1b}\\", - utils::path::fake_delta_cwd_for_tests() - .join(relative_path) - .to_string_lossy() - ) - ); - } - - #[test] - fn test_paint_file_path_with_line_number_empty() { - // hunk-header-style includes neither 'file' nor 'line-number'. - // This causes `paint_file_path_with_line_number` to return empty string. - let config = integration_test_utils::make_config_from_args(&[ - "--hunk-header-style", - "syntax bold", - "--hunk-header-decoration-style", - "omit", - ]); - - let result = paint_file_path_with_line_number( - Some(3), - "some-file", - &config.hunk_header_style, - &config.hunk_header_line_number_style, - &config.hunk_header_style_include_file_path, - &config.hunk_header_style_include_line_number, - ":", - &config, - ); - - // result is - // "\u{1b}[1msome-file\u{1b}[0m:\u{1b}[34m3\u{1b}[0m" - assert_eq!(result, ""); - } - - #[test] - fn test_paint_file_path_with_line_number_empty_hyperlinks() { - // hunk-header-style includes neither 'file' nor 'line-number'. - // This causes `paint_file_path_with_line_number` to return empty string. - // This test confirms that this remains true even when we are requesting hyperlinks. - - let config = integration_test_utils::make_config_from_args(&[ - "--hunk-header-style", - "syntax bold", - "--hunk-header-decoration-style", - "omit", - "--features", - "hyperlinks", - ]); - - let result = paint_file_path_with_line_number( - Some(3), - "some-file", - &config.hunk_header_style, - &config.hunk_header_line_number_style, - &config.hunk_header_style_include_file_path, - &config.hunk_header_style_include_line_number, - ":", - &config, - ); - - assert_eq!(result, ""); - } - - #[test] - fn test_paint_file_path_with_line_number_empty_navigate() { - let config = integration_test_utils::make_config_from_args(&[ - "--hunk-header-style", - "syntax bold", - "--hunk-header-decoration-style", - "omit", - "--navigate", - ]); - - let result = paint_file_path_with_line_number( - Some(3), - "δ some-file", - &config.hunk_header_style, - &config.hunk_header_line_number_style, - &config.hunk_header_style_include_file_path, - &config.hunk_header_style_include_line_number, - ":", - &config, - ); - - // result is - // "\u{1b}[1mδ some-file\u{1b}[0m:\u{1b}[34m3\u{1b}[0m" - assert_eq!(result, ""); - } - - #[test] - fn test_not_a_hunk_header_is_handled_gracefully() { - let config = integration_test_utils::make_config_from_args(&[]); - let output = - integration_test_utils::run_delta(GIT_LOG_OUTPUT_WITH_NOT_A_HUNK_HEADER, &config); - let output = strip_ansi_codes(&output); - assert!(output.contains("@@@2021-12-05")); - } - - const GIT_LOG_OUTPUT_WITH_NOT_A_HUNK_HEADER: &str = "\ -@@@2021-12-05 - -src/config.rs | 2 +- -src/delta.rs | 3 ++- -src/handlers/hunk.rs | 12 ++++++------ -src/handlers/hunk_header.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------ -src/handlers/merge_conflict.rs | 2 +- -src/handlers/submodule.rs | 4 ++-- -src/paint.rs | 2 +- -7 files changed, 90 insertions(+), 54 deletions(-) -"; -} |