1use ratatui::style::{Color, Modifier, Style};
2use ratatui::text::{Line, Span};
3use syntect::easy::HighlightLines;
4use syntect::highlighting::{Style as SyntectStyle, ThemeSet};
5use syntect::parsing::SyntaxSet;
6use syntect::util::LinesWithEndings;
7
8pub struct SqlHighlighter {
9 }
11
12impl Clone for SqlHighlighter {
13 fn clone(&self) -> Self {
14 SqlHighlighter {}
15 }
16}
17
18impl SqlHighlighter {
19 pub fn new() -> Self {
20 Self {}
21 }
22
23 pub fn highlight_sql_line(&self, text: &str) -> Line<'static> {
24 let syntax_set = SyntaxSet::load_defaults_newlines();
26 let theme_set = ThemeSet::load_defaults();
27
28 let syntax = syntax_set
30 .find_syntax_by_extension("sql")
31 .unwrap_or_else(|| syntax_set.find_syntax_plain_text());
32
33 let theme = &theme_set.themes["base16-ocean.dark"];
35
36 let mut highlighter = HighlightLines::new(syntax, theme);
37
38 let mut spans = Vec::new();
39
40 if let Ok(ranges) = highlighter.highlight_line(text, &syntax_set) {
42 for (style, text_piece) in ranges {
43 let ratatui_style = self.syntect_to_ratatui_style(style);
44 spans.push(Span::styled(text_piece.to_string(), ratatui_style));
45 }
46 } else {
47 spans.push(Span::raw(text.to_string()));
49 }
50
51 Line::from(spans)
52 }
53
54 pub fn highlight_sql_multiline(&self, text: &str) -> Vec<Line<'static>> {
55 let syntax_set = SyntaxSet::load_defaults_newlines();
56 let theme_set = ThemeSet::load_defaults();
57
58 let syntax = syntax_set
59 .find_syntax_by_extension("sql")
60 .unwrap_or_else(|| syntax_set.find_syntax_plain_text());
61
62 let theme = &theme_set.themes["base16-ocean.dark"];
63 let mut highlighter = HighlightLines::new(syntax, theme);
64
65 let mut lines = Vec::new();
66
67 for line in LinesWithEndings::from(text) {
68 let mut spans = Vec::new();
69
70 if let Ok(ranges) = highlighter.highlight_line(line, &syntax_set) {
71 for (style, text_piece) in ranges {
72 let ratatui_style = self.syntect_to_ratatui_style(style);
73 spans.push(Span::styled(text_piece.to_string(), ratatui_style));
74 }
75 } else {
76 spans.push(Span::raw(line.to_string()));
77 }
78
79 lines.push(Line::from(spans));
80 }
81
82 lines
83 }
84
85 fn syntect_to_ratatui_style(&self, syntect_style: SyntectStyle) -> Style {
86 let mut style = Style::default();
87
88 let fg_color = syntect_style.foreground;
90 let ratatui_color = Color::Rgb(fg_color.r, fg_color.g, fg_color.b);
91 style = style.fg(ratatui_color);
92
93 style
98 }
99
100 pub fn simple_sql_highlight(&self, text: &str) -> Line<'static> {
102 let keywords = [
103 "SELECT", "FROM", "WHERE", "AND", "OR", "IN", "ORDER", "BY", "ASC", "DESC", "INSERT",
104 "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "TABLE", "INDEX", "GROUP", "HAVING",
105 "LIMIT", "OFFSET", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "NULL", "NOT", "IS",
106 "LIKE", "BETWEEN", "EXISTS", "DISTINCT", "AS", "ON",
107 ];
108
109 let linq_methods = [
110 "Contains",
111 "StartsWith",
112 "EndsWith",
113 "Length",
114 "ToUpper",
115 "ToLower",
116 "IsNullOrEmpty",
117 "Trim",
118 "Substring",
119 "IndexOf",
120 "Replace",
121 ];
122
123 let operators = ["=", "!=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/"];
124 let string_delimiters = ["'", "\""];
125
126 let paren_colors = [
128 Color::Yellow,
129 Color::Cyan,
130 Color::Magenta,
131 Color::Green,
132 Color::Blue,
133 Color::Red,
134 ];
135
136 let mut spans = Vec::new();
137 let mut current_word = String::new();
138 let mut in_string = false;
139 let mut string_delimiter = '\0';
140 let mut paren_depth = 0;
141
142 let chars: Vec<char> = text.chars().collect();
143 let mut i = 0;
144
145 while i < chars.len() {
146 let ch = chars[i];
147 if in_string {
148 current_word.push(ch);
149 if ch == string_delimiter {
150 spans.push(Span::styled(
152 current_word.clone(),
153 Style::default().fg(Color::Green),
154 ));
155 current_word.clear();
156 in_string = false;
157 }
158 i += 1;
159 continue;
160 }
161
162 if string_delimiters.contains(&ch.to_string().as_str()) {
163 if !current_word.is_empty() {
165 self.push_word_span(
166 &mut spans,
167 ¤t_word,
168 &keywords,
169 &operators,
170 &linq_methods,
171 );
172 current_word.clear();
173 }
174 current_word.push(ch);
175 in_string = true;
176 string_delimiter = ch;
177 i += 1;
178 continue;
179 }
180
181 if ch.is_alphabetic() || ch == '_' || (ch.is_numeric() && !current_word.is_empty()) {
182 current_word.push(ch);
183 } else if ch == '.' && !current_word.is_empty() {
184 let mut j = i + 1;
186 let mut method_name = String::new();
187
188 while j < chars.len() && (chars[j].is_alphabetic() || chars[j] == '_') {
190 method_name.push(chars[j]);
191 j += 1;
192 }
193
194 if linq_methods.contains(&method_name.as_str()) {
195 spans.push(Span::raw(current_word.clone())); spans.push(Span::styled(".", Style::default().fg(Color::Magenta))); spans.push(Span::styled(
199 method_name.clone(),
200 Style::default()
201 .fg(Color::Magenta)
202 .add_modifier(Modifier::BOLD),
203 )); current_word.clear();
205 i = j - 1; } else {
207 self.push_word_span(
209 &mut spans,
210 ¤t_word,
211 &keywords,
212 &operators,
213 &linq_methods,
214 );
215 current_word.clear();
216 spans.push(Span::raw("."));
217 }
218 } else {
219 if !current_word.is_empty() {
221 self.push_word_span(
222 &mut spans,
223 ¤t_word,
224 &keywords,
225 &operators,
226 &linq_methods,
227 );
228 current_word.clear();
229 }
230
231 if operators.contains(&ch.to_string().as_str()) {
233 spans.push(Span::styled(
234 ch.to_string(),
235 Style::default().fg(Color::Cyan),
236 ));
237 } else if ch == '(' {
238 let color = paren_colors[paren_depth % paren_colors.len()];
239 spans.push(Span::styled(
240 ch.to_string(),
241 Style::default().fg(color).add_modifier(Modifier::BOLD),
242 ));
243 paren_depth += 1;
244 } else if ch == ')' {
245 if paren_depth > 0 {
246 paren_depth -= 1;
247 }
248 let color = paren_colors[paren_depth % paren_colors.len()];
249 spans.push(Span::styled(
250 ch.to_string(),
251 Style::default().fg(color).add_modifier(Modifier::BOLD),
252 ));
253 } else {
254 spans.push(Span::raw(ch.to_string()));
255 }
256 }
257
258 i += 1;
259 }
260
261 if !current_word.is_empty() {
263 if in_string {
264 spans.push(Span::styled(
265 current_word,
266 Style::default().fg(Color::Green),
267 ));
268 } else {
269 self.push_word_span(
270 &mut spans,
271 ¤t_word,
272 &keywords,
273 &operators,
274 &linq_methods,
275 );
276 }
277 }
278
279 Line::from(spans)
280 }
281
282 fn push_word_span(
283 &self,
284 spans: &mut Vec<Span<'static>>,
285 word: &str,
286 keywords: &[&str],
287 operators: &[&str],
288 linq_methods: &[&str],
289 ) {
290 let upper_word = word.to_uppercase();
291
292 if keywords.contains(&upper_word.as_str()) {
293 spans.push(Span::styled(
295 word.to_string(),
296 Style::default().fg(Color::Blue),
297 ));
298 } else if linq_methods.contains(&word) {
299 spans.push(Span::styled(
301 word.to_string(),
302 Style::default()
303 .fg(Color::Magenta)
304 .add_modifier(Modifier::BOLD),
305 ));
306 } else if operators.contains(&word) {
307 spans.push(Span::styled(
309 word.to_string(),
310 Style::default().fg(Color::Cyan),
311 ));
312 } else if word.chars().all(|c| c.is_numeric() || c == '.') {
313 spans.push(Span::styled(
315 word.to_string(),
316 Style::default().fg(Color::Magenta),
317 ));
318 } else {
319 spans.push(Span::raw(word.to_string()));
321 }
322 }
323}