use ratatui::style::{Color, Modifier, Style};
use ratatui::text::Span;
use vimltui::SyntaxHighlighter;
const SQL_KEYWORDS: &[&str] = &[
"SELECT", "FROM", "WHERE", "INSERT", "INTO", "UPDATE", "DELETE", "SET",
"JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "FULL", "CROSS", "ON",
"AND", "OR", "NOT", "IN", "IS", "NULL", "LIKE", "BETWEEN", "EXISTS",
"AS", "ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET", "DISTINCT",
"UNION", "ALL", "CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW",
"BEGIN", "END", "COMMIT", "ROLLBACK", "DECLARE", "CURSOR", "FETCH",
"CASE", "WHEN", "THEN", "ELSE", "ASC", "DESC", "COUNT", "SUM", "AVG",
"MAX", "MIN", "PROCEDURE", "FUNCTION", "PACKAGE", "BODY", "REPLACE",
"VALUES", "WITH", "RECURSIVE", "TRIGGER", "GRANT", "REVOKE",
"TYPE", "RETURN", "IF", "ELSIF", "LOOP", "FOR", "WHILE", "EXIT",
"EXCEPTION", "RAISE", "PRAGMA", "EXECUTE", "IMMEDIATE", "BULK",
"COLLECT", "FORALL", "OPEN", "CLOSE", "DBMS_OUTPUT", "PUT_LINE",
];
pub struct SqlHighlighter {
pub keyword: Color,
pub string: Color,
pub number: Color,
pub comment: Color,
pub operator: Color,
}
impl SqlHighlighter {
pub fn from_theme(theme: &crate::ui::theme::Theme) -> Self {
Self {
keyword: theme.sql_keyword,
string: theme.sql_string,
number: theme.sql_number,
comment: theme.sql_comment,
operator: theme.sql_operator,
}
}
}
impl SyntaxHighlighter for SqlHighlighter {
fn highlight_line<'a>(&self, line: &'a str, spans: &mut Vec<Span<'a>>) {
if line.is_empty() {
return;
}
if let Some(comment_pos) = line.find("--") {
if comment_pos > 0 {
self.highlight_tokens(&line[..comment_pos], spans);
}
spans.push(Span::styled(
&line[comment_pos..],
Style::default()
.fg(self.comment)
.add_modifier(Modifier::ITALIC),
));
return;
}
self.highlight_tokens(line, spans);
}
fn highlight_segment<'a>(&self, text: &'a str, spans: &mut Vec<Span<'a>>) {
self.highlight_line(text, spans);
}
}
impl SqlHighlighter {
fn highlight_tokens<'a>(&self, text: &'a str, spans: &mut Vec<Span<'a>>) {
let mut remaining = text;
while !remaining.is_empty() {
if remaining.starts_with(|c: char| c.is_whitespace()) {
let ws_end = remaining
.find(|c: char| !c.is_whitespace())
.unwrap_or(remaining.len());
spans.push(Span::raw(&remaining[..ws_end]));
remaining = &remaining[ws_end..];
continue;
}
if remaining.starts_with('\'') {
let end = remaining[1..]
.find('\'')
.map(|p| p + 2)
.unwrap_or(remaining.len());
spans.push(Span::styled(
&remaining[..end],
Style::default().fg(self.string),
));
remaining = &remaining[end..];
continue;
}
if remaining.starts_with(|c: char| c.is_ascii_digit()) {
let end = remaining
.find(|c: char| !c.is_ascii_digit() && c != '.')
.unwrap_or(remaining.len());
spans.push(Span::styled(
&remaining[..end],
Style::default().fg(self.number),
));
remaining = &remaining[end..];
continue;
}
if remaining.starts_with(|c: char| c.is_alphanumeric() || c == '_') {
let end = remaining
.find(|c: char| !c.is_alphanumeric() && c != '_')
.unwrap_or(remaining.len());
let word = &remaining[..end];
let upper = word.to_uppercase();
if SQL_KEYWORDS.contains(&upper.as_str()) {
spans.push(Span::styled(
word,
Style::default()
.fg(self.keyword)
.add_modifier(Modifier::BOLD),
));
} else {
spans.push(Span::raw(word));
}
remaining = &remaining[end..];
continue;
}
let end = remaining
.find(|c: char| c.is_alphanumeric() || c == '_' || c == '\'' || c.is_whitespace())
.unwrap_or(remaining.len())
.max(1);
spans.push(Span::styled(
&remaining[..end],
Style::default().fg(self.operator),
));
remaining = &remaining[end..];
}
}
}