From 9e63cbee7f049cc6d94eee1bedf6f98f9ca0bf16 Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:31:27 +0200 Subject: [PATCH] move highlighting to separate module, change marker --- src/highlight.rs | 156 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/parse.rs | 104 ++----------------------------- src/status.rs | 3 +- 4 files changed, 164 insertions(+), 100 deletions(-) create mode 100644 src/highlight.rs diff --git a/src/highlight.rs b/src/highlight.rs new file mode 100644 index 0000000..1b15adb --- /dev/null +++ b/src/highlight.rs @@ -0,0 +1,156 @@ +use std::iter; + +use itertools::Itertools; +use syntect::{ + easy::HighlightLines, + highlighting::{Color, FontStyle, Style, Theme, ThemeSet}, + parsing::{SyntaxReference, SyntaxSet}, + util::as_24_bit_terminal_escaped, +}; + +// Diff styles +// TODO: use existing colors from configuration? +const MARKER_NONE: Style = Style { + foreground: Color::WHITE, + background: Color::BLACK, + font_style: FontStyle::empty(), +}; + +const MARKER_ADDED: Style = Style { + foreground: Color { + r: 0x78, + g: 0xde, + b: 0x0c, + a: 0xff, + }, + background: Color { + r: 0x0a, + g: 0x28, + b: 0x00, + a: 0xff, + }, + font_style: FontStyle::empty(), +}; + +const MARKER_REMOVED: Style = Style { + foreground: Color { + r: 0xd3, + g: 0x2e, + b: 0x09, + a: 0xff, + }, + background: Color { + r: 0x3f, + g: 0x0e, + b: 0x00, + a: 0xff, + }, + font_style: FontStyle::empty(), +}; + +#[derive(Debug)] +pub struct SyntaxHighlight { + syntax_set: SyntaxSet, + theme: Theme, +} + +impl SyntaxHighlight { + pub fn new() -> Self { + Self { + syntax_set: SyntaxSet::load_defaults_newlines(), + // TODO: theme configuration + theme: ThemeSet::load_defaults().themes["base16-eighties.dark"].clone(), + } + } + + pub fn get_syntax(&self, path: &str) -> &SyntaxReference { + // TODO: probably better to use std::path? + let file_ext = path.rsplit('.').next().unwrap_or(""); + self.syntax_set + .find_syntax_by_extension(file_ext) + .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()) + } +} + +// TODO: just a workaround so that Status::default() still works +impl Default for SyntaxHighlight { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum DiffStatus { + Unchanged, + Added, + Removed, +} + +pub fn highlight_hunk(hunk: &str, highlight: &SyntaxHighlight, syntax: &SyntaxReference) -> String { + let mut highlighter = HighlightLines::new(syntax, &highlight.theme); + + if let Some(hunk) = try_highlight_single_line(hunk, highlight, syntax) { + return hunk; + } + + // syntax highlight each line + hunk.lines() + .map(|line| { + let Some(diff_char) = line.chars().next() else { + return line.to_owned(); + }; + + let diff_char_len = diff_char.len_utf8(); + + // add marker and one space + let (marker, diff_line, status) = match diff_char { + '+' => ( + (MARKER_ADDED, "\u{258c} "), + &line[diff_char_len..], + DiffStatus::Added, + ), + '-' => ( + (MARKER_REMOVED, "\u{258c} "), + &line[diff_char_len..], + DiffStatus::Removed, + ), + _ => ((MARKER_NONE, " "), line, DiffStatus::Unchanged), + }; + + let Ok(ranges) = highlighter.highlight_line(diff_line, &highlight.syntax_set) else { + // Syntax highlighting failed, fallback to no highlighting + // TODO: propagate error? + return diff_line.to_owned(); + }; + + let mut ranges: Vec<_> = iter::once(marker).chain(ranges).collect(); + + match status { + DiffStatus::Unchanged => (), + DiffStatus::Added => { + for r in &mut ranges { + r.0.background = MARKER_ADDED.background; + } + } + DiffStatus::Removed => { + for r in &mut ranges { + r.0.background = MARKER_REMOVED.background; + } + } + } + + as_24_bit_terminal_escaped(&ranges, status != DiffStatus::Unchanged) + }) + .join("\n") +} + +fn try_highlight_single_line(hunk: &str, highlight: &SyntaxHighlight, syntax: &SyntaxReference) -> Option { + // TODO: attempt highlighting single line changes with strong inline highlight + + // - foo(bar, baz); + // --- strong background highlight (red) + // + foo(bar, BAZ); + // --- strong background highlight (green) + + None +} diff --git a/src/main.rs b/src/main.rs index e6c5de8..1a4d2a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ mod branch; mod command; mod config; mod debug; +mod highlight; mod minibuffer; mod parse; mod render; diff --git a/src/parse.rs b/src/parse.rs index 360b127..d444974 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,73 +1,10 @@ -use std::{collections::HashMap, iter}; +use std::collections::HashMap; use anyhow::{Context, Result}; use itertools::Itertools; use nom::{bytes::complete::tag, character::complete::not_line_ending, IResult}; -use syntect::{ - easy::HighlightLines, - highlighting::{Color, FontStyle, Style, Theme, ThemeSet}, - parsing::{SyntaxReference, SyntaxSet}, - util::as_24_bit_terminal_escaped, -}; -// Diff styles: -const MARKER_NONE: Style = Style { - foreground: Color::WHITE, - background: Color::BLACK, - font_style: FontStyle::empty(), -}; - -const MARKER_ADDED: Style = Style { - foreground: Color { - r: 0x78, - g: 0xde, - b: 0x0c, - a: 0xff, - }, - background: Color::BLACK, - font_style: FontStyle::empty(), -}; -const MARKER_REMOVED: Style = Style { - foreground: Color { - r: 0xd3, - g: 0x2e, - b: 0x09, - a: 0xff, - }, - background: Color::BLACK, - font_style: FontStyle::empty(), -}; - -#[derive(Debug)] -pub struct SyntaxHighlight { - syntax_set: SyntaxSet, - theme: Theme, -} - -impl SyntaxHighlight { - pub fn new() -> Self { - Self { - syntax_set: SyntaxSet::load_defaults_newlines(), - // TODO: theme configuration - theme: ThemeSet::load_defaults().themes["base16-eighties.dark"].clone(), - } - } - - fn get_syntax(&self, path: &str) -> &SyntaxReference { - // TODO: probably better to use std::path? - let file_ext = path.rsplit('.').next().unwrap_or(""); - self.syntax_set - .find_syntax_by_extension(file_ext) - .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()) - } -} - -// TODO: just a workaround so that Status::default() still works -impl Default for SyntaxHighlight { - fn default() -> Self { - Self::new() - } -} +use crate::highlight::{highlight_hunk, SyntaxHighlight}; /// The returned hashmap associates a filename with a `Vec` of `String` where the strings contain /// the content of each hunk. @@ -84,43 +21,12 @@ pub fn parse_diff<'a>( .skip(1) { let path = get_path(diff)?; + // get language syntax here, since all hunks are from the same file let syntax = highlight.get_syntax(path); - let mut highlighter = HighlightLines::new(syntax, &highlight.theme); let hunks = get_hunks(diff)? - .into_iter() - .map(|hunk| { - let s: String = hunk - .lines() - .map(|line| { - let Some(diff_char) = line.chars().next() else { - return line.to_owned(); - }; - - let diff_char_len = diff_char.len_utf8(); - - // add marker and one space - let (marker, diff_line) = match diff_char { - '+' => ((MARKER_ADDED, "+ "), &line[diff_char_len..]), - '-' => ((MARKER_REMOVED, "- "), &line[diff_char_len..]), - _ => ((MARKER_NONE, " "), line), - }; - - let Ok(ranges) = - highlighter.highlight_line(diff_line, &highlight.syntax_set) - else { - // Syntax highighting failed, fallback to no highlighting - // TODO: propagate error? - return diff_line.to_owned(); - }; - - let ranges: Vec<_> = iter::once(marker).chain(ranges).collect(); - as_24_bit_terminal_escaped(&ranges, false) - }) - .join("\n"); - s - }) + .iter() + .map(|hunk| highlight_hunk(hunk, highlight, syntax)) .collect(); - diffs.insert(path, hunks); } Ok(diffs) diff --git a/src/status.rs b/src/status.rs index 8ae7f17..d447fac 100644 --- a/src/status.rs +++ b/src/status.rs @@ -15,8 +15,9 @@ use nom::{bytes::complete::take_until, IResult}; use crate::{ config::{Config, Options, CONFIG}, git_process, + highlight::SyntaxHighlight, minibuffer::{MessageType, MiniBuffer}, - parse::{self, parse_hunk_new, parse_hunk_old, SyntaxHighlight}, + parse::{self, parse_hunk_new, parse_hunk_old}, render::{self, Renderer, ResetAttributes, ResetColor}, };