aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.rs18
-rw-r--r--src/command.rs136
-rw-r--r--src/lisp/eval.rs28
-rw-r--r--src/lisp/lex.rs1
4 files changed, 172 insertions, 11 deletions
diff --git a/src/app.rs b/src/app.rs
index 04468a6..aba71f8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -27,7 +27,6 @@ use std::{
27 path::{Path, PathBuf}, 27 path::{Path, PathBuf},
28}; 28};
29 29
30use log::info;
31use obi::{CompressionType, Image}; 30use obi::{CompressionType, Image};
32use sdl2::{ 31use 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 @@
1use crate::lisp::{eval::completions, Environment};
2
1#[derive(Debug)] 3#[derive(Debug)]
2pub struct CommandBox { 4pub 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)]
13pub struct Completions {
14 pub trigger_idx: usize,
15 pub candidates: Vec<String>,
16 pub idx: usize,
17}
18
19impl 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
9impl CommandBox { 52impl 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
23impl<'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
23impl<'ctx, 'global> Evaluator<'ctx, 'global> 32impl<'ctx, 'global> Evaluator<'ctx, 'global>
24where 33where
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
349pub 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)]
341mod tests { 369mod 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
236fn is_ident(ch: char) -> bool { 236fn 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,