use std::io::Write;
use itertools::Itertools;
use syntect::easy::HighlightLines;
use syntect::highlighting::Style as SyntectStyle;
use syntect::parsing::{SyntaxReference, SyntaxSet};
use unicode_segmentation::UnicodeSegmentation;
use crate::ansi;
use crate::config::{self, delta_unreachable};
use crate::delta::State;
use crate::edits;
use crate::features::line_numbers;
use crate::features::side_by_side;
use crate::paint::superimpose_style_sections::superimpose_style_sections;
use crate::style::Style;
pub struct Painter<'a> {
pub minus_lines: Vec<(String, State)>,
pub plus_lines: Vec<(String, State)>,
pub writer: &'a mut dyn Write,
pub syntax: &'a SyntaxReference,
pub highlighter: Option<HighlightLines<'a>>,
pub config: &'a config::Config,
pub output_buffer: String,
pub line_numbers_data: line_numbers::LineNumbersData<'a>,
}
impl<'a> Painter<'a> {
pub fn new(writer: &'a mut dyn Write, config: &'a config::Config) -> Self {
let default_syntax = Self::get_syntax(&config.syntax_set, None);
let line_numbers_data = if config.line_numbers {
line_numbers::LineNumbersData::from_format_strings(
&config.line_numbers_left_format,
&config.line_numbers_right_format,
)
} else {
line_numbers::LineNumbersData::default()
};
Self {
minus_lines: Vec::new(),
plus_lines: Vec::new(),
output_buffer: String::new(),
syntax: default_syntax,
highlighter: None,
writer,
config,
line_numbers_data,
}
}
pub fn set_syntax(&mut self, extension: Option<&str>) {
self.syntax = Painter::get_syntax(&self.config.syntax_set, extension);
}
fn get_syntax(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))
};
}
/// Replace initial -/+ character with ' ', expand tabs as spaces, and optionally terminate with
/// newline.
// Terminating with newline character is necessary for many of the sublime syntax definitions to
// highlight correctly.
// See https://docs.rs/syntect/3.2.0/syntect/parsing/struct.SyntaxSetBuilder.html#method.add_from_folder
pub fn prepare(&self, line: &str) -> String {
if !line.is_empty() {
let mut line = line.graphemes(true);
// The first column contains a -/+/space character, added by git. We substitute it for
// a space now, so that it is not present during syntax highlighting. When emitting the
// line in Painter::paint_line, we drop the space (unless --keep-plus-minus-markers is
// in effect in which case we replace it with the appropriate marker).
// TODO: Things should, but do not, work if this leading space is omitted at this stage.
// See comment in align::Alignment::new.
line.next();
format!(" {}\n", self.expand_tabs(line))
} else {
"\n".to_string()
}
}
/// Remove the initial +/- character of a line that will be emitted unchanged, including any