From b9e577aac0a81f759806a8fcbf8c29df2aa8c347 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Mon, 28 Dec 2020 15:45:10 +0000 Subject: State machine refactor (#477) --- src/delta.rs | 715 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 393 insertions(+), 322 deletions(-) diff --git a/src/delta.rs b/src/delta.rs index 620e00e8..f72302e4 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -52,254 +52,414 @@ impl State { // | HunkMinus | flush, emit | flush, emit | flush, emit | flush, emit | push | push | // | HunkPlus | flush, emit | flush, emit | flush, emit | flush, emit | flush, push | push | -pub fn delta( - mut lines: ByteLines, - writer: &mut dyn Write, - config: &Config, -) -> std::io::Result<()> -where - I: BufRead, -{ - let mut painter = Painter::new(writer, config); - let mut minus_file = "".to_string(); - let mut plus_file = "".to_string(); - let mut file_event = parse::FileEvent::NoEvent; - let mut state = State::Unknown; - let mut source = Source::Unknown; +struct StateMachine<'a> { + line: String, + raw_line: String, + state: State, + source: Source, + minus_file: String, + plus_file: String, + file_event: parse::FileEvent, + painter: Painter<'a>, + config: &'a Config, // When a file is modified, we use lines starting with '---' or '+++' to obtain the file name. // When a file is renamed without changes, we use lines starting with 'rename' to obtain the // file name (there is no diff hunk and hence no lines starting with '---' or '+++'). But when // a file is renamed with changes, both are present, and we rely on the following variables to // avoid emitting the file meta header line twice (#245). - let mut current_file_pair; - let mut handled_file_meta_header_line_file_pair = None; + current_file_pair: Option<(String, String)>, + handled_file_meta_header_line_file_pair: Option<(String, String)>, +} - while let Some(Ok(raw_line_bytes)) = lines.next() { - let raw_line = String::from_utf8_lossy(&raw_line_bytes); - let raw_line = if config.max_line_length > 0 && raw_line.len() > config.max_line_length { - ansi::truncate_str(&raw_line, config.max_line_length, &config.truncation_symbol) - } else { - raw_line - }; - let line = ansi::strip_ansi_codes(&raw_line).to_string(); - if source == Source::Unknown { - source = detect_source(&line); +pub fn delta(lines: ByteLines, writer: &mut dyn Write, config: &Config) -> std::io::Result<()> +where + I: BufRead, +{ + StateMachine::new(writer, config).consume(lines) +} + +impl<'a> StateMachine<'a> { + pub fn new(writer: &'a mut dyn Write, config: &'a Config) -> Self { + Self { + line: "".to_string(), + raw_line: "".to_string(), + state: State::Unknown, + source: Source::Unknown, + minus_file: "".to_string(), + plus_file: "".to_string(), + file_event: parse::FileEvent::NoEvent, + current_file_pair: None, + handled_file_meta_header_line_file_pair: None, + painter: Painter::new(writer, config), + config, } - if line.starts_with("commit ") { - painter.paint_buffered_minus_and_plus_lines(); - state = State::CommitMeta; - if should_handle(&state, config) { - painter.emit()?; - handle_commit_meta_header_line(&mut painter, &line, &raw_line, config)?; - continue; - } - } else if line.starts_with("diff ") { - painter.paint_buffered_minus_and_plus_lines(); - state = State::FileMeta; - handled_file_meta_header_line_file_pair = None; - } else if (state == State::FileMeta || source == Source::DiffUnified) - && (line.starts_with("--- ") - || line.starts_with("rename from ") - || line.starts_with("copy from ")) - { - let parsed_file_meta_line = - parse::parse_file_meta_line(&line, source == Source::GitDiff); - minus_file = parsed_file_meta_line.0; - file_event = parsed_file_meta_line.1; - - if source == Source::DiffUnified { - state = State::FileMeta; - painter.set_syntax(parse::get_file_extension_from_marker_line(&line)); - } else { - painter.set_syntax(parse::get_file_extension_from_file_meta_line_file_path( - &minus_file, - )); - } + } - // In color_only mode, raw_line's structure shouldn't be changed. - // So it needs to avoid fn handle_file_meta_header_line - // (it connects the plus_file and minus_file), - // and to call fn handle_generic_file_meta_header_line directly. - if config.color_only { - handle_generic_file_meta_header_line(&mut painter, &line, &raw_line, config)?; - continue; - } - } else if (state == State::FileMeta || source == Source::DiffUnified) - && (line.starts_with("+++ ") - || line.starts_with("rename to ") - || line.starts_with("copy to ")) - { - let parsed_file_meta_line = - parse::parse_file_meta_line(&line, source == Source::GitDiff); - plus_file = parsed_file_meta_line.0; - painter.set_syntax(parse::get_file_extension_from_file_meta_line_file_path( - &plus_file, - )); - current_file_pair = Some((minus_file.clone(), plus_file.clone())); - - // In color_only mode, raw_line's structure shouldn't be changed. - // So it needs to avoid fn handle_file_meta_header_line - // (it connects the plus_file and minus_file), - // and to call fn handle_generic_file_meta_header_line directly. - if config.color_only { - handle_generic_file_meta_header_line(&mut painter, &line, &raw_line, config)?; - continue; + fn consume(&mut self, mut lines: ByteLines) -> std::io::Result<()> + where + I: BufRead, + { + while let Some(Ok(raw_line_bytes)) = lines.next() { + self.ingest_line(raw_line_bytes); + let line = &self.line; + + if self.source == Source::Unknown { + self.source = detect_source(&line); } - if should_handle(&State::FileMeta, config) - && handled_file_meta_header_line_file_pair != current_file_pair + + let mut handled_line = if line.starts_with("commit ") { + self.handle_commit_meta_header_line()? + } else if line.starts_with("diff ") { + self.handle_file_meta_diff_line()? + } else if (self.state == State::FileMeta || self.source == Source::DiffUnified) + && (line.starts_with("--- ") + || line.starts_with("rename from ") + || line.starts_with("copy from ")) { - painter.emit()?; - handle_file_meta_header_line( - &mut painter, - &minus_file, - &plus_file, - config, - &file_event, - source == Source::DiffUnified, - )?; - handled_file_meta_header_line_file_pair = current_file_pair + self.handle_file_meta_minus_line()? + } else if (self.state == State::FileMeta || self.source == Source::DiffUnified) + && (line.starts_with("+++ ") + || line.starts_with("rename to ") + || line.starts_with("copy to ")) + { + self.handle_file_meta_plus_line()? + } else if line.starts_with("@@") { + self.handle_hunk_header_line()? + } else if self.source == Source::DiffUnified && line.starts_with("Only in ") + || line.starts_with("Submodule ") + || line.starts_with("Binary files ") + { + self.handle_additional_file_meta_cases()? + } else if self.state.is_in_hunk() { + // A true hunk line should start with one of: '+', '-', ' '. However, handle_hunk_line + // handles all lines until the state self transitions away from the hunk states. + self.handle_hunk_line()? + } else { + false + }; + if self.state == State::FileMeta && self.should_handle() && !self.config.color_only { + // The file metadata section is 4 lines. Skip them under non-plain file-styles. + // However in the case of color_only mode, + // we won't skip because we can't change raw_line structure. + handled_line = true } - } else if line.starts_with("@@") { - painter.paint_buffered_minus_and_plus_lines(); - state = State::HunkHeader; - painter.set_highlighter(); - painter.emit()?; - handle_hunk_header_line(&mut painter, &line, &raw_line, &plus_file, config)?; - painter.set_highlighter(); - continue; - } else if source == Source::DiffUnified && line.starts_with("Only in ") - || line.starts_with("Submodule ") - || line.starts_with("Binary files ") - { - // Additional FileMeta cases: - // - // 1. When comparing directories with diff -u, if filenames match between the - // directories, the files themselves will be compared. However, if an equivalent - // filename is not present, diff outputs a single line (Only in...) starting - // indicating that the file is present in only one of the directories. - // - // 2. Git diff emits lines describing submodule state such as "Submodule x/y/z contains - // untracked content" - // - // See https://github.com/dandavison/delta/issues/60#issuecomment-557485242 for a - // proposal for more robust parsing logic. - - painter.paint_buffered_minus_and_plus_lines(); - state = State::FileMeta; - if should_handle(&State::FileMeta, config) { - painter.emit()?; - handle_generic_file_meta_header_line(&mut painter, &line, &raw_line, config)?; - continue; + if !handled_line { + self.painter.emit()?; + writeln!( + self.painter.writer, + "{}", + format::format_raw_line(&self.raw_line, self.config) + )?; } - } else if state.is_in_hunk() { - // A true hunk line should start with one of: '+', '-', ' '. However, handle_hunk_line - // handles all lines until the state machine transitions away from the hunk states. - state = handle_hunk_line(&mut painter, &line, &raw_line, state, config); - painter.emit()?; - continue; } - if state == State::FileMeta && should_handle(&State::FileMeta, config) && !config.color_only - { - // The file metadata section is 4 lines. Skip them under non-plain file-styles. - // However in the case of color_only mode, - // we won't skip because we can't change raw_line structure. - continue; + self.painter.paint_buffered_minus_and_plus_lines(); + self.painter.emit()?; + Ok(()) + } + + fn ingest_line(&mut self, raw_line_bytes: &[u8]) { + // TODO: retain raw_line as Cow + self.raw_line = String::from_utf8_lossy(&raw_line_bytes).to_string(); + if self.config.max_line_length > 0 && self.raw_line.len() > self.config.max_line_length { + self.raw_line = ansi::truncate_str( + &self.raw_line, + self.config.max_line_length, + &self.config.truncation_symbol, + ) + .to_string() + }; + self.line = ansi::strip_ansi_codes(&self.raw_line); + } + + /// Should a handle_* function be called on this element? + fn should_handle(&self) -> bool { + let style = self.config.get_style(&self.state); + !(style.is_raw && style.decoration_style == DecorationStyle::NoDecoration) + } + + fn handle_commit_meta_header_line(&mut self) -> std::io::Result { + let mut handled_line = false; + self.painter.paint_buffered_minus_and_plus_lines(); + self.state = State::CommitMeta; + if self.should_handle() { + self.painter.emit()?; + self._handle_commit_meta_header_line()?; + handled_line = true + } + Ok(handled_line) + } + + fn _handle_commit_meta_header_line(&mut self) -> std::io::Result<()> { + if self.config.commit_style.is_omitted { + return Ok(()); + } + let (mut draw_fn, pad, decoration_ansi_term_style) = + draw::get_draw_function(self.config.commit_style.decoration_style); + let (formatted_line, formatted_raw_line) = if self.config.hyperlinks { + ( + features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink( + &self.line, + self.config, + ), + features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink( + &self.raw_line, + self.config, + ), + ) + } else { + (Cow::from(&self.line), Cow::from(&self.raw_line)) + }; + + draw_fn( + self.painter.writer, + &format!("{}{}", formatted_line, if pad { " " } else { "" }), + &format!("{}{}", formatted_raw_line, if pad { " " } else { "" }), + &self.config.decorations_width, + self.config.commit_style, + decoration_ansi_term_style, + )?; + Ok(()) + } + + fn handle_file_meta_diff_line(&mut self) -> std::io::Result { + self.painter.paint_buffered_minus_and_plus_lines(); + self.state = State::FileMeta; + self.handled_file_meta_header_line_file_pair = None; + Ok(false) + } + + 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); + self.minus_file = parsed_file_meta_line.0; + self.file_event = parsed_file_meta_line.1; + + if self.source == Source::DiffUnified { + self.state = State::FileMeta; + self.painter + .set_syntax(parse::get_file_extension_from_marker_line(&self.line)); } else { - painter.emit()?; - writeln!( - painter.writer, - "{}", - format::format_raw_line(&raw_line, config) + self.painter + .set_syntax(parse::get_file_extension_from_file_meta_line_file_path( + &self.minus_file, + )); + } + + // In color_only mode, raw_line's structure shouldn't be changed. + // So it needs to avoid fn _handle_file_meta_header_line + // (it connects the plus_file and minus_file), + // and to call fn handle_generic_file_meta_header_line directly. + if self.config.color_only { + _write_generic_file_meta_header_line( + &self.line, + &self.raw_line, + &mut self.painter, + self.config, )?; + handled_line = true; } + Ok(handled_line) } - painter.paint_buffered_minus_and_plus_lines(); - painter.emit()?; - Ok(()) -} + 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); + self.plus_file = parsed_file_meta_line.0; + self.painter + .set_syntax(parse::get_file_extension_from_file_meta_line_file_path( + &self.plus_file, + )); + self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone())); -/// Should a handle_* function be called on this element? -fn should_handle(state: &State, config: &Config) -> bool { - let style = config.get_style(state); - !(style.is_raw && style.decoration_style == DecorationStyle::NoDecoration) -} + // In color_only mode, raw_line's structure shouldn't be changed. + // So it needs to avoid fn _handle_file_meta_header_line + // (it connects the plus_file and minus_file), + // and to call fn handle_generic_file_meta_header_line directly. + if self.config.color_only { + _write_generic_file_meta_header_line( + &self.line, + &self.raw_line, + &mut self.painter, + self.config, + )?; + handled_line = true + } else if self.should_handle() + && self.handled_file_meta_header_line_file_pair != self.current_file_pair + { + self.painter.emit()?; + self._handle_file_meta_header_line(self.source == Source::DiffUnified)?; + self.handled_file_meta_header_line_file_pair = self.current_file_pair.clone() + } + Ok(handled_line) + } -/// Try to detect what is producing the input for delta. -/// -/// Currently can detect: -/// * git diff -/// * diff -u -fn detect_source(line: &str) -> Source { - if line.starts_with("commit ") || line.starts_with("diff --git ") { - Source::GitDiff - } else if line.starts_with("diff -u") - || line.starts_with("diff -ru") - || line.starts_with("diff -r -u") - || line.starts_with("diff -U") - || line.starts_with("--- ") - || line.starts_with("Only in ") - { - Source::DiffUnified - } else { - Source::Unknown + /// Construct file change line from minus and plus file and write with FileMeta styling. + fn _handle_file_meta_header_line(&mut self, comparing: bool) -> std::io::Result<()> { + let line = parse::get_file_change_description_from_file_paths( + &self.minus_file, + &self.plus_file, + comparing, + &self.file_event, + self.config, + ); + // FIXME: no support for 'raw' + _write_generic_file_meta_header_line(&line, &line, &mut self.painter, self.config) } -} -fn handle_commit_meta_header_line( - painter: &mut Painter, - line: &str, - raw_line: &str, - config: &Config, -) -> std::io::Result<()> { - if config.commit_style.is_omitted { - return Ok(()); + fn handle_additional_file_meta_cases(&mut self) -> std::io::Result { + let mut handled_line = false; + + // Additional FileMeta cases: + // + // 1. When comparing directories with diff -u, if filenames match between the + // directories, the files themselves will be compared. However, if an equivalent + // filename is not present, diff outputs a single line (Only in...) starting + // indicating that the file is present in only one of the directories. + // + // 2. Git diff emits lines describing submodule state such as "Submodule x/y/z contains + // untracked content" + // + // See https://github.com/dandavison/delta/issues/60#issuecomment-557485242 for a + // proposal for more robust parsing logic. + + self.painter.paint_buffered_minus_and_plus_lines(); + self.state = State::FileMeta; + if self.should_handle() { + self.painter.emit()?; + _write_generic_file_meta_header_line( + &self.line, + &self.raw_line, + &mut self.painter, + self.config, + )?; + handled_line = true; + } + + Ok(handled_line) } - let (mut draw_fn, pad, decoration_ansi_term_style) = - draw::get_draw_function(config.commit_style.decoration_style); - let (formatted_line, formatted_raw_line) = if config.hyperlinks { - ( - features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink(line, config), - features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink(raw_line, config), - ) - } else { - (Cow::from(line), Cow::from(raw_line)) - }; - draw_fn( - painter.writer, - &format!("{}{}", formatted_line, if pad { " " } else { "" }), - &format!("{}{}", formatted_raw_line, if pad { " " } else { "" }), - &config.decorations_width, - config.commit_style, - decoration_ansi_term_style, - )?; - Ok(()) -} + /// Emit the hunk header, with any requested decoration. + fn handle_hunk_header_line(&mut self) -> std::io::Result { + self.painter.paint_buffered_minus_and_plus_lines(); + self.state = State::HunkHeader; + self.painter.set_highlighter(); + self.painter.emit()?; -/// Construct file change line from minus and plus file and write with FileMeta styling. -fn handle_file_meta_header_line( - painter: &mut Painter, - minus_file: &str, - plus_file: &str, - config: &Config, - file_event: &parse::FileEvent, - comparing: bool, -) -> std::io::Result<()> { - let line = parse::get_file_change_description_from_file_paths( - minus_file, plus_file, comparing, file_event, config, - ); - // FIXME: no support for 'raw' - handle_generic_file_meta_header_line(painter, &line, &line, config) + let (raw_code_fragment, line_numbers) = parse::parse_hunk_header(&self.line); + if self.config.line_numbers { + self.painter + .line_numbers_data + .initialize_hunk(&line_numbers, self.plus_file.to_string()); + } + + if self.config.hunk_header_style.is_raw { + hunk_header::write_hunk_header_raw( + &mut self.painter, + &self.line, + &self.raw_line, + self.config, + )?; + } else if self.config.hunk_header_style.is_omitted { + writeln!(self.painter.writer)?; + } else { + hunk_header::write_hunk_header( + &raw_code_fragment, + &line_numbers, + &mut self.painter, + &self.line, + &self.plus_file, + self.config, + )?; + }; + self.painter.set_highlighter(); + Ok(true) + } + + /// Handle a hunk line, i.e. a minus line, a plus line, or an unchanged line. + // In the case of a minus or plus line, we store the line in a + // buffer. When we exit the changed region we process the collected + // minus and plus lines jointly, in order to paint detailed + // highlighting according to inferred edit operations. In the case of + // an unchanged line, we paint it immediately. + fn handle_hunk_line(&mut self) -> std::io::Result { + // Don't let the line buffers become arbitrarily large -- if we + // were to allow that, then for a large deleted/added file we + // would process the entire file before painting anything. + if self.painter.minus_lines.len() > self.config.line_buffer_size + || self.painter.plus_lines.len() > self.config.line_buffer_size + { + self.painter.paint_buffered_minus_and_plus_lines(); + } + self.state = match self.line.chars().next() { + Some('-') => { + if let State::HunkPlus(_) = self.state { + self.painter.paint_buffered_minus_and_plus_lines(); + } + let state = match self.config.inspect_raw_lines { + cli::InspectRawLines::True + if style::line_has_style_other_than( + &self.raw_line, + [*style::GIT_DEFAULT_MINUS_STYLE, self.config.git_minus_style].iter(), + ) => + { + State::HunkMinus(Some(self.painter.prepare_raw_line(&self.raw_line))) + } + _ => State::HunkMinus(None), + }; + self.painter + .minus_lines + .push((self.painter.prepare(&self.line), state.clone())); + state + } + Some('+') => { + let state = match self.config.inspect_raw_lines { + cli::InspectRawLines::True + if style::line_has_style_other_than( + &self.raw_line, + [*style::GIT_DEFAULT_PLUS_STYLE, self.config.git_plus_style].iter(), + ) => + { + State::HunkPlus(Some(self.painter.prepare_raw_line(&self.raw_line))) + } + _ => State::HunkPlus(None), + }; + self.painter + .plus_lines + .push((self.painter.prepare(&self.line), state.clone())); + state + } + Some(' ') => { + self.painter.paint_buffered_minus_and_plus_lines(); + self.painter.paint_zero_line(&self.line); + State::HunkZero + } + _ => { + // The first character here could be e.g. '\' from '\ No newline at end of file'. This + // is not a hunk line, but the parser does not have a more accurate state corresponding + // to this. + self.painter.paint_buffered_minus_and_plus_lines(); + self.painter + .output_buffer + .push_str(&self.painter.expand_tabs(self.raw_line.graphemes(true))); + self.painter.output_buffer.push('\n'); + State::HunkZero + } + }; + self.painter.emit()?; + Ok(true) + } } /// Write `line` with FileMeta styling. -fn handle_generic_file_meta_header_line( - painter: &mut Painter, +fn _write_generic_file_meta_header_line( line: &str, raw_line: &str, + painter: &mut Painter, config: &Config, ) -> std::io::Result<()> { // If file_style is "omit", we'll skip the process and print nothing. @@ -327,112 +487,23 @@ fn handle_generic_file_meta_header_line( Ok(()) } -/// Emit the hunk header, with any requested decoration. -fn handle_hunk_header_line( - painter: &mut Painter, - line: &str, - raw_line: &str, - plus_file: &str, - config: &Config, -) -> std::io::Result<()> { - let (raw_code_fragment, line_numbers) = parse::parse_hunk_header(&line); - if config.line_numbers { - painter - .line_numbers_data - .initialize_hunk(&line_numbers, plus_file.to_string()); - } - - if config.hunk_header_style.is_raw { - hunk_header::write_hunk_header_raw(painter, line, raw_line, config)?; - } else if config.hunk_header_style.is_omitted { - writeln!(painter.writer)?; - } else { - hunk_header::write_hunk_header( - &raw_code_fragment, - &line_numbers, - painter, - line, - plus_file, - config, - )?; - }; - Ok(()) -} - -/// Handle a hunk line, i.e. a minus line, a plus line, or an unchanged line. -// In the case of a minus or plus line, we store the line in a -// buffer. When we exit the changed region we process the collected -// minus and plus lines jointly, in order to paint detailed -// highlighting according to inferred edit operations. In the case of -// an unchanged line, we paint it immediately. -fn handle_hunk_line( - painter: &mut Painter, - line: &str, - raw_line: &str, - state: State, - config: &Config, -) -> State { - // Don't let the line buffers become arbitrarily large -- if we - // were to allow that, then for a large deleted/added file we - // would process the entire file before painting anything. - if painter.minus_lines.len() > config.line_buffer_size - || painter.plus_lines.len() > config.line_buffer_size +/// Try to detect what is producing the input for delta. +/// +/// Currently can detect: +/// * git diff +/// * diff -u +fn detect_source(line: &str) -> Source { + if line.starts_with("commit ") || line.starts_with("diff --git ") { + Source::GitDiff + } else if line.starts_with("diff -u") + || line.starts_with("diff -ru") + || line.starts_with("diff -r -u") + || line.starts_with("diff -U") + || line.starts_with("--- ") + || line.starts_with("Only in ") { - painter.paint_buffered_minus_and_plus_lines(); - } - match line.chars().next() { - Some('-') => { - if let State::HunkPlus(_) = state { - painter.paint_buffered_minus_and_plus_lines(); - } - let state = match config.inspect_raw_lines { - cli::InspectRawLines::True - if style::line_has_style_other_than( - raw_line, - [*style::GIT_DEFAULT_MINUS_STYLE, config.git_minus_style].iter(), - ) => - { - State::HunkMinus(Some(painter.prepare_raw_line(raw_line))) - } - _ => State::HunkMinus(None), - }; - painter - .minus_lines - .push((painter.prepare(&line), state.clone())); - state - } - Some('+') => { - let state = match config.inspect_raw_lines { - cli::InspectRawLines::True - if style::line_has_style_other_than( - raw_line, - [*style::GIT_DEFAULT_PLUS_STYLE, config.git_plus_style].iter(), - ) => - { - State::HunkPlus(Some(painter.prepare_raw_line(raw_line))) - } - _ => State::HunkPlus(None), - }; - painter - .plus_lines - .push((painter.prepare(&line), state.clone())); - state - } - Some(' ') => { - painter.paint_buffered_minus_and_plus_lines(); - painter.paint_zero_line(&line); - State::HunkZero - } - _ => { - // The first character here could be e.g. '\' from '\ No newline at end of file'. This - // is not a hunk line, but the parser does not have a more accurate state corresponding - // to this. - painter.paint_buffered_minus_and_plus_lines(); - painter - .output_buffer - .push_str(&painter.expand_tabs(raw_line.graphemes(true))); - painter.output_buffer.push('\n'); - State::HunkZero - } + Source::DiffUnified + } else { + Source::Unknown } } -- cgit v1.2.3