diff options
author | Akshay <[email protected]> | 2021-05-17 12:25:09 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2021-05-17 12:25:09 +0100 |
commit | d2cc31ee49d673f343ce5089071ef3628c3cdc97 (patch) | |
tree | a654f312f0d6b66c52c9dda8882b6e954e97988f /src | |
parent | 09ee8cc84251d1758766dedff9e25497eebb88d8 (diff) |
add tab to complete env variables
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 18 | ||||
-rw-r--r-- | src/command.rs | 136 | ||||
-rw-r--r-- | src/lisp/eval.rs | 28 | ||||
-rw-r--r-- | src/lisp/lex.rs | 1 |
4 files changed, 172 insertions, 11 deletions
@@ -27,7 +27,6 @@ use std::{ | |||
27 | path::{Path, PathBuf}, | 27 | path::{Path, PathBuf}, |
28 | }; | 28 | }; |
29 | 29 | ||
30 | use log::info; | ||
31 | use obi::{CompressionType, Image}; | 30 | use obi::{CompressionType, Image}; |
32 | use sdl2::{ | 31 | use sdl2::{ |
33 | event::Event, | 32 | event::Event, |
@@ -319,10 +318,7 @@ impl<'ctx> AppState<'ctx> { | |||
319 | } | 318 | } |
320 | 319 | ||
321 | pub fn eval_expr(&mut self, expr: &LispExpr) { | 320 | pub fn eval_expr(&mut self, expr: &LispExpr) { |
322 | let mut evaluator = eval::Evaluator { | 321 | let mut evaluator = eval::Evaluator::new(self); |
323 | app: self, | ||
324 | context: Vec::new(), | ||
325 | }; | ||
326 | match evaluator.eval(expr) { | 322 | match evaluator.eval(expr) { |
327 | Ok(val) => self.message.set_info(format!("{}", val)), | 323 | Ok(val) => self.message.set_info(format!("{}", val)), |
328 | Err(eval_err) => self.message.set_error(format!("{}", eval_err)), | 324 | Err(eval_err) => self.message.set_error(format!("{}", eval_err)), |
@@ -745,7 +741,6 @@ impl<'ctx> AppState<'ctx> { | |||
745 | self.mode = Mode::Command; | 741 | self.mode = Mode::Command; |
746 | } | 742 | } |
747 | } | 743 | } |
748 | info!("key press: {:?}", &event); | ||
749 | match self.mode { | 744 | match self.mode { |
750 | Mode::Draw => { | 745 | Mode::Draw => { |
751 | match event { | 746 | match event { |
@@ -1047,6 +1042,7 @@ impl<'ctx> AppState<'ctx> { | |||
1047 | Keycode::Up => self.command_box.hist_prev(), | 1042 | Keycode::Up => self.command_box.hist_prev(), |
1048 | Keycode::Down => self.command_box.hist_next(), | 1043 | Keycode::Down => self.command_box.hist_next(), |
1049 | Keycode::Return => self.eval_command(), | 1044 | Keycode::Return => self.eval_command(), |
1045 | Keycode::Tab => self.command_box.complete_next(&self.lisp_env), | ||
1050 | Keycode::Escape => { | 1046 | Keycode::Escape => { |
1051 | self.command_box.clear(); | 1047 | self.command_box.clear(); |
1052 | self.message.text.clear(); | 1048 | self.message.text.clear(); |
@@ -1062,11 +1058,11 @@ impl<'ctx> AppState<'ctx> { | |||
1062 | _ => (), | 1058 | _ => (), |
1063 | }, | 1059 | }, |
1064 | // how does one handle alt keys | 1060 | // how does one handle alt keys |
1065 | // _ if keymod == Mod::LALTMOD => match k { | 1061 | _ if keymod == Mod::LALTMOD => match k { |
1066 | // Keycode::B => self.command_box.cursor_back_word(), | 1062 | Keycode::B => self.command_box.cursor_back_word(), |
1067 | // Keycode::F => self.command_box.cursor_forward_word(), | 1063 | Keycode::F => self.command_box.cursor_forward_word(), |
1068 | // _ => (), | 1064 | _ => (), |
1069 | // }, | 1065 | }, |
1070 | _ => (), | 1066 | _ => (), |
1071 | }, | 1067 | }, |
1072 | Event::TextInput { text, .. } => { | 1068 | Event::TextInput { text, .. } => { |
diff --git a/src/command.rs b/src/command.rs index 52ff7c8..26aeb05 100644 --- a/src/command.rs +++ b/src/command.rs | |||
@@ -1,9 +1,52 @@ | |||
1 | use crate::lisp::{eval::completions, Environment}; | ||
2 | |||
1 | #[derive(Debug)] | 3 | #[derive(Debug)] |
2 | pub struct CommandBox { | 4 | pub struct CommandBox { |
3 | pub history: History<String>, | 5 | pub history: History<String>, |
4 | pub hist_idx: Option<usize>, | 6 | pub hist_idx: Option<usize>, |
5 | pub text: String, | 7 | pub text: String, |
6 | pub cursor: usize, | 8 | pub cursor: usize, |
9 | pub completions: Option<Completions>, | ||
10 | } | ||
11 | |||
12 | #[derive(Debug)] | ||
13 | pub struct Completions { | ||
14 | pub trigger_idx: usize, | ||
15 | pub candidates: Vec<String>, | ||
16 | pub idx: usize, | ||
17 | } | ||
18 | |||
19 | impl Completions { | ||
20 | pub fn new(trigger_idx: usize, mut candidates: Vec<String>) -> Self { | ||
21 | candidates.insert(0, "".into()); | ||
22 | Self { | ||
23 | trigger_idx, | ||
24 | candidates, | ||
25 | idx: 0, | ||
26 | } | ||
27 | } | ||
28 | pub fn prefix(&self) -> &str { | ||
29 | self.candidates[0].as_str() | ||
30 | } | ||
31 | pub fn next(&mut self) -> &str { | ||
32 | if self.idx + 1 == self.candidates.len() { | ||
33 | self.idx = 0; | ||
34 | } else { | ||
35 | self.idx += 1; | ||
36 | } | ||
37 | self.get() | ||
38 | } | ||
39 | pub fn prev(&mut self) -> &str { | ||
40 | if self.idx == 0 { | ||
41 | self.idx = self.candidates.len() - 1; | ||
42 | } else { | ||
43 | self.idx -= 1; | ||
44 | } | ||
45 | self.get() | ||
46 | } | ||
47 | fn get(&self) -> &str { | ||
48 | &self.candidates[self.idx] | ||
49 | } | ||
7 | } | 50 | } |
8 | 51 | ||
9 | impl CommandBox { | 52 | impl CommandBox { |
@@ -13,6 +56,13 @@ impl CommandBox { | |||
13 | hist_idx: None, | 56 | hist_idx: None, |
14 | text: String::new(), | 57 | text: String::new(), |
15 | cursor: 0, | 58 | cursor: 0, |
59 | completions: None, | ||
60 | } | ||
61 | } | ||
62 | |||
63 | pub fn invalidate_completions(&mut self) { | ||
64 | if self.completions.is_some() { | ||
65 | self.completions = None; | ||
16 | } | 66 | } |
17 | } | 67 | } |
18 | 68 | ||
@@ -20,14 +70,17 @@ impl CommandBox { | |||
20 | if self.cursor < self.text.len() { | 70 | if self.cursor < self.text.len() { |
21 | self.cursor += 1; | 71 | self.cursor += 1; |
22 | } | 72 | } |
73 | self.invalidate_completions(); | ||
23 | } | 74 | } |
24 | 75 | ||
25 | pub fn cursor_end(&mut self) { | 76 | pub fn cursor_end(&mut self) { |
26 | self.cursor = self.text.len(); | 77 | self.cursor = self.text.len(); |
78 | self.invalidate_completions(); | ||
27 | } | 79 | } |
28 | 80 | ||
29 | pub fn cursor_start(&mut self) { | 81 | pub fn cursor_start(&mut self) { |
30 | self.cursor = 0; | 82 | self.cursor = 0; |
83 | self.invalidate_completions(); | ||
31 | } | 84 | } |
32 | 85 | ||
33 | pub fn cursor_back_word(&mut self) { | 86 | pub fn cursor_back_word(&mut self) { |
@@ -40,6 +93,7 @@ impl CommandBox { | |||
40 | } | 93 | } |
41 | } | 94 | } |
42 | self.cursor = prev_word_idx; | 95 | self.cursor = prev_word_idx; |
96 | self.invalidate_completions(); | ||
43 | } | 97 | } |
44 | 98 | ||
45 | pub fn cursor_forward_word(&mut self) { | 99 | pub fn cursor_forward_word(&mut self) { |
@@ -54,10 +108,26 @@ impl CommandBox { | |||
54 | } | 108 | } |
55 | } | 109 | } |
56 | self.cursor = next_word_idx; | 110 | self.cursor = next_word_idx; |
111 | self.invalidate_completions(); | ||
112 | } | ||
113 | |||
114 | // returns the word prefix if completable | ||
115 | pub fn completable_word(&mut self) -> Option<&str> { | ||
116 | // text behind the cursor | ||
117 | let sl = &self.text[0..self.cursor]; | ||
118 | // index of last non-completable character | ||
119 | let idx = sl.rfind(|c: char| !c.is_alphanumeric() && !"!$%&*+-./<=>?^_|#".contains(c)); | ||
120 | // i+1 to cursor contains completable word | ||
121 | match idx { | ||
122 | Some(i) if i == self.cursor - 1 => None, | ||
123 | Some(i) => Some(&self.text[i + 1..self.cursor]), | ||
124 | _ => None, | ||
125 | } | ||
57 | } | 126 | } |
58 | 127 | ||
59 | pub fn backward(&mut self) { | 128 | pub fn backward(&mut self) { |
60 | self.cursor = self.cursor.saturating_sub(1); | 129 | self.cursor = self.cursor.saturating_sub(1); |
130 | self.invalidate_completions(); | ||
61 | } | 131 | } |
62 | 132 | ||
63 | pub fn backspace(&mut self) { | 133 | pub fn backspace(&mut self) { |
@@ -65,26 +135,38 @@ impl CommandBox { | |||
65 | self.text.remove(self.cursor - 1); | 135 | self.text.remove(self.cursor - 1); |
66 | self.backward(); | 136 | self.backward(); |
67 | } | 137 | } |
138 | self.invalidate_completions(); | ||
68 | } | 139 | } |
69 | 140 | ||
70 | pub fn delete(&mut self) { | 141 | pub fn delete(&mut self) { |
71 | if self.cursor < self.text.len() { | 142 | if self.cursor < self.text.len() { |
72 | self.text.remove(self.cursor); | 143 | self.text.remove(self.cursor); |
73 | } | 144 | } |
145 | self.invalidate_completions(); | ||
146 | } | ||
147 | |||
148 | pub fn delete_n(&mut self, n: usize) { | ||
149 | for _ in 0..n { | ||
150 | self.delete() | ||
151 | } | ||
152 | self.invalidate_completions(); | ||
74 | } | 153 | } |
75 | 154 | ||
76 | pub fn delete_to_end(&mut self) { | 155 | pub fn delete_to_end(&mut self) { |
77 | self.text.truncate(self.cursor); | 156 | self.text.truncate(self.cursor); |
157 | self.invalidate_completions(); | ||
78 | } | 158 | } |
79 | 159 | ||
80 | pub fn delete_to_start(&mut self) { | 160 | pub fn delete_to_start(&mut self) { |
81 | self.text = self.text.chars().skip(self.cursor).collect(); | 161 | self.text = self.text.chars().skip(self.cursor).collect(); |
82 | self.cursor = 0; | 162 | self.cursor = 0; |
163 | self.invalidate_completions(); | ||
83 | } | 164 | } |
84 | 165 | ||
85 | pub fn push_str(&mut self, v: &str) { | 166 | pub fn push_str(&mut self, v: &str) { |
86 | self.text.insert_str(self.cursor, v); | 167 | self.text.insert_str(self.cursor, v); |
87 | self.cursor += v.len(); | 168 | self.cursor += v.len(); |
169 | self.invalidate_completions(); | ||
88 | } | 170 | } |
89 | 171 | ||
90 | pub fn is_empty(&self) -> bool { | 172 | pub fn is_empty(&self) -> bool { |
@@ -94,11 +176,14 @@ impl CommandBox { | |||
94 | pub fn clear(&mut self) { | 176 | pub fn clear(&mut self) { |
95 | self.text.clear(); | 177 | self.text.clear(); |
96 | self.cursor = 0; | 178 | self.cursor = 0; |
179 | self.hist_idx = None; | ||
180 | self.invalidate_completions(); | ||
97 | } | 181 | } |
98 | 182 | ||
99 | pub fn hist_append(&mut self) { | 183 | pub fn hist_append(&mut self) { |
100 | self.history.append(self.text.drain(..).collect()); | 184 | self.history.append(self.text.drain(..).collect()); |
101 | self.cursor_start(); | 185 | self.cursor_start(); |
186 | self.invalidate_completions(); | ||
102 | } | 187 | } |
103 | 188 | ||
104 | fn get_from_hist(&self) -> String { | 189 | fn get_from_hist(&self) -> String { |
@@ -121,6 +206,7 @@ impl CommandBox { | |||
121 | self.text = self.get_from_hist(); | 206 | self.text = self.get_from_hist(); |
122 | self.cursor_end(); | 207 | self.cursor_end(); |
123 | } | 208 | } |
209 | self.invalidate_completions(); | ||
124 | } | 210 | } |
125 | 211 | ||
126 | pub fn hist_next(&mut self) { | 212 | pub fn hist_next(&mut self) { |
@@ -135,6 +221,36 @@ impl CommandBox { | |||
135 | } | 221 | } |
136 | self.cursor_end(); | 222 | self.cursor_end(); |
137 | } | 223 | } |
224 | self.invalidate_completions(); | ||
225 | } | ||
226 | |||
227 | pub fn complete_next(&mut self, env_list: &[Environment]) { | ||
228 | let c = self.cursor; | ||
229 | // completions exist, fill with next completion | ||
230 | if let Some(cs) = &mut self.completions { | ||
231 | self.cursor = cs.trigger_idx; | ||
232 | let prev_len = cs.get().len(); | ||
233 | // skips over the first empty completion | ||
234 | let new_insertion = cs.next(); | ||
235 | self.text = format!( | ||
236 | "{}{}{}", | ||
237 | &self.text[..self.cursor], | ||
238 | new_insertion, | ||
239 | &self.text[self.cursor + prev_len..] | ||
240 | ); | ||
241 | self.cursor += new_insertion.len(); | ||
242 | } | ||
243 | // generate candidates, fill second candidate (first candidate is empty candidate) | ||
244 | else if let Some(prefix) = self.completable_word() { | ||
245 | self.completions = Some(Completions::new( | ||
246 | c, | ||
247 | completions(env_list, prefix) | ||
248 | .iter() | ||
249 | .map(|&s| s.to_owned()) | ||
250 | .collect(), | ||
251 | )); | ||
252 | self.complete_next(env_list) | ||
253 | } | ||
138 | } | 254 | } |
139 | } | 255 | } |
140 | 256 | ||
@@ -274,6 +390,26 @@ mod command_tests { | |||
274 | } | 390 | } |
275 | 391 | ||
276 | #[test] | 392 | #[test] |
393 | fn get_last_completable() { | ||
394 | assert_eq!(setup_with("(and").completable_word(), Some("and")); | ||
395 | assert_eq!(setup_with("(and (hello").completable_word(), Some("hello")); | ||
396 | } | ||
397 | |||
398 | #[test] | ||
399 | fn get_middle_completable() { | ||
400 | let mut cmd = setup_with("(and another"); | ||
401 | cmd.cursor = 4; | ||
402 | assert_eq!(cmd.completable_word(), Some("and")); | ||
403 | } | ||
404 | |||
405 | #[test] | ||
406 | fn no_complete() { | ||
407 | assert_eq!(setup_with("(and ").completable_word(), None); | ||
408 | assert_eq!(setup_with("(and (").completable_word(), None); | ||
409 | assert_eq!(setup_with("(and ( ").completable_word(), None); | ||
410 | } | ||
411 | |||
412 | #[test] | ||
277 | fn hist_append() { | 413 | fn hist_append() { |
278 | let mut cmd = setup_with("hello"); | 414 | let mut cmd = setup_with("hello"); |
279 | cmd.hist_append(); | 415 | cmd.hist_append(); |
diff --git a/src/lisp/eval.rs b/src/lisp/eval.rs index b39730b..784cba0 100644 --- a/src/lisp/eval.rs +++ b/src/lisp/eval.rs | |||
@@ -20,6 +20,15 @@ pub struct Evaluator<'ctx, 'global> { | |||
20 | pub context: Context, | 20 | pub context: Context, |
21 | } | 21 | } |
22 | 22 | ||
23 | impl<'ctx, 'global> Evaluator<'ctx, 'global> { | ||
24 | pub fn new(app: &'global mut AppState<'ctx>) -> Self { | ||
25 | Self { | ||
26 | app, | ||
27 | context: Vec::new(), | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
23 | impl<'ctx, 'global> Evaluator<'ctx, 'global> | 32 | impl<'ctx, 'global> Evaluator<'ctx, 'global> |
24 | where | 33 | where |
25 | 'ctx: 'global, | 34 | 'ctx: 'global, |
@@ -337,6 +346,25 @@ pub fn lookup(env_list: &[Environment], key: &str) -> Result<LispExpr, LispError | |||
337 | } | 346 | } |
338 | } | 347 | } |
339 | 348 | ||
349 | pub fn completions<'a>(env_list: &'a [Environment], prefix: &str) -> Vec<&'a str> { | ||
350 | fn helper<'h>(env_list: &'h [Environment], prefix: &str, completions: &mut Vec<Vec<&'h str>>) { | ||
351 | if !env_list.is_empty() { | ||
352 | let nearest_env = env_list.last().unwrap(); | ||
353 | completions.push( | ||
354 | nearest_env | ||
355 | .keys() | ||
356 | .filter(|k| k.starts_with(prefix)) | ||
357 | .map(|s| &s[prefix.len()..]) | ||
358 | .collect::<Vec<_>>(), | ||
359 | ); | ||
360 | helper(&env_list[..env_list.len() - 1], prefix, completions); | ||
361 | } | ||
362 | } | ||
363 | let mut completions = Vec::new(); | ||
364 | helper(env_list, prefix, &mut completions); | ||
365 | completions.into_iter().flatten().collect() | ||
366 | } | ||
367 | |||
340 | #[cfg(test)] | 368 | #[cfg(test)] |
341 | mod tests { | 369 | mod tests { |
342 | use super::*; | 370 | use super::*; |
diff --git a/src/lisp/lex.rs b/src/lisp/lex.rs index 754a23f..6e59469 100644 --- a/src/lisp/lex.rs +++ b/src/lisp/lex.rs | |||
@@ -235,6 +235,7 @@ fn parse_string(input: &str) -> Result<(usize, Token<'_>), ParseErrorKind> { | |||
235 | 235 | ||
236 | fn is_ident(ch: char) -> bool { | 236 | fn is_ident(ch: char) -> bool { |
237 | match ch { | 237 | match ch { |
238 | // "!$%&*+-./<=>?^_|#" | ||
238 | '!' | '$' | '%' | '&' | '*' | '+' | '-' | '.' | '/' | '<' | '=' | '>' | '?' | '^' | '_' | 239 | '!' | '$' | '%' | '&' | '*' | '+' | '-' | '.' | '/' | '<' | '=' | '>' | '?' | '^' | '_' |
239 | | '|' | '#' => true, | 240 | | '|' | '#' => true, |
240 | _ if ch.is_alphanumeric() => true, | 241 | _ if ch.is_alphanumeric() => true, |