use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use syntect::easy::HighlightLines;
use syntect::highlighting::{Style as SyntectStyle, ThemeSet};
use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings;
pub struct SqlHighlighter {
}
impl Clone for SqlHighlighter {
fn clone(&self) -> Self {
SqlHighlighter {}
}
}
impl Default for SqlHighlighter {
fn default() -> Self {
Self::new()
}
}
impl SqlHighlighter {
#[must_use]
pub fn new() -> Self {
Self {}
}
#[must_use]
pub fn highlight_sql_line(&self, text: &str) -> Line<'static> {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults();
let syntax = syntax_set
.find_syntax_by_extension("sql")
.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
let theme = &theme_set.themes["base16-ocean.dark"];
let mut highlighter = HighlightLines::new(syntax, theme);
let mut spans = Vec::new();
if let Ok(ranges) = highlighter.highlight_line(text, &syntax_set) {
for (style, text_piece) in ranges {
let ratatui_style = self.syntect_to_ratatui_style(style);
spans.push(Span::styled(text_piece.to_string(), ratatui_style));
}
} else {
spans.push(Span::raw(text.to_string()));
}
Line::from(spans)
}
#[must_use]
pub fn highlight_sql_multiline(&self, text: &str) -> Vec<Line<'static>> {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults();
let syntax = syntax_set
.find_syntax_by_extension("sql")
.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
let theme = &theme_set.themes["base16-ocean.dark"];
let mut highlighter = HighlightLines::new(syntax, theme);
let mut lines = Vec::new();
for line in LinesWithEndings::from(text) {
let mut spans = Vec::new();
if let Ok(ranges) = highlighter.highlight_line(line, &syntax_set) {
for (style, text_piece) in ranges {
let ratatui_style = self.syntect_to_ratatui_style(style);
spans.push(Span::styled(text_piece.to_string(), ratatui_style));
}
} else {
spans.push(Span::raw(line.to_string()));
}
lines.push(Line::from(spans));
}
lines
}
fn syntect_to_ratatui_style(&self, syntect_style: SyntectStyle) -> Style {
let mut style = Style::default();
let fg_color = syntect_style.foreground;
let ratatui_color = Color::Rgb(fg_color.r, fg_color.g, fg_color.b);
style = style.fg(ratatui_color);
style
}
#[must_use]
pub fn simple_sql_highlight(&self, text: &str) -> Line<'static> {
let keywords = [
"SELECT", "FROM", "WHERE", "AND", "OR", "IN", "ORDER", "BY", "ASC", "DESC", "INSERT",
"UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "TABLE", "INDEX", "GROUP", "HAVING",
"LIMIT", "OFFSET", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "NULL", "NOT", "IS",
"LIKE", "BETWEEN", "EXISTS", "DISTINCT", "AS", "ON",
];
let linq_methods = [
"Contains",
"StartsWith",
"EndsWith",
"Length",
"ToUpper",
"ToLower",
"IsNullOrEmpty",
"Trim",
"Substring",
"IndexOf",
"Replace",
];
let operators = ["=", "!=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/"];
let string_delimiters = ["'", "\""];
let paren_colors = [
Color::Yellow,
Color::Cyan,
Color::Magenta,
Color::Green,
Color::Blue,
Color::Red,
];
let mut spans = Vec::new();
let mut current_word = String::new();
let mut in_string = false;
let mut string_delimiter = '\0';
let mut paren_depth = 0;
let chars: Vec<char> = text.chars().collect();
let mut i = 0;
while i < chars.len() {
let ch = chars[i];
if in_string {
current_word.push(ch);
if ch == string_delimiter {
spans.push(Span::styled(
current_word.clone(),
Style::default().fg(Color::Green),
));
current_word.clear();
in_string = false;
}
i += 1;
continue;
}
if string_delimiters.contains(&ch.to_string().as_str()) {
if !current_word.is_empty() {
self.push_word_span(
&mut spans,
¤t_word,
&keywords,
&operators,
&linq_methods,
);
current_word.clear();
}
current_word.push(ch);
in_string = true;
string_delimiter = ch;
i += 1;
continue;
}
if ch.is_alphabetic() || ch == '_' || (ch.is_numeric() && !current_word.is_empty()) {
current_word.push(ch);
} else if ch == '.' && !current_word.is_empty() {
let mut j = i + 1;
let mut method_name = String::new();
while j < chars.len() && (chars[j].is_alphabetic() || chars[j] == '_') {
method_name.push(chars[j]);
j += 1;
}
if linq_methods.contains(&method_name.as_str()) {
spans.push(Span::raw(current_word.clone())); spans.push(Span::styled(".", Style::default().fg(Color::Magenta))); spans.push(Span::styled(
method_name.clone(),
Style::default()
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
)); current_word.clear();
i = j - 1; } else {
self.push_word_span(
&mut spans,
¤t_word,
&keywords,
&operators,
&linq_methods,
);
current_word.clear();
spans.push(Span::raw("."));
}
} else {
if !current_word.is_empty() {
self.push_word_span(
&mut spans,
¤t_word,
&keywords,
&operators,
&linq_methods,
);
current_word.clear();
}
if operators.contains(&ch.to_string().as_str()) {
spans.push(Span::styled(
ch.to_string(),
Style::default().fg(Color::Cyan),
));
} else if ch == '(' {
let color = paren_colors[paren_depth % paren_colors.len()];
spans.push(Span::styled(
ch.to_string(),
Style::default().fg(color).add_modifier(Modifier::BOLD),
));
paren_depth += 1;
} else if ch == ')' {
paren_depth = paren_depth.saturating_sub(1);
let color = paren_colors[paren_depth % paren_colors.len()];
spans.push(Span::styled(
ch.to_string(),
Style::default().fg(color).add_modifier(Modifier::BOLD),
));
} else {
spans.push(Span::raw(ch.to_string()));
}
}
i += 1;
}
if !current_word.is_empty() {
if in_string {
spans.push(Span::styled(
current_word,
Style::default().fg(Color::Green),
));
} else {
self.push_word_span(
&mut spans,
¤t_word,
&keywords,
&operators,
&linq_methods,
);
}
}
Line::from(spans)
}
fn push_word_span(
&self,
spans: &mut Vec<Span<'static>>,
word: &str,
keywords: &[&str],
operators: &[&str],
linq_methods: &[&str],
) {
let upper_word = word.to_uppercase();
if keywords.contains(&upper_word.as_str()) {
spans.push(Span::styled(
word.to_string(),
Style::default().fg(Color::Blue),
));
} else if linq_methods.contains(&word) {
spans.push(Span::styled(
word.to_string(),
Style::default()
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
));
} else if operators.contains(&word) {
spans.push(Span::styled(
word.to_string(),
Style::default().fg(Color::Cyan),
));
} else if word.chars().all(|c| c.is_numeric() || c == '.') {
spans.push(Span::styled(
word.to_string(),
Style::default().fg(Color::Magenta),
));
} else {
spans.push(Span::raw(word.to_string()));
}
}
}