use hac_core::syntax::highlighter::HIGHLIGHTER;
use std::ops::Add;
use ratatui::style::{Color, Stylize};
use ratatui::text::{Line, Span};
use tree_sitter::Tree;
fn is_endline(c: char) -> bool {
matches!(c, '\n' | '\r')
}
pub fn build_syntax_highlighted_lines(
content: &str,
tree: Option<&Tree>,
colors: &hac_colors::Colors,
) -> Vec<Line<'static>> {
let mut styled_lines: Vec<Line> = vec![];
let mut highlights = HIGHLIGHTER
.read()
.unwrap()
.apply(content, tree, &colors.tokens);
let mut current_line: Vec<Span> = vec![];
let mut current_token = String::default();
let mut current_capture = highlights.pop_front();
let mut skip_next = false;
for (i, c) in content.chars().enumerate() {
if skip_next {
skip_next = false;
continue;
}
if let Some(ref capture) = current_capture {
if i == capture.start && current_token.is_empty() {
current_token.push(c);
continue;
}
if i == capture.start && !current_token.is_empty() {
current_line.push(Span::from(current_token.clone()).fg(colors.normal.white));
current_token.clear();
current_token.push(c);
continue;
}
if i == capture.end && is_endline(c) {
current_token.push(c);
current_line.push(Span::styled(current_token.clone(), capture.style));
styled_lines.push(current_line.clone().into());
current_token.clear();
current_line.clear();
current_capture = highlights.pop_front();
content
.chars()
.nth(i.add(1))
.and_then(|next| is_endline(next).then(|| skip_next = true));
continue;
}
if i == capture.end {
current_line.push(Span::styled(current_token.clone(), capture.style));
current_token.clear();
current_token.push(c);
current_capture = highlights.pop_front();
continue;
}
if is_endline(c) {
current_token.push(c);
current_line.push(Span::styled(current_token.clone(), capture.style));
styled_lines.push(current_line.clone().into());
current_token.clear();
current_line.clear();
content
.chars()
.nth(i.add(1))
.and_then(|next| is_endline(next).then(|| skip_next = true));
continue;
}
current_token.push(c);
continue;
}
if !current_token.is_empty() && !is_endline(c) {
current_line.push(Span::from(current_token.clone()).fg(colors.normal.white));
current_token.clear();
current_token.push(c);
continue;
}
if is_endline(c) {
current_line.push(Span::from(current_token.clone()).fg(colors.normal.white));
styled_lines.push(current_line.clone().into());
current_token.clear();
current_line.clear();
content
.chars()
.nth(i.add(1))
.and_then(|next| is_endline(next).then(|| skip_next = true));
continue;
}
current_token.push(c);
}
current_line.push(current_token.clone().into());
styled_lines.push(current_line.clone().into());
styled_lines
}
pub fn blend_colors_multiply(original: Color, overlay: Color, alpha: f32) -> Color {
let blend_component = |fg, bg| (alpha * fg as f32 + (1.0 - alpha) * bg as f32) as u8;
let Some(foreground) = color_to_rgb(original) else {
return original;
};
let Some(background) = color_to_rgb(overlay) else {
return original;
};
let r = blend_component(foreground.0, background.0);
let g = blend_component(foreground.1, background.1);
let b = blend_component(foreground.2, background.2);
Color::Rgb(r, g, b)
}
fn color_to_rgb(color: Color) -> Option<(u8, u8, u8)> {
match color {
Color::Rgb(r, g, b) => Some((r, g, b)),
Color::Indexed(color) => ansi_to_rgb(color),
_ => None,
}
}
fn ansi_to_rgb(val: u8) -> Option<(u8, u8, u8)> {
let rgb_table: [(u8, u8, u8); 16] = [
(0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0), (0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192), (128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0), (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255), ];
if val < 16 {
Some(rgb_table[val as usize])
} else {
None
}
}
pub trait EnumIter {
fn iter() -> &'static [Self]
where
Self: Sized;
fn len() -> usize
where
Self: Sized;
}
#[macro_export]
macro_rules! impl_enum_iter {
($name:ident { $($variant:ident),* $(,)? }) => {
impl $name {
pub const fn iter() -> &'static [$name] {
&[
$( $name::$variant ),*
]
}
pub const fn len() -> usize {
$name::iter().len()
}
}
};
}