Skip to content

Commit

Permalink
move highlighting to separate module, change marker
Browse files Browse the repository at this point in the history
  • Loading branch information
CptPotato committed Sep 22, 2023
1 parent 820688e commit 9e63cbe
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 100 deletions.
156 changes: 156 additions & 0 deletions src/highlight.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
// 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
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod branch;
mod command;
mod config;
mod debug;
mod highlight;
mod minibuffer;
mod parse;
mod render;
Expand Down
104 changes: 5 additions & 99 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand Down

0 comments on commit 9e63cbe

Please sign in to comment.