| //! Kill Ring management |
| use line_buffer::{DeleteListener, Direction}; |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| enum Action { |
| Kill, |
| Yank(usize), |
| Other, |
| } |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum Mode { |
| Append, |
| Prepend, |
| } |
| |
| pub struct KillRing { |
| slots: Vec<String>, |
| // where we are in the kill ring |
| index: usize, |
| // whether or not the last command was a kill or a yank |
| last_action: Action, |
| killing: bool, |
| } |
| |
| impl KillRing { |
| /// Create a new kill-ring of the given `size`. |
| pub fn new(size: usize) -> KillRing { |
| KillRing { |
| slots: Vec::with_capacity(size), |
| index: 0, |
| last_action: Action::Other, |
| killing: false, |
| } |
| } |
| |
| /// Reset `last_action` state. |
| pub fn reset(&mut self) { |
| self.last_action = Action::Other; |
| } |
| |
| /// Add `text` to the kill-ring. |
| pub fn kill(&mut self, text: &str, dir: Mode) { |
| if let Action::Kill = self.last_action { |
| if self.slots.capacity() == 0 { |
| // disabled |
| return; |
| } |
| match dir { |
| Mode::Append => self.slots[self.index].push_str(text), |
| Mode::Prepend => self.slots[self.index].insert_str(0, text), |
| }; |
| } else { |
| self.last_action = Action::Kill; |
| if self.slots.capacity() == 0 { |
| // disabled |
| return; |
| } |
| if self.index == self.slots.capacity() - 1 { |
| // full |
| self.index = 0; |
| } else if !self.slots.is_empty() { |
| self.index += 1; |
| } |
| if self.index == self.slots.len() { |
| self.slots.push(String::from(text)) |
| } else { |
| self.slots[self.index] = String::from(text); |
| } |
| } |
| } |
| |
| /// Yank previously killed text. |
| /// Return `None` when kill-ring is empty. |
| pub fn yank(&mut self) -> Option<&String> { |
| if self.slots.is_empty() { |
| None |
| } else { |
| self.last_action = Action::Yank(self.slots[self.index].len()); |
| Some(&self.slots[self.index]) |
| } |
| } |
| |
| /// Yank killed text stored in previous slot. |
| /// Return `None` when the previous command was not a yank. |
| pub fn yank_pop(&mut self) -> Option<(usize, &String)> { |
| match self.last_action { |
| Action::Yank(yank_size) => { |
| if self.slots.is_empty() { |
| return None; |
| } |
| if self.index == 0 { |
| self.index = self.slots.len() - 1; |
| } else { |
| self.index -= 1; |
| } |
| self.last_action = Action::Yank(self.slots[self.index].len()); |
| Some((yank_size, &self.slots[self.index])) |
| } |
| _ => None, |
| } |
| } |
| } |
| |
| impl DeleteListener for KillRing { |
| fn start_killing(&mut self) { |
| self.killing = true; |
| } |
| |
| fn delete(&mut self, _: usize, string: &str, dir: Direction) { |
| if !self.killing { |
| return; |
| } |
| let mode = match dir { |
| Direction::Forward => Mode::Append, |
| Direction::Backward => Mode::Prepend, |
| }; |
| self.kill(string, mode); |
| } |
| |
| fn stop_killing(&mut self) { |
| self.killing = false; |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{Action, KillRing, Mode}; |
| |
| #[test] |
| fn disabled() { |
| let mut kill_ring = KillRing::new(0); |
| kill_ring.kill("text", Mode::Append); |
| assert!(kill_ring.slots.is_empty()); |
| assert_eq!(0, kill_ring.index); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| |
| assert_eq!(None, kill_ring.yank()); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn one_kill() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| assert_eq!(0, kill_ring.index); |
| assert_eq!(1, kill_ring.slots.len()); |
| assert_eq!("word1", kill_ring.slots[0]); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn kill_append() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| kill_ring.kill(" word2", Mode::Append); |
| assert_eq!(0, kill_ring.index); |
| assert_eq!(1, kill_ring.slots.len()); |
| assert_eq!("word1 word2", kill_ring.slots[0]); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn kill_backward() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Prepend); |
| kill_ring.kill("word2 ", Mode::Prepend); |
| assert_eq!(0, kill_ring.index); |
| assert_eq!(1, kill_ring.slots.len()); |
| assert_eq!("word2 word1", kill_ring.slots[0]); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn kill_other_kill() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("word2", Mode::Append); |
| assert_eq!(1, kill_ring.index); |
| assert_eq!(2, kill_ring.slots.len()); |
| assert_eq!("word1", kill_ring.slots[0]); |
| assert_eq!("word2", kill_ring.slots[1]); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn many_kill() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("word2", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("word3", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("word4", Mode::Append); |
| assert_eq!(1, kill_ring.index); |
| assert_eq!(2, kill_ring.slots.len()); |
| assert_eq!("word3", kill_ring.slots[0]); |
| assert_eq!("word4", kill_ring.slots[1]); |
| assert_eq!(Action::Kill, kill_ring.last_action); |
| } |
| |
| #[test] |
| fn yank() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("word2", Mode::Append); |
| |
| assert_eq!(Some(&"word2".to_owned()), kill_ring.yank()); |
| assert_eq!(Action::Yank(5), kill_ring.last_action); |
| assert_eq!(Some(&"word2".to_owned()), kill_ring.yank()); |
| assert_eq!(Action::Yank(5), kill_ring.last_action); |
| } |
| |
| #[test] |
| fn yank_pop() { |
| let mut kill_ring = KillRing::new(2); |
| kill_ring.kill("word1", Mode::Append); |
| kill_ring.reset(); |
| kill_ring.kill("longword2", Mode::Append); |
| |
| assert_eq!(None, kill_ring.yank_pop()); |
| kill_ring.yank(); |
| assert_eq!(Some((9, &"word1".to_owned())), kill_ring.yank_pop()); |
| assert_eq!(Some((5, &"longword2".to_owned())), kill_ring.yank_pop()); |
| assert_eq!(Some((9, &"word1".to_owned())), kill_ring.yank_pop()); |
| } |
| } |