debug/
debug.rs

1use lineeditor::event::LineEditorEvent;
2use lineeditor::keybindings::KeyCombination;
3use lineeditor::style::Style;
4use lineeditor::styled_buffer::StyledBuffer;
5use lineeditor::Color;
6use lineeditor::Completer;
7use lineeditor::DefaultAutoPair;
8use lineeditor::Highlighter;
9use lineeditor::Hinter;
10use lineeditor::KeyModifiers;
11use lineeditor::LineEditor;
12use lineeditor::LineEditorResult;
13use lineeditor::Span;
14use lineeditor::StringPrompt;
15use lineeditor::Suggestion;
16
17const GITQL_RESERVED_KEYWORDS: [&str; 31] = [
18    "set", "select", "distinct", "from", "group", "where", "having", "offset", "limit", "order",
19    "by", "case", "when", "then", "else", "end", "between", "in", "is", "not", "like", "glob",
20    "or", "and", "xor", "true", "false", "null", "as", "asc", "desc",
21];
22
23#[derive(Default)]
24pub struct GitQLHinter {}
25
26impl Hinter for GitQLHinter {
27    fn hint(&self, buffer: &mut StyledBuffer) -> Option<StyledBuffer> {
28        if let Some(keyword) = buffer.last_alphabetic_keyword() {
29            let keyword_lower = keyword.to_lowercase();
30            for word in GITQL_RESERVED_KEYWORDS {
31                if word.starts_with(&keyword_lower) {
32                    let hint = &word[keyword.len()..];
33                    let mut styled_buffer = StyledBuffer::default();
34                    let mut style = Style::default();
35                    style.set_foreground_color(Color::DarkGrey);
36                    styled_buffer.insert_styled_string(hint, style);
37                    return Some(styled_buffer);
38                }
39            }
40        }
41        None
42    }
43}
44
45pub struct FixedCompleter;
46
47impl Completer for FixedCompleter {
48    fn complete(&self, input: &StyledBuffer) -> Vec<Suggestion> {
49        let mut suggestions: Vec<Suggestion> = vec![];
50        if input.position() != input.len() {
51            return suggestions;
52        }
53
54        if let Some(keyword) = input.last_alphabetic_keyword() {
55            for reserved_keyword in GITQL_RESERVED_KEYWORDS {
56                if reserved_keyword.starts_with(&keyword) {
57                    let suggestion = Suggestion {
58                        content: StyledBuffer::from(reserved_keyword),
59                        span: Span {
60                            start: input.len() - keyword.len(),
61                            end: input.len(),
62                        },
63                    };
64                    suggestions.push(suggestion);
65                }
66            }
67        }
68        suggestions
69    }
70}
71
72#[derive(Default)]
73pub struct GitQLHighlighter;
74
75impl Highlighter for GitQLHighlighter {
76    fn highlight(&self, buffer: &mut StyledBuffer) {
77        let lines = buffer.buffer().clone();
78        let mut i: usize = 0;
79
80        let mut keyword_style = Style::default();
81        keyword_style.set_foreground_color(Color::Magenta);
82
83        let mut string_style = Style::default();
84        string_style.set_foreground_color(Color::Yellow);
85
86        loop {
87            if i >= lines.len() {
88                break;
89            }
90
91            // Highlight String literal
92            if lines[i] == '"' {
93                buffer.style_char(i, string_style.clone());
94                i += 1;
95
96                while i < lines.len() && lines[i] != '"' {
97                    buffer.style_char(i, string_style.clone());
98                    i += 1;
99                }
100
101                if i < lines.len() && lines[i] == '"' {
102                    buffer.style_char(i, string_style.clone());
103                    i += 1;
104                }
105
106                continue;
107            }
108
109            // Highlight reserved keyword
110            if lines[i].is_alphabetic() {
111                let start = i;
112                let mut keyword = String::new();
113                while i < lines.len() && (lines[i].is_alphanumeric() || lines[i] == '_') {
114                    keyword.push(lines[i]);
115                    i += 1;
116                }
117
118                keyword = keyword.to_lowercase();
119                if GITQL_RESERVED_KEYWORDS.contains(&keyword.as_str()) {
120                    buffer.style_range(start, i, keyword_style.clone())
121                }
122                continue;
123            }
124
125            i += 1;
126        }
127    }
128}
129
130#[derive(Default)]
131pub struct MatchingBracketsHighlighter;
132
133impl Highlighter for MatchingBracketsHighlighter {
134    fn highlight(&self, buffer: &mut StyledBuffer) {
135        let colors = vec![Color::Red, Color::Blue, Color::Yellow, Color::Green];
136        let mut brackets_stack: Vec<Color> = vec![];
137        let mut current_color_index = 0;
138
139        let lines = buffer.buffer().clone();
140        let mut i: usize = 0;
141        loop {
142            if i >= lines.len() {
143                break;
144            }
145
146            if lines[i] == '"' {
147                i += 1;
148                while i < lines.len() && lines[i] != '"' {
149                    i += 1;
150                }
151
152                if i < lines.len() {
153                    i += 1;
154                }
155                continue;
156            }
157
158            if lines[i] == '(' || lines[i] == '<' || lines[i] == '[' || lines[i] == '{' {
159                if current_color_index >= colors.len() {
160                    current_color_index = 0;
161                }
162
163                let color = colors[current_color_index];
164                current_color_index += 1;
165
166                brackets_stack.push(color);
167
168                let mut style = Style::default();
169                style.set_foreground_color(color);
170                buffer.style_char(i, style);
171                i += 1;
172                continue;
173            }
174
175            if lines[i] == ')' || lines[i] == '>' || lines[i] == ']' || lines[i] == '}' {
176                let color = if brackets_stack.is_empty() {
177                    colors[0]
178                } else {
179                    brackets_stack.pop().unwrap()
180                };
181
182                let mut style = Style::default();
183                style.set_foreground_color(color);
184                buffer.style_char(i, style);
185
186                i += 1;
187                continue;
188            }
189            i += 1;
190        }
191    }
192}
193
194pub fn create_new_line_editor() -> LineEditor {
195    let prompt = StringPrompt::new("gitql > ".to_string());
196    let mut line_editor = LineEditor::new(Box::new(prompt));
197
198    line_editor.add_highlighter(Box::<GitQLHighlighter>::default());
199    line_editor.add_highlighter(Box::<MatchingBracketsHighlighter>::default());
200    line_editor.set_auto_pair(Some(Box::<DefaultAutoPair>::default()));
201    line_editor.add_hinter(Box::<GitQLHinter>::default());
202    line_editor.set_completer(Box::new(FixedCompleter));
203
204    let bindings = line_editor.keybinding();
205    bindings.register_common_control_bindings();
206    bindings.register_common_navigation_bindings();
207    bindings.register_common_edit_bindings();
208    bindings.register_common_selection_bindings();
209    bindings.register_binding(
210        KeyCombination {
211            key_kind: lineeditor::KeyEventKind::Press,
212            modifier: KeyModifiers::NONE,
213            key_code: lineeditor::KeyCode::Tab,
214        },
215        LineEditorEvent::ToggleAutoComplete,
216    );
217
218    line_editor
219}
220
221fn main() {
222    let mut line_editor = create_new_line_editor();
223
224    loop {
225        match line_editor.read_line() {
226            Ok(LineEditorResult::Success(line)) => {
227                println!();
228
229                if line == "exit" {
230                    break;
231                }
232
233                println!("Result {}", line.len());
234            }
235            _ => {}
236        }
237    }
238}