datafusion_cli/
highlighter.rs1use std::{
21 borrow::Cow::{self, Borrowed},
22 fmt::Display,
23};
24
25use datafusion::sql::sqlparser::{
26 dialect::{dialect_from_str, Dialect, GenericDialect},
27 keywords::Keyword,
28 tokenizer::{Token, Tokenizer},
29};
30use datafusion_common::config;
31use rustyline::highlight::{CmdKind, Highlighter};
32
33#[derive(Debug)]
35pub struct SyntaxHighlighter {
36 dialect: Box<dyn Dialect>,
37}
38
39impl SyntaxHighlighter {
40 pub fn new(dialect: &config::Dialect) -> Self {
41 let dialect = dialect_from_str(dialect).unwrap_or(Box::new(GenericDialect {}));
42 Self { dialect }
43 }
44}
45
46pub struct NoSyntaxHighlighter {}
47
48impl Highlighter for NoSyntaxHighlighter {}
49
50impl Highlighter for SyntaxHighlighter {
51 fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
52 let mut out_line = String::new();
53
54 let mut tokenizer =
56 Tokenizer::new(self.dialect.as_ref(), line).with_unescape(false);
57 let tokens = tokenizer.tokenize();
58 match tokens {
59 Ok(tokens) => {
60 for token in tokens.iter() {
61 match token {
62 Token::Word(w) if w.keyword != Keyword::NoKeyword => {
63 out_line.push_str(&Color::red(token));
64 }
65 Token::SingleQuotedString(_) => {
66 out_line.push_str(&Color::green(token));
67 }
68 other => out_line.push_str(&format!("{other}")),
69 }
70 }
71 out_line.into()
72 }
73 Err(_) => Borrowed(line),
74 }
75 }
76
77 fn highlight_char(&self, line: &str, _pos: usize, _cmd: CmdKind) -> bool {
78 !line.is_empty()
79 }
80}
81
82struct Color {}
84
85impl Color {
86 fn green(s: impl Display) -> String {
87 format!("\x1b[92m{s}\x1b[0m")
88 }
89
90 fn red(s: impl Display) -> String {
91 format!("\x1b[91m{s}\x1b[0m")
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::config::Dialect;
98 use super::SyntaxHighlighter;
99 use rustyline::highlight::Highlighter;
100
101 #[test]
102 fn highlighter_valid() {
103 let s = "SElect col_a from tab_1;";
104 let highlighter = SyntaxHighlighter::new(&Dialect::Generic);
105 let out = highlighter.highlight(s, s.len());
106 assert_eq!(
107 "\u{1b}[91mSElect\u{1b}[0m col_a \u{1b}[91mfrom\u{1b}[0m tab_1;",
108 out
109 );
110 }
111
112 #[test]
113 fn highlighter_valid_with_new_line() {
114 let s = "SElect col_a from tab_1\n WHERE col_b = 'なにか';";
115 let highlighter = SyntaxHighlighter::new(&Dialect::Generic);
116 let out = highlighter.highlight(s, s.len());
117 assert_eq!(
118 "\u{1b}[91mSElect\u{1b}[0m col_a \u{1b}[91mfrom\u{1b}[0m tab_1\n \u{1b}[91mWHERE\u{1b}[0m col_b = \u{1b}[92m'なにか'\u{1b}[0m;",
119 out
120 );
121 }
122
123 #[test]
124 fn highlighter_invalid() {
125 let s = "SElect col_a from tab_1 WHERE col_b = ';";
126 let highlighter = SyntaxHighlighter::new(&Dialect::Generic);
127 let out = highlighter.highlight(s, s.len());
128 assert_eq!("SElect col_a from tab_1 WHERE col_b = ';", out);
129 }
130}