diff --git a/src/keymap.rs b/src/keymap.rs index a63be00e2f42ff72bc72f48e83150330f1af9384..3287f53552fb0b258adb0a21d8a18f854ac7cf85 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -119,15 +119,6 @@ pub enum CharSearch { } impl CharSearch { - pub fn is_backward(&self) -> bool { - match *self { - CharSearch::Forward(_) => false, - CharSearch::ForwardBefore(_) => false, - CharSearch::Backward(_) => true, - CharSearch::BackwardAfter(_) => true, - } - } - fn opposite(&self) -> CharSearch { match *self { CharSearch::Forward(c) => CharSearch::Backward(c), diff --git a/src/kill_ring.rs b/src/kill_ring.rs index cbe2aa12aa5eec5b692014919a9f6152c81ab1e5..b6342f6f05ab0f8257ce59e6a02cae853c4b991e 100644 --- a/src/kill_ring.rs +++ b/src/kill_ring.rs @@ -1,4 +1,5 @@ //! Kill Ring +use line_buffer::{ChangeListener, Direction}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Action { @@ -102,6 +103,18 @@ impl KillRing { } } +impl ChangeListener for KillRing { + fn insert_char(&mut self, _: usize, _: char) {} + fn insert_str(&mut self, _: usize, _: &str) {} + fn delete(&mut self, _: usize, string: &str, dir: Direction) { + let mode = match dir { + Direction::Forward => Mode::Append, + Direction::Backward => Mode::Prepend, + }; + self.kill(string, mode); + } +} + #[cfg(test)] mod tests { use super::{Action, Mode, KillRing}; diff --git a/src/lib.rs b/src/lib.rs index 0718d734d36da0cbd01f36f2ff97df6b519a673f..fc3f6fc83e344c66df414f46be8454b5d52d2e2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; -use line_buffer::{ChangeListener, LineBuffer, MAX_LINE, WordAction}; +use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use keymap::{Anchor, At, CharSearch, Cmd, EditState, Movement, RepeatCount, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -80,6 +80,7 @@ struct State<'out, 'prompt> { term: Terminal, // terminal byte_buffer: [u8; 4], edit_state: EditState, + changes: Rc<RefCell<Changeset>>, } #[derive(Copy, Clone, Debug, Default)] @@ -111,6 +112,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { term: term, byte_buffer: [0; 4], edit_state: EditState::new(config), + changes: Rc::new(RefCell::new(Changeset::new())), } } @@ -829,6 +831,9 @@ fn reverse_incremental_search<R: RawReader>(rdr: &mut R, Ok(Some(cmd)) } +static KILL_RING_NAME: &'static str = "kill_ring"; +static UNDOS_NAME: &'static str = "undos"; + /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) @@ -842,13 +847,12 @@ fn readline_edit<C: Completer>(prompt: &str, let mut stdout = editor.term.create_writer(); editor.reset_kill_ring(); - editor.clear_changes(); let mut s = State::new(&mut stdout, editor.term.clone(), &editor.config, prompt, editor.history.len()); - s.line.bind(Some(editor.listener.clone())); + s.line.bind(UNDOS_NAME, s.changes.clone()); try!(s.refresh_line()); let mut rdr = try!(s.term.create_reader(&editor.config)); @@ -941,13 +945,15 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::Kill(Movement::EndOfLine) => { // Kill the text from point to the end of the line. - editor.set_kill_ring_mode(Mode::Append); - try!(edit_kill_line(&mut s)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_kill_line(&mut s)); + s.line.unbind(KILL_RING_NAME); } Cmd::Kill(Movement::WholeLine) => { try!(edit_move_home(&mut s)); - editor.set_kill_ring_mode(Mode::Append); - try!(edit_kill_line(&mut s)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_kill_line(&mut s)); + s.line.unbind(KILL_RING_NAME); } Cmd::ClearScreen => { // Clear the screen leaving the current line at the top of the screen. @@ -971,8 +977,9 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::Kill(Movement::BeginningOfLine) => { // Kill backward from point to the beginning of the line. - editor.set_kill_ring_mode(Mode::Prepend); - try!(edit_discard_line(&mut s)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_discard_line(&mut s)); + s.line.unbind(KILL_RING_NAME); } #[cfg(unix)] Cmd::QuotedInsert => { @@ -983,14 +990,14 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::Yank(n, anchor) => { // retrieve (yank) last item killed - if let Some(text) = editor.listener.borrow_mut().kill_ring.yank() { + if let Some(text) = editor.kill_ring.borrow_mut().yank() { try!(edit_yank(&mut s, text, anchor, n)) } } Cmd::ViYankTo(mvt) => { editor.reset_kill_ring(); if let Some(text) = s.line.copy(mvt) { - editor.kill(&text, Mode::Append) + editor.kill_ring.borrow_mut().kill(&text, Mode::Append) } } // TODO CTRL-_ // undo @@ -1002,8 +1009,9 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::Kill(Movement::BackwardWord(n, word_def)) => { // kill one word backward (until start of word) - editor.set_kill_ring_mode(Mode::Prepend); - try!(edit_delete_prev_word(&mut s, word_def, n)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_delete_prev_word(&mut s, word_def, n)); + s.line.unbind(KILL_RING_NAME); } Cmd::BeginningOfHistory => { // move to first entry in history @@ -1027,8 +1035,9 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::Kill(Movement::ForwardWord(n, at, word_def)) => { // kill one word forward (until start/end of word) - editor.set_kill_ring_mode(Mode::Append); - try!(edit_delete_word(&mut s, at, word_def, n)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_delete_word(&mut s, at, word_def, n)); + s.line.unbind(KILL_RING_NAME); } Cmd::ForwardWord(n, at, word_def) => { // move forwards one word @@ -1052,7 +1061,7 @@ fn readline_edit<C: Completer>(prompt: &str, } Cmd::YankPop => { // yank-pop - if let Some((yank_size, text)) = editor.listener.borrow_mut().kill_ring.yank_pop() { + if let Some((yank_size, text)) = editor.kill_ring.borrow_mut().yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } @@ -1061,17 +1070,16 @@ fn readline_edit<C: Completer>(prompt: &str, try!(edit_move_to(&mut s, cs, n)) } Cmd::Kill(Movement::ViCharSearch(n, cs)) => { - if cs.is_backward() { - editor.set_kill_ring_mode(Mode::Prepend); - } else { - editor.set_kill_ring_mode(Mode::Append); - } - try!(edit_delete_to(&mut s, cs, n)) + s.line.bind(KILL_RING_NAME, editor.kill_ring.clone()); + try!(edit_delete_to(&mut s, cs, n)); + s.line.unbind(KILL_RING_NAME); } Cmd::Undo => { - if editor.undo(&mut s.line) { + s.line.unbind(UNDOS_NAME); + if s.changes.borrow_mut().undo(&mut s.line) { try!(s.refresh_line()); } + s.line.bind(UNDOS_NAME, s.changes.clone()); } Cmd::Interrupt => { editor.reset_kill_ring(); @@ -1130,71 +1138,10 @@ pub struct Editor<C: Completer> { term: Terminal, history: History, completer: Option<C>, - listener: Rc<RefCell<Listener>>, + kill_ring: Rc<RefCell<KillRing>>, config: Config, } -struct Listener { - kill_ring: KillRing, - mode: Option<Mode>, - changes: Changeset, // FIXME useful only during one line edition (versus kill_ring) - undoing: bool, -} - -impl Listener { - fn new() -> Rc<RefCell<Listener>> { - let l = Listener { - kill_ring: KillRing::new(60), - mode: None, - changes: Changeset::new(), - undoing: false, - }; - Rc::new(RefCell::new(l)) - } - - fn reset_kill_ring(&mut self) { - self.kill_ring.reset(); - self.mode = None; - } - fn set_kill_ring_mode(&mut self, mode: Mode) { - self.mode = Some(mode); - } - - fn undo(&mut self, line: &mut LineBuffer) -> bool { - self.undoing = true; - let ok = self.changes.undo(line); - self.undoing = false; - ok - } - fn clear_changes(&mut self) { - self.changes.clear(); - } -} -impl ChangeListener for Listener { - fn insert_char(&mut self, idx: usize, c: char) { - if self.undoing { - return; - } - self.changes.insert(idx, c); - } - fn insert_str(&mut self, idx: usize, string: &str) { - if self.undoing { - return; - } - self.changes.insert_str(idx, string); - } - fn delete(&mut self, idx: usize, string: &str) { - if self.mode.is_some() { - self.kill_ring.kill(string, self.mode.unwrap()); - self.mode = None; - } - if self.undoing { - return; - } - self.changes.delete(idx, string); - } -} - impl<C: Completer> Editor<C> { pub fn new() -> Editor<C> { Self::with_config(Config::default()) @@ -1206,7 +1153,7 @@ impl<C: Completer> Editor<C> { term: term, history: History::with_config(config), completer: None, - listener: Listener::new(), + kill_ring: Rc::new(RefCell::new(KillRing::new(60))), config: config, } } @@ -1277,20 +1224,7 @@ impl<C: Completer> Editor<C> { } fn reset_kill_ring(&self) { - self.listener.borrow_mut().reset_kill_ring(); - } - fn set_kill_ring_mode(&self, mode: Mode) { - self.listener.borrow_mut().set_kill_ring_mode(mode); - } - fn kill(&self, text: &str, dir: Mode) { - self.listener.borrow_mut().kill_ring.kill(text, dir) - } - - fn undo(&self, line: &mut LineBuffer) -> bool { - self.listener.borrow_mut().undo(line) - } - fn clear_changes(&self) { - self.listener.borrow_mut().clear_changes() + self.kill_ring.borrow_mut().reset(); } } @@ -1328,7 +1262,9 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { #[cfg(test)] mod test { + use std::cell::RefCell; use std::io::Write; + use std::rc::Rc; use line_buffer::LineBuffer; use history::History; use completion::Completer; @@ -1337,6 +1273,7 @@ mod test { use keymap::{Cmd, EditState}; use super::{Editor, Position, Result, State}; use tty::{Terminal, Term}; + use undo::Changeset; fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1358,6 +1295,7 @@ mod test { term: term, byte_buffer: [0; 4], edit_state: EditState::new(&config), + changes: Rc::new(RefCell::new(Changeset::new())), } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 7d8000da84a8b29d8179797291bd2b4e3951612a..6813eaa716c1fd0e837e51fe6b1776c3d14ea035 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,6 @@ //! Line buffer with current cursor position use std::cell::RefCell; +use std::collections::HashMap; use std::fmt; use std::iter; use std::ops::{Deref, Range}; @@ -18,16 +19,28 @@ pub enum WordAction { UPPERCASE, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Forward, + Backward +} + +impl Default for Direction { + fn default() -> Direction { + Direction::Forward + } +} + pub trait ChangeListener { fn insert_char(&mut self, idx: usize, c: char); fn insert_str(&mut self, idx: usize, string: &str); - fn delete(&mut self, idx: usize, string: &str); + fn delete(&mut self, idx: usize, string: &str, dir: Direction); } pub struct LineBuffer { buf: String, // Edited line buffer pos: usize, // Current cursor position (byte position) - cl: Option<Rc<RefCell<ChangeListener>>>, + cl: HashMap<&'static str, Rc<RefCell<ChangeListener>>>, } impl fmt::Debug for LineBuffer { @@ -45,7 +58,7 @@ impl LineBuffer { LineBuffer { buf: String::with_capacity(capacity), pos: 0, - cl: None, + cl: HashMap::new(), } } @@ -54,12 +67,17 @@ impl LineBuffer { let mut lb = Self::with_capacity(MAX_LINE); assert!(lb.insert_str(0, line)); lb.set_pos(pos); - lb.cl = cl; + if cl.is_some() { + lb.bind("test", cl.unwrap()); + } lb } - pub fn bind(&mut self, cl: Option<Rc<RefCell<ChangeListener>>>) { - self.cl = cl; + pub fn bind(&mut self, key: &'static str, cl: Rc<RefCell<ChangeListener>>) { + self.cl.insert(key, cl); + } + pub fn unbind(&mut self, key: &'static str) { + self.cl.remove(key); } /// Extracts a string slice containing the entire buffer. @@ -94,7 +112,7 @@ impl LineBuffer { pub fn update(&mut self, buf: &str, pos: usize) { assert!(pos <= buf.len()); let end = self.len(); - self.drain(0..end); + self.drain(0..end, Direction::default()); let max = self.buf.capacity(); if buf.len() > max { self.insert_str(0, &buf[..max]); @@ -112,7 +130,7 @@ impl LineBuffer { /// Backup `src` pub fn backup(&mut self, src: &LineBuffer) { let end = self.len(); - self.drain(0..end); + self.drain(0..end, Direction::default()); self.insert_str(0, &src.buf); self.pos = src.pos; } @@ -161,7 +179,7 @@ impl LineBuffer { let push = self.pos == self.buf.len(); if n == 1 { self.buf.insert(self.pos, ch); - for cl in &self.cl { + for (_, cl) in &self.cl { cl.borrow_mut().insert_char(self.pos, ch); } } else { @@ -197,7 +215,7 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option<bool> { let end = self.pos; let start = end - yank_size; - self.drain(start..end); + self.drain(start..end, Direction::default()); self.pos -= yank_size; self.yank(text, 1) } @@ -251,7 +269,7 @@ impl LineBuffer { match self.next_pos(n) { Some(pos) => { let start = self.pos; - let chars = self.drain(start..pos).collect::<String>(); + let chars = self.drain(start..pos, Direction::Forward).collect::<String>(); Some(chars) } None => None, @@ -264,7 +282,7 @@ impl LineBuffer { match self.prev_pos(n) { Some(pos) => { let end = self.pos; - self.drain(pos..end); + self.drain(pos..end, Direction::Backward); self.pos = pos; true } @@ -277,7 +295,7 @@ impl LineBuffer { if !self.buf.is_empty() && self.pos < self.buf.len() { let start = self.pos; let end = self.buf.len(); - self.drain(start..end); + self.drain(start..end, Direction::Forward); true } else { false @@ -288,7 +306,7 @@ impl LineBuffer { pub fn discard_line(&mut self) -> bool { if self.pos > 0 && !self.buf.is_empty() { let end = self.pos; - self.drain(0..end); + self.drain(0..end, Direction::Backward); self.pos = 0; true } else { @@ -361,7 +379,7 @@ impl LineBuffer { pub fn delete_prev_word(&mut self, word_def: Word, n: RepeatCount) -> bool { if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { let end = self.pos; - self.drain(pos..end); + self.drain(pos..end, Direction::Backward); self.pos = pos; true } else { @@ -502,7 +520,7 @@ impl LineBuffer { pub fn delete_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> bool { if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) { let start = self.pos; - self.drain(start..pos); + self.drain(start..pos, Direction::Forward); true } else { false @@ -520,15 +538,15 @@ impl LineBuffer { CharSearch::BackwardAfter(_) => { let end = self.pos; self.pos = pos; - self.drain(pos..end); + self.drain(pos..end, Direction::Backward); } CharSearch::ForwardBefore(_) => { let start = self.pos; - self.drain(start..pos); + self.drain(start..pos, Direction::Forward); } CharSearch::Forward(c) => { let start = self.pos; - self.drain(start..pos + c.len_utf8()); + self.drain(start..pos + c.len_utf8(), Direction::Forward); } }; true @@ -555,7 +573,7 @@ impl LineBuffer { if start == end { return false; } - let word = self.drain(start..end).collect::<String>(); + let word = self.drain(start..end, Direction::default()).collect::<String>(); let result = match a { WordAction::CAPITALIZE => { let ch = (&word).graphemes(true).next().unwrap(); @@ -590,10 +608,10 @@ impl LineBuffer { let w1 = self.buf[w1_beg..w1_end].to_owned(); - let w2 = self.drain(w2_beg..w2_end).collect::<String>(); + let w2 = self.drain(w2_beg..w2_end, Direction::default()).collect::<String>(); self.insert_str(w2_beg, &w1); - self.drain(w1_beg..w1_end); + self.drain(w1_beg..w1_end, Direction::default()); self.insert_str(w1_beg, &w2); self.pos = w2_end; @@ -604,13 +622,13 @@ impl LineBuffer { /// and positions the cursor to the end of text. pub fn replace(&mut self, range: Range<usize>, text: &str) { let start = range.start; - self.drain(range); + self.drain(range, Direction::default()); self.insert_str(start, text); self.pos = start + text.len(); } pub fn insert_str(&mut self, idx: usize, s: &str) -> bool { - for cl in &self.cl { + for (_, cl) in &self.cl { cl.borrow_mut().insert_str(idx, s); } if idx == self.buf.len() { @@ -623,12 +641,13 @@ impl LineBuffer { } pub fn delete_range(&mut self, range: Range<usize>) { - self.drain(range); + self.set_pos(range.start); + self.drain(range, Direction::default()); } - fn drain(&mut self, range: Range<usize>) -> Drain { - for cl in &self.cl { - cl.borrow_mut().delete(range.start, &self.buf[range.start..range.end]); + fn drain(&mut self, range: Range<usize>, dir: Direction) -> Drain { + for (_, cl) in &self.cl { + cl.borrow_mut().delete(range.start, &self.buf[range.start..range.end], dir); } self.buf.drain(range) } @@ -741,7 +760,7 @@ mod test { use std::cell::RefCell; use std::rc::Rc; use keymap::{At, CharSearch, Word}; - use super::{ChangeListener, LineBuffer, MAX_LINE, WordAction}; + use super::{ChangeListener, Direction, LineBuffer, MAX_LINE, WordAction}; struct Listener { deleted_str: Option<String>, @@ -762,7 +781,7 @@ mod test { impl ChangeListener for Listener { fn insert_char(&mut self, _: usize, _: char) {} fn insert_str(&mut self, _: usize, _: &str) {} - fn delete(&mut self, _: usize, string: &str) { + fn delete(&mut self, _: usize, string: &str, _: Direction) { self.deleted_str = Some(string.to_owned()); } } diff --git a/src/undo.rs b/src/undo.rs index 1b070a89907660e1e980695fae88977d6f935fc6..710b4c09f4ded933a5543be5623f348e3a6f37da 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -1,5 +1,5 @@ //! Undo API -use line_buffer::LineBuffer; +use line_buffer::{ChangeListener, Direction, LineBuffer}; use std_unicode::str::UnicodeStr; use unicode_segmentation::UnicodeSegmentation; @@ -202,10 +202,17 @@ impl Changeset { None => false, } } +} - pub fn clear(&mut self) { - self.undos.clear(); - self.redos.clear(); +impl ChangeListener for Changeset { + fn insert_char(&mut self, idx: usize, c: char) { + self.insert(idx, c); + } + fn insert_str(&mut self, idx: usize, string: &str) { + self.insert_str(idx, string); + } + fn delete(&mut self, idx: usize, string: &str, _: Direction) { + self.delete(idx, string); } }