neumann_shell/input/
mod.rs1mod completer;
5mod highlighter;
6mod validator;
7
8pub use completer::NeumannCompleter;
9pub use highlighter::NeumannHighlighter;
10pub use validator::NeumannValidator;
11
12use crate::style::Theme;
13use rustyline::completion::{Completer, Pair};
14use rustyline::highlight::{CmdKind, Highlighter};
15use rustyline::hint::Hinter;
16use rustyline::validate::{ValidationContext, ValidationResult, Validator};
17use rustyline::Helper;
18
19pub struct NeumannHelper {
21 completer: NeumannCompleter,
22 highlighter: NeumannHighlighter,
23 validator: NeumannValidator,
24}
25
26impl NeumannHelper {
27 #[must_use]
29 pub fn new(theme: Theme) -> Self {
30 Self {
31 completer: NeumannCompleter::new(),
32 highlighter: NeumannHighlighter::new(theme),
33 validator: NeumannValidator::new(),
34 }
35 }
36
37 pub fn set_tables(&mut self, tables: Vec<String>) {
39 self.completer.set_tables(tables);
40 }
41}
42
43impl Default for NeumannHelper {
44 fn default() -> Self {
45 Self::new(Theme::auto())
46 }
47}
48
49impl Helper for NeumannHelper {}
50
51impl Completer for NeumannHelper {
52 type Candidate = Pair;
53
54 fn complete(
55 &self,
56 line: &str,
57 pos: usize,
58 ctx: &rustyline::Context<'_>,
59 ) -> rustyline::Result<(usize, Vec<Pair>)> {
60 self.completer.complete(line, pos, ctx)
61 }
62}
63
64impl Highlighter for NeumannHelper {
65 fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
66 self.highlighter.highlight(line, pos)
67 }
68
69 fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
70 self.highlighter.highlight_char(line, pos, kind)
71 }
72
73 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
74 &'s self,
75 prompt: &'p str,
76 default: bool,
77 ) -> std::borrow::Cow<'b, str> {
78 self.highlighter.highlight_prompt(prompt, default)
79 }
80
81 fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
82 self.highlighter.highlight_hint(hint)
83 }
84
85 fn highlight_candidate<'c>(
86 &self,
87 candidate: &'c str,
88 completion: rustyline::CompletionType,
89 ) -> std::borrow::Cow<'c, str> {
90 self.highlighter.highlight_candidate(candidate, completion)
91 }
92}
93
94impl Hinter for NeumannHelper {
95 type Hint = String;
96
97 fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> {
98 None
99 }
100}
101
102impl Validator for NeumannHelper {
103 fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result<ValidationResult> {
104 self.validator.validate(ctx)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_helper_creation() {
114 let _helper = NeumannHelper::new(Theme::plain());
115 }
116
117 #[test]
118 fn test_helper_default() {
119 let _helper = NeumannHelper::default();
120 }
121
122 #[test]
123 fn test_helper_set_tables() {
124 let mut helper = NeumannHelper::new(Theme::plain());
125 helper.set_tables(vec!["users".to_string(), "orders".to_string()]);
126 }
127
128 #[test]
129 fn test_helper_completer_trait() {
130 let helper = NeumannHelper::new(Theme::plain());
131 let history = rustyline::history::DefaultHistory::new();
132 let ctx = rustyline::Context::new(&history);
133 let result = helper.complete("SELECT ", 7, &ctx);
134 assert!(result.is_ok());
135 let (start, completions) = result.unwrap();
136 assert_eq!(start, 7);
137 assert!(!completions.is_empty());
138 }
139
140 #[test]
141 fn test_helper_highlighter_trait() {
142 let helper = NeumannHelper::new(Theme::plain());
143
144 let highlighted = helper.highlight("SELECT * FROM users", 0);
146 assert!(!highlighted.is_empty());
147
148 let needs_highlight = helper.highlight_char("SELECT", 0, CmdKind::MoveCursor);
150 let _ = needs_highlight;
152
153 let prompt = helper.highlight_prompt("neumann> ", false);
155 assert!(!prompt.is_empty());
156
157 let hint = helper.highlight_hint("users");
159 assert!(!hint.is_empty());
160
161 let candidate = helper.highlight_candidate("SELECT", rustyline::CompletionType::List);
163 assert!(!candidate.is_empty());
164 }
165
166 #[test]
167 fn test_helper_hinter_trait() {
168 let helper = NeumannHelper::new(Theme::plain());
169 let history = rustyline::history::DefaultHistory::new();
170 let ctx = rustyline::Context::new(&history);
171 let hint = helper.hint("SELECT", 6, &ctx);
172 assert!(hint.is_none()); }
174
175 #[test]
176 fn test_helper_with_different_themes() {
177 let themes = [Theme::plain(), Theme::dark(), Theme::light(), Theme::auto()];
178 for theme in themes {
179 let helper = NeumannHelper::new(theme);
180 let highlighted = helper.highlight("SELECT", 0);
181 assert!(!highlighted.is_empty());
182 }
183 }
184
185 #[test]
186 fn test_helper_complete_partial() {
187 let helper = NeumannHelper::new(Theme::plain());
188 let history = rustyline::history::DefaultHistory::new();
189 let ctx = rustyline::Context::new(&history);
190
191 let result = helper.complete("SEL", 3, &ctx);
193 assert!(result.is_ok());
194 let (start, completions) = result.unwrap();
195 assert_eq!(start, 0);
196 assert!(completions.iter().any(|p| p.display == "SELECT"));
197 }
198
199 #[test]
200 fn test_helper_complete_with_tables() {
201 let mut helper = NeumannHelper::new(Theme::plain());
202 helper.set_tables(vec!["users".to_string(), "orders".to_string()]);
203
204 let history = rustyline::history::DefaultHistory::new();
205 let ctx = rustyline::Context::new(&history);
206 let result = helper.complete("SELECT * FROM ", 14, &ctx);
207 assert!(result.is_ok());
208 let (_, completions) = result.unwrap();
209 assert!(completions.iter().any(|p| p.display == "users"));
210 assert!(completions.iter().any(|p| p.display == "orders"));
211 }
212
213 #[test]
214 fn test_helper_highlight_keywords() {
215 let helper = NeumannHelper::new(Theme::plain());
216
217 let _h1 = helper.highlight("INSERT INTO users VALUES (1)", 0);
219 let _h2 = helper.highlight("UPDATE users SET name = 'x'", 0);
220 let _h3 = helper.highlight("DELETE FROM users WHERE id = 1", 0);
221 let _h4 = helper.highlight("CREATE TABLE test (id INT)", 0);
222 let _h5 = helper.highlight("DROP TABLE test", 0);
223 }
224}