use std::borrow::Cow; use std::collections::HashMap; use std::io::Write; use ansi_term::ANSIString; use itertools::Itertools; use syntect::easy::HighlightLines; use syntect::highlighting::Style as SyntectStyle; use syntect::parsing::{SyntaxReference, SyntaxSet}; use crate::config::{self, delta_unreachable, Config}; use crate::delta::{DiffType, InMergeConflict, MergeParents, State}; use crate::features::hyperlinks; use crate::features::line_numbers::{self, LineNumbersData}; use crate::features::side_by_side::ansifill; use crate::features::side_by_side::{self, PanelSide}; use crate::handlers::merge_conflict; use crate::minusplus::*; use crate::paint::superimpose_style_sections::superimpose_style_sections; use crate::style::Style; use crate::{ansi, style}; use crate::{edits, utils, utils::tabs}; pub type LineSections<'a, S> = Vec<(S, &'a str)>; pub struct Painter<'p> { pub minus_lines: Vec<(String, State)>, pub plus_lines: Vec<(String, State)>, pub writer: &'p mut dyn Write, pub syntax: &'p SyntaxReference, pub highlighter: Option>, pub config: &'p config::Config, pub output_buffer: String, // If config.line_numbers is true, then the following is always Some(). // In side-by-side mode it is always Some (but possibly an empty one), even // if config.line_numbers is false. See `UseFullPanelWidth` as well. pub line_numbers_data: Option>, pub merge_conflict_lines: merge_conflict::MergeConflictLines, pub merge_conflict_commit_names: merge_conflict::MergeConflictCommitNames, } // How the background of a line is filled up to the end #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum BgFillMethod { // Fill the background with ANSI spaces if possible, // but might fallback to Spaces (e.g. in the left side-by-side panel), // also see `UseFullPanelWidth` TryAnsiSequence, Spaces, } // If the background of a line extends to the end, and if configured to do so, how. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum BgShouldFill { With(BgFillMethod), No, } impl Default for BgShouldFill { fn default() -> Self { BgShouldFill::With(BgFillMethod::TryAnsiSequence) } } #[derive(PartialEq, Debug)] pub enum StyleSectionSpecifier<'l> { Style(Style), StyleSections(LineSections<'l, Style>), } impl<'p> Painter<'p> { pub fn new(writer: &'p mut dyn Write, config: &'p config::Config) -> Self { let default_syntax = Self::get_syntax(&config.syntax_set, None); let panel_width_fix = ansifill::UseFullPanelWidth::new(config); let line_numbers_data = if config.line_numbers { Some(line_numbers::LineNumbersData::from_format_strings( &config.line_numbers_format, panel_width_fix, )) } else if config.side_by_side { // If line numbers are disabled in side-by-side then the data is still used // for width calculation and to pad odd width to even, see `UseFullPanelWidth` // for details. Some(line_numbers::LineNumbersData::empty_for_sbs( panel_width_fix, )) } else { None }; Self { minus_lines: Vec::new(), plus_lines: Vec::new(), output_buffer: String::new(), syntax: default_syntax, highlighter: None, writer, config, line_numbers_data, merge_conflict_lines: merge_conflict::MergeConflictLines::new(), merge_conflict_commit_names: merge_conflict::MergeConflictCommitNames::new(), } } pub fn set_syntax(&mut self, extension: Option<&str>) { self.syntax = Painter::get_syntax(&self.config.syntax_set, extension); } fn get_syntax<'a>(syntax_set: &'a SyntaxSet, extension: Option<&str>) -> &'a SyntaxReference { if let Some(extension) = extension { if let Some(syntax) = syntax_set.find_syntax_by_extension(extension) { return syntax; } } syntax_set .find_syntax_by_extension("txt") .unwrap_or_else(|| delta_unreachable("Failed to find any language syntax definitions.")) } pub fn set_highlighter(&mut self) { if let Some(ref syntax_theme) = self.config.syntax_theme { self.highlighter = Some(HighlightLines::new(self.syntax, syntax_theme)) }; } pub fn paint_buffered_minus_and_plus_lines(&mut self) { if self.minus_lines.is_empty() && self.plus_lines.is_empty() { return; } paint_minus_and_plus_lines( MinusPlus::new(&self.minus_lines, &self.plus_lines), &mut self.line_numbers_data, &mut self.highlighter, &mut self.output_buffer, self.config, ); self.minus_lines.clear(); self.plus_lines.clear(); } pub fn paint_zero_line(&mut self, line: &str, state: State) { let lines = &[(line.to_string(), state.clone())]; let syntax_style_sections = get_syntax_style_sections_for_lines(lines, self.highlighter.as_mut(), self.config); let mut diff_style_sections = vec![vec![(self.config.zero_style, lines[0].0.as_str())]]; // TODO: compute style from state Painter::update_diff_style_sections( lines, &mut diff_style_sections, None, None, &[false], self.config, ); if self.config.side_by_side { // `lines[0].0` so the line has the '\n' already added (as in the +- case) side_by_side::paint_zero_lines_side_by_side( &lines[0].0, syntax_style_sections, diff_style_sections, &mut self.output_buffer, self.config, &mut self.line_numbers_data.as_mut(), painted_prefix(state, self.config), BgShouldFill::With(BgFillMethod::Spaces), ); } else { Painter::paint_lines( lines, &syntax_style_sections, diff_style_sections.as_slice(), &[false], &mut self.output_buffer, self.config, &mut self.line_numbers_data.as_mut(), None, BgShouldFill::With(BgFillMethod::Spaces), ); } } /// Superimpose background styles and foreground syntax /// highlighting styles, and write colored lines to output buffer. #[allow(clippy::too_many_arguments)] pub fn paint_lines<'a>( lines: &'a [(String, State)], syntax_style_sections: &[LineSections<'a, SyntectStyle>], diff_style_sections: &[LineSections<'a, Style>], lines_have_homolog: &[bool], output_buffer: &mut String, config: &config::Config, line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>, empty_line_style: Option