use std::fmt::Write as FmtWrite;
use crate::tui::colorizer::types::CodeLang;
pub struct CodeColorizer;
impl CodeColorizer {
pub fn highlight(code: &str, lang: CodeLang, max_lines: Option<usize>) -> String {
let lines: Vec<&str> = code.lines().collect();
let total = lines.len();
let show = max_lines.unwrap_or(total).min(total);
let truncated = total > show;
let mut out = String::with_capacity(code.len() + (code.len() / 4));
for (i, line) in lines.iter().take(show).enumerate() {
if i > 0 {
out.push('\n');
}
Self::highlight_line(line, lang, &mut out);
}
if truncated {
let _ = write!(out, "\n\x1b[2m... ({} more lines)\x1b[0m", total - show);
}
out
}
fn highlight_line(line: &str, lang: CodeLang, out: &mut String) {
let chars: Vec<char> = line.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
let c = chars[i];
if c == '"' || c == '\'' || c == '`' {
let quote = c;
out.push_str("\x1b[33m"); out.push(quote);
i += 1;
while i < len {
if chars[i] == '\\' && i + 1 < len {
out.push(chars[i]);
out.push(chars[i + 1]);
i += 2;
} else if chars[i] == quote {
out.push(chars[i]);
i += 1;
break;
} else {
out.push(chars[i]);
i += 1;
}
}
out.push_str("\x1b[0m");
continue;
}
if (c == '/' && i + 1 < len && chars[i + 1] == '/')
|| (c == '#' && lang != CodeLang::Toml)
{
out.push_str("\x1b[2m\x1b[37m"); while i < len {
out.push(chars[i]);
i += 1;
}
out.push_str("\x1b[0m");
break;
}
if c == '/' && i + 1 < len && chars[i + 1] == '*' {
out.push_str("\x1b[2m\x1b[37m"); out.push_str("/*");
i += 2;
while i < len {
if chars[i] == '*' && i + 1 < len && chars[i + 1] == '/' {
out.push_str("*/");
i += 2;
break;
}
out.push(chars[i]);
i += 1;
}
out.push_str("\x1b[0m");
continue;
}
if c == '<'
&& i + 3 < len
&& chars[i + 1] == '!'
&& chars[i + 2] == '-'
&& chars[i + 3] == '-'
{
out.push_str("\x1b[2m\x1b[37m");
while i < len {
out.push(chars[i]);
i += 1;
}
out.push_str("\x1b[0m");
break;
}
if c.is_ascii_digit()
&& (i == 0 || !chars[i - 1].is_alphanumeric() || chars[i - 1] == '.')
{
out.push_str("\x1b[35m"); if c == '0' && i + 1 < len && (chars[i + 1] == 'x' || chars[i + 1] == 'X') {
out.push_str("0x");
i += 2;
}
while i < len
&& (chars[i].is_ascii_digit()
|| chars[i] == '.'
|| chars[i] == '_'
|| chars[i].is_ascii_alphabetic()
&& i > 0
&& chars[i - 1] == '0'
&& (chars[i] == 'x' || chars[i] == 'X'))
{
if chars[i].is_ascii_alphabetic()
&& !(i > 0 && chars[i - 1] == '0' && (chars[i] == 'x' || chars[i] == 'X'))
&& !(chars[i] == 'e' || chars[i] == 'E')
{
break;
}
out.push(chars[i]);
i += 1;
}
out.push_str("\x1b[0m");
continue;
}
let remaining: String = chars[i..].iter().collect();
let mut matched_bool = false;
for kw in &["true", "false", "null", "nil", "None", "True", "False"] {
if remaining.starts_with(kw)
&& (i + kw.len() >= len
|| !chars[i + kw.len()].is_alphanumeric() && chars[i + kw.len()] != '_')
{
out.push_str("\x1b[35m"); out.push_str(kw);
out.push_str("\x1b[0m");
i += kw.len();
matched_bool = true;
break;
}
}
if matched_bool {
continue;
}
let kw_matched = Self::try_keyword(&chars, i, len, lang);
if let Some(kw_len) = kw_matched {
out.push_str("\x1b[34m"); if let Some(slice) = chars.get(i..i + kw_len) {
for &ch in slice {
out.push(ch);
}
}
out.push_str("\x1b[0m");
i += kw_len;
continue;
}
out.push(c);
i += 1;
}
}
fn try_keyword(chars: &[char], i: usize, len: usize, lang: CodeLang) -> Option<usize> {
let remaining: String = chars[i..].iter().collect();
for kw in crate::tui::keywords::get_keywords(lang) {
if remaining.starts_with(*kw) {
let kw_len = kw.len();
if i + kw_len >= len
|| !chars[i + kw_len].is_alphanumeric() && chars[i + kw_len] != '_'
{
if i == 0 || !chars[i - 1].is_alphanumeric() && chars[i - 1] != '_' {
return Some(kw_len);
}
}
}
}
None
}
}