#[derive(Debug)] pub struct CommandBox { pub history: History, pub hist_idx: Option, pub text: String, pub cursor: usize, } impl CommandBox { pub fn new() -> Self { CommandBox { history: History::new(64), hist_idx: None, text: String::new(), cursor: 0, } } pub fn forward(&mut self) { if self.cursor < self.text.len() { self.cursor += 1; } } pub fn cursor_end(&mut self) { self.cursor = self.text.len(); } pub fn cursor_start(&mut self) { self.cursor = 0; } pub fn cursor_back_word(&mut self) { let mut prev_word_idx = 0; { let sl = &self.text[0..self.cursor]; let idx = sl.rfind(|c: char| !c.is_alphanumeric() && c != '_'); if let Some(i) = idx { prev_word_idx = i; } } self.cursor = prev_word_idx; } pub fn cursor_forward_word(&mut self) { let mut next_word_idx = self.cursor; { if self.cursor != self.text.len() { let sl = &self.text[self.cursor..]; let idx = sl.find(|c: char| !c.is_alphanumeric() && c != '_'); if let Some(i) = idx { next_word_idx = i; } } } self.cursor = next_word_idx; } pub fn backward(&mut self) { self.cursor = self.cursor.saturating_sub(1); } pub fn backspace(&mut self) { if self.cursor != 0 { self.text.remove(self.cursor - 1); self.backward(); } } pub fn delete(&mut self) { if self.cursor < self.text.len() { self.text.remove(self.cursor); } } pub fn delete_to_end(&mut self) { self.text.truncate(self.cursor); } pub fn delete_to_start(&mut self) { self.text = self.text.chars().skip(self.cursor).collect(); self.cursor = 0; } pub fn push_str(&mut self, v: &str) { self.text.insert_str(self.cursor, v); self.cursor += v.len(); } pub fn is_empty(&self) -> bool { self.text.is_empty() } pub fn clear(&mut self) { self.text.clear(); self.cursor = 0; } pub fn hist_append(&mut self) { self.history.append(self.text.drain(..).collect()); self.cursor_start(); } fn get_from_hist(&self) -> String { let size = self.history.items.len(); self.history.items[size - 1 - self.hist_idx.unwrap()].clone() } pub fn hist_prev(&mut self) { if self.history.items.is_empty() { return; } if let Some(idx) = self.hist_idx { if idx + 1 < self.history.items.len() { self.hist_idx = Some(idx + 1); self.text = self.get_from_hist(); self.cursor_end(); } } else { self.hist_idx = Some(0); self.text = self.get_from_hist(); self.cursor_end(); } } pub fn hist_next(&mut self) { if let Some(idx) = self.hist_idx { // most recent hist item, reset command box if idx == 0 { self.hist_idx = None; self.text = "(".into(); } else { self.hist_idx = Some(idx - 1); self.text = self.get_from_hist(); } self.cursor_end(); } } } impl std::default::Default for CommandBox { fn default() -> Self { CommandBox::new() } } #[derive(Debug)] pub struct History { pub items: Vec, pub max_size: usize, } impl History { pub fn new(max_size: usize) -> Self { if max_size == 0 { panic!(); } Self { items: vec![], max_size, } } pub fn append(&mut self, item: T) { if self.items.len() >= self.max_size { self.items.remove(0); } self.items.push(item); } } #[cfg(test)] mod command_tests { use super::*; fn setup_with(text: &str) -> CommandBox { let mut cmd = CommandBox::new(); cmd.push_str(text); cmd } #[test] fn entering_text() { let cmd = setup_with("save as file.png"); assert_eq!(&cmd.text, "save as file.png"); assert_eq!(cmd.cursor, 16) } #[test] fn entering_text_between() { let mut cmd = setup_with("save as file.png"); cmd.backward(); cmd.backward(); cmd.backward(); cmd.push_str("ext"); assert_eq!(&cmd.text, "save as file.extpng"); } #[test] fn delete_to_end() { let mut cmd = setup_with("save as file.png"); cmd.backward(); cmd.backward(); cmd.backward(); cmd.delete_to_end(); assert_eq!(&cmd.text, "save as file."); } #[test] fn delete_to_start() { let mut cmd = setup_with("save as file.png"); cmd.backward(); cmd.backward(); cmd.backward(); cmd.delete_to_start(); assert_eq!(&cmd.text, "png"); } #[test] fn backspacing_from_end() { let mut cmd = setup_with("save"); cmd.backspace(); assert_eq!(&cmd.text, "sav"); assert_eq!(cmd.cursor, 3); } #[test] fn backspacing_from_middle() { let mut cmd = setup_with("save"); cmd.backward(); cmd.backspace(); assert_eq!(&cmd.text, "sae"); assert_eq!(cmd.cursor, 2); } #[test] fn delete() { let mut cmd = setup_with("save"); cmd.backward(); cmd.delete(); assert_eq!(&cmd.text, "sav"); assert_eq!(cmd.cursor, 3); } #[test] fn delete_end() { let mut cmd = setup_with("save"); cmd.delete(); assert_eq!(&cmd.text, "save"); } #[test] fn delete_all() { let mut cmd = setup_with("save"); for _ in 0..4 { cmd.backward(); } for _ in 0..4 { cmd.delete(); } assert_eq!(&cmd.text, ""); assert_eq!(cmd.cursor, 0); } #[test] fn seeking() { let mut cmd = setup_with("save"); for _ in 0..4 { cmd.backward(); } assert_eq!(cmd.cursor, 0); cmd.forward(); assert_eq!(cmd.cursor, 1); } #[test] fn hist_append() { let mut cmd = setup_with("hello"); cmd.hist_append(); cmd.push_str("another"); cmd.hist_append(); cmd.push_str("one"); cmd.hist_append(); assert_eq!(cmd.history.items.len(), 3); } #[test] fn hist_prev() { let mut cmd = setup_with("hello"); cmd.hist_append(); cmd.push_str("another"); cmd.hist_append(); cmd.push_str("one"); cmd.hist_append(); cmd.hist_prev(); assert_eq!(&cmd.text, "one"); cmd.hist_prev(); assert_eq!(&cmd.text, "another"); } #[test] fn hist_next() { let mut cmd = setup_with("hello"); cmd.hist_append(); cmd.push_str("another"); cmd.hist_append(); cmd.push_str("one"); cmd.hist_append(); cmd.hist_prev(); cmd.hist_prev(); cmd.hist_next(); assert_eq!(&cmd.text, "one"); } } #[cfg(test)] mod history_tests { use super::*; #[test] fn append() { let mut h = History::::new(4); h.append(5); h.append(6); h.append(7); h.append(8); h.append(9); assert_eq!(h.items, vec![6, 7, 8, 9]); } }