From d2cc31ee49d673f343ce5089071ef3628c3cdc97 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 17 May 2021 16:55:09 +0530 Subject: add tab to complete env variables --- src/command.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) (limited to 'src/command.rs') 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 @@ +use crate::lisp::{eval::completions, Environment}; + #[derive(Debug)] pub struct CommandBox { pub history: History, pub hist_idx: Option, pub text: String, pub cursor: usize, + pub completions: Option, +} + +#[derive(Debug)] +pub struct Completions { + pub trigger_idx: usize, + pub candidates: Vec, + pub idx: usize, +} + +impl Completions { + pub fn new(trigger_idx: usize, mut candidates: Vec) -> Self { + candidates.insert(0, "".into()); + Self { + trigger_idx, + candidates, + idx: 0, + } + } + pub fn prefix(&self) -> &str { + self.candidates[0].as_str() + } + pub fn next(&mut self) -> &str { + if self.idx + 1 == self.candidates.len() { + self.idx = 0; + } else { + self.idx += 1; + } + self.get() + } + pub fn prev(&mut self) -> &str { + if self.idx == 0 { + self.idx = self.candidates.len() - 1; + } else { + self.idx -= 1; + } + self.get() + } + fn get(&self) -> &str { + &self.candidates[self.idx] + } } impl CommandBox { @@ -13,6 +56,13 @@ impl CommandBox { hist_idx: None, text: String::new(), cursor: 0, + completions: None, + } + } + + pub fn invalidate_completions(&mut self) { + if self.completions.is_some() { + self.completions = None; } } @@ -20,14 +70,17 @@ impl CommandBox { if self.cursor < self.text.len() { self.cursor += 1; } + self.invalidate_completions(); } pub fn cursor_end(&mut self) { self.cursor = self.text.len(); + self.invalidate_completions(); } pub fn cursor_start(&mut self) { self.cursor = 0; + self.invalidate_completions(); } pub fn cursor_back_word(&mut self) { @@ -40,6 +93,7 @@ impl CommandBox { } } self.cursor = prev_word_idx; + self.invalidate_completions(); } pub fn cursor_forward_word(&mut self) { @@ -54,10 +108,26 @@ impl CommandBox { } } self.cursor = next_word_idx; + self.invalidate_completions(); + } + + // returns the word prefix if completable + pub fn completable_word(&mut self) -> Option<&str> { + // text behind the cursor + let sl = &self.text[0..self.cursor]; + // index of last non-completable character + let idx = sl.rfind(|c: char| !c.is_alphanumeric() && !"!$%&*+-./<=>?^_|#".contains(c)); + // i+1 to cursor contains completable word + match idx { + Some(i) if i == self.cursor - 1 => None, + Some(i) => Some(&self.text[i + 1..self.cursor]), + _ => None, + } } pub fn backward(&mut self) { self.cursor = self.cursor.saturating_sub(1); + self.invalidate_completions(); } pub fn backspace(&mut self) { @@ -65,26 +135,38 @@ impl CommandBox { self.text.remove(self.cursor - 1); self.backward(); } + self.invalidate_completions(); } pub fn delete(&mut self) { if self.cursor < self.text.len() { self.text.remove(self.cursor); } + self.invalidate_completions(); + } + + pub fn delete_n(&mut self, n: usize) { + for _ in 0..n { + self.delete() + } + self.invalidate_completions(); } pub fn delete_to_end(&mut self) { self.text.truncate(self.cursor); + self.invalidate_completions(); } pub fn delete_to_start(&mut self) { self.text = self.text.chars().skip(self.cursor).collect(); self.cursor = 0; + self.invalidate_completions(); } pub fn push_str(&mut self, v: &str) { self.text.insert_str(self.cursor, v); self.cursor += v.len(); + self.invalidate_completions(); } pub fn is_empty(&self) -> bool { @@ -94,11 +176,14 @@ impl CommandBox { pub fn clear(&mut self) { self.text.clear(); self.cursor = 0; + self.hist_idx = None; + self.invalidate_completions(); } pub fn hist_append(&mut self) { self.history.append(self.text.drain(..).collect()); self.cursor_start(); + self.invalidate_completions(); } fn get_from_hist(&self) -> String { @@ -121,6 +206,7 @@ impl CommandBox { self.text = self.get_from_hist(); self.cursor_end(); } + self.invalidate_completions(); } pub fn hist_next(&mut self) { @@ -135,6 +221,36 @@ impl CommandBox { } self.cursor_end(); } + self.invalidate_completions(); + } + + pub fn complete_next(&mut self, env_list: &[Environment]) { + let c = self.cursor; + // completions exist, fill with next completion + if let Some(cs) = &mut self.completions { + self.cursor = cs.trigger_idx; + let prev_len = cs.get().len(); + // skips over the first empty completion + let new_insertion = cs.next(); + self.text = format!( + "{}{}{}", + &self.text[..self.cursor], + new_insertion, + &self.text[self.cursor + prev_len..] + ); + self.cursor += new_insertion.len(); + } + // generate candidates, fill second candidate (first candidate is empty candidate) + else if let Some(prefix) = self.completable_word() { + self.completions = Some(Completions::new( + c, + completions(env_list, prefix) + .iter() + .map(|&s| s.to_owned()) + .collect(), + )); + self.complete_next(env_list) + } } } @@ -273,6 +389,26 @@ mod command_tests { assert_eq!(cmd.cursor, 1); } + #[test] + fn get_last_completable() { + assert_eq!(setup_with("(and").completable_word(), Some("and")); + assert_eq!(setup_with("(and (hello").completable_word(), Some("hello")); + } + + #[test] + fn get_middle_completable() { + let mut cmd = setup_with("(and another"); + cmd.cursor = 4; + assert_eq!(cmd.completable_word(), Some("and")); + } + + #[test] + fn no_complete() { + assert_eq!(setup_with("(and ").completable_word(), None); + assert_eq!(setup_with("(and (").completable_word(), None); + assert_eq!(setup_with("(and ( ").completable_word(), None); + } + #[test] fn hist_append() { let mut cmd = setup_with("hello"); -- cgit v1.2.3