From c024d3c71eea66f11a3bd124f5cb8a111e57c080 Mon Sep 17 00:00:00 2001
From: gwenn <gtreguier@gmail.com>
Date: Sat, 22 Apr 2017 11:11:52 +0200
Subject: [PATCH] Make replace atomic (relative to ChangeListener)

---
 src/kill_ring.rs   |  6 ++---
 src/lib.rs         |  9 +++----
 src/line_buffer.rs | 61 ++++++++++++++++++++++++++++++-------------
 src/undo.rs        | 64 +++++++++++++++++++++++++++++-----------------
 4 files changed, 90 insertions(+), 50 deletions(-)

diff --git a/src/kill_ring.rs b/src/kill_ring.rs
index d76d94b1..c168570d 100644
--- a/src/kill_ring.rs
+++ b/src/kill_ring.rs
@@ -1,5 +1,5 @@
 //! Kill Ring
-use line_buffer::{ChangeListener, Direction};
+use line_buffer::{DeleteListener, Direction};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 enum Action {
@@ -112,9 +112,7 @@ impl KillRing {
     }
 }
 
-impl ChangeListener for KillRing {
-    fn insert_char(&mut self, _: usize, _: char) {}
-    fn insert_str(&mut self, _: usize, _: &str) {}
+impl DeleteListener for KillRing {
     fn delete(&mut self, _: usize, string: &str, dir: Direction) {
         if !self.killing {
             return;
diff --git a/src/lib.rs b/src/lib.rs
index 6e11c9ea..d3ecc1f5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -897,9 +897,8 @@ fn readline_edit<C: Completer>(prompt: &str,
                            editor.history.len(),
                            editor.custom_bindings.clone());
 
-    s.line.bind(editor.kill_ring.clone());
-    // must be the last
-    s.line.bind(s.changes.clone());
+    s.line.set_delete_listener(editor.kill_ring.clone());
+    s.line.set_change_listener(s.changes.clone());
 
     try!(s.refresh_line());
 
@@ -1104,11 +1103,11 @@ fn readline_edit<C: Completer>(prompt: &str,
                 editor.kill_ring.borrow_mut().stop_killing();
             }
             Cmd::Undo => {
-                s.line.unbind();
+                s.line.remove_change_listener();
                 if s.changes.borrow_mut().undo(&mut s.line) {
                     try!(s.refresh_line());
                 }
-                s.line.bind(s.changes.clone());
+                s.line.set_change_listener(s.changes.clone());
             }
             Cmd::Interrupt => {
                 return Err(error::ReadlineError::Interrupted);
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index c9f251fc..c6b1bbcd 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -2,7 +2,7 @@
 use std::cell::RefCell;
 use std::fmt;
 use std::iter;
-use std::ops::{Deref, Range};
+use std::ops::{Deref, Index, Range};
 use std::rc::Rc;
 use std::string::Drain;
 use std_unicode::str::UnicodeStr;
@@ -30,16 +30,21 @@ impl Default for Direction {
     }
 }
 
-pub trait ChangeListener {
+pub trait DeleteListener {
+    fn delete(&mut self, idx: usize, string: &str, dir: Direction);
+}
+
+pub trait ChangeListener: DeleteListener {
     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, dir: Direction);
+    fn replace(&mut self, idx: usize, old: &str, new: &str);
 }
 
 pub struct LineBuffer {
     buf: String, // Edited line buffer
     pos: usize, // Current cursor position (byte position)
-    cl: Vec<Rc<RefCell<ChangeListener>>>,
+    dl: Option<Rc<RefCell<DeleteListener>>>,
+    cl: Option<Rc<RefCell<ChangeListener>>>,
 }
 
 impl fmt::Debug for LineBuffer {
@@ -57,7 +62,8 @@ impl LineBuffer {
         LineBuffer {
             buf: String::with_capacity(capacity),
             pos: 0,
-            cl: Vec::new(),
+            dl: None,
+            cl: None,
         }
     }
 
@@ -66,17 +72,21 @@ impl LineBuffer {
         let mut lb = Self::with_capacity(MAX_LINE);
         assert!(lb.insert_str(0, line));
         lb.set_pos(pos);
-        if cl.is_some() {
-            lb.bind(cl.unwrap());
-        }
+        lb.cl = cl;
         lb
     }
 
-    pub fn bind(&mut self, cl: Rc<RefCell<ChangeListener>>) {
-        self.cl.push(cl);
+    pub fn set_delete_listener(&mut self, dl: Rc<RefCell<DeleteListener>>) {
+        self.dl = Some(dl);
+    }
+    pub fn remove_delete_listener(&mut self) {
+        self.dl = None;
     }
-    pub fn unbind(&mut self) {
-        self.cl.pop();
+    pub fn set_change_listener(&mut self, dl: Rc<RefCell<ChangeListener>>) {
+        self.cl = Some(dl);
+    }
+    pub fn remove_change_listener(&mut self) {
+        self.cl = None;
     }
 
     /// Extracts a string slice containing the entire buffer.
@@ -616,8 +626,16 @@ 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, Direction::default());
-        self.insert_str(start, text);
+        for cl in &self.cl {
+            cl.borrow_mut()
+                .replace(start, self.buf.index(range.clone()), text);
+        }
+        self.buf.drain(range);
+        if start == self.buf.len() {
+            self.buf.push_str(text);
+        } else {
+            self.buf.insert_str(start, text);
+        }
         self.pos = start + text.len();
     }
 
@@ -640,6 +658,10 @@ impl LineBuffer {
     }
 
     fn drain(&mut self, range: Range<usize>, dir: Direction) -> Drain {
+        for dl in &self.dl {
+            dl.borrow_mut()
+                .delete(range.start, &self.buf[range.start..range.end], dir);
+        }
         for cl in &self.cl {
             cl.borrow_mut()
                 .delete(range.start, &self.buf[range.start..range.end], dir);
@@ -764,7 +786,7 @@ mod test {
     use std::cell::RefCell;
     use std::rc::Rc;
     use keymap::{At, CharSearch, Word};
-    use super::{ChangeListener, Direction, LineBuffer, MAX_LINE, WordAction};
+    use super::{ChangeListener, DeleteListener, Direction, LineBuffer, MAX_LINE, WordAction};
 
     struct Listener {
         deleted_str: Option<String>,
@@ -782,13 +804,16 @@ mod test {
         }
     }
 
-    impl ChangeListener for Listener {
-        fn insert_char(&mut self, _: usize, _: char) {}
-        fn insert_str(&mut self, _: usize, _: &str) {}
+    impl DeleteListener for Listener {
         fn delete(&mut self, _: usize, string: &str, _: Direction) {
             self.deleted_str = Some(string.to_owned());
         }
     }
+    impl ChangeListener for Listener {
+        fn insert_char(&mut self, _: usize, _: char) {}
+        fn insert_str(&mut self, _: usize, _: &str) {}
+        fn replace(&mut self, _: usize, _: &str, _: &str) {}
+    }
 
     #[test]
     fn next_pos() {
diff --git a/src/undo.rs b/src/undo.rs
index 0afe419f..e2febeb1 100644
--- a/src/undo.rs
+++ b/src/undo.rs
@@ -1,7 +1,7 @@
 //! Undo API
 use std::fmt::Debug;
 
-use line_buffer::{ChangeListener, Direction, LineBuffer};
+use line_buffer::{ChangeListener, DeleteListener, Direction, LineBuffer};
 use std_unicode::str::UnicodeStr;
 use unicode_segmentation::UnicodeSegmentation;
 
@@ -10,7 +10,11 @@ enum Change {
     End,
     Insert { idx: usize, text: String }, // QuotedInsert, SelfInsert, Yank
     Delete { idx: usize, text: String }, /* BackwardDeleteChar, BackwardKillWord, DeleteChar, KillLine, KillWholeLine, KillWord, UnixLikeDiscard, ViDeleteTo */
-                                         //  Replace {idx: usize, old: String, new: String}, /* CapitalizeWord, Complete, DowncaseWord, Replace, TransposeChars, TransposeWords, UpcaseWord, YankPop */
+    Replace {
+        idx: usize,
+        old: String,
+        new: String,
+    }, /* CapitalizeWord, Complete, DowncaseWord, Replace, TransposeChars, TransposeWords, UpcaseWord, YankPop */
 }
 
 impl Change {
@@ -26,9 +30,13 @@ impl Change {
                 line.insert_str(idx, text);
                 line.set_pos(idx + text.len());
             }
-            /*Change::Replace{idx, ref old, ref new} => {
+            Change::Replace {
+                idx,
+                ref old,
+                ref new,
+            } => {
                 line.replace(idx..idx + new.len(), old);
-            }*/
+            }
         }
     }
 
@@ -44,9 +52,13 @@ impl Change {
             Change::Delete { idx, ref text } => {
                 line.delete_range(idx..idx + text.len());
             }
-            /*Change::Replace{idx, ref old, ref new} => {
+            Change::Replace {
+                idx,
+                ref old,
+                ref new,
+            } => {
                 line.replace(idx..idx + old.len(), new);
-            }*/
+            }
         }
     }
 
@@ -208,14 +220,15 @@ impl Changeset {
         graphemes.next().is_none()
     }
 
-    /*pub fn replace<S: Into<String>>(&mut self, idx: usize, old: String, new: S) {
+    pub fn replace<S: Into<String>>(&mut self, idx: usize, old: S, new: S) {
         self.redos.clear();
-        self.undos.push(Change::Replace {
-            idx: idx,
-            old: old.into(),
-            new: new.into(),
-        });
-    }*/
+        self.undos
+            .push(Change::Replace {
+                      idx: idx,
+                      old: old.into(),
+                      new: new.into(),
+                  });
+    }
 
     pub fn undo(&mut self, line: &mut LineBuffer) -> bool {
         debug!(target: "rustyline", "Changeset::undo");
@@ -281,6 +294,11 @@ impl Changeset {
     }
 }
 
+impl DeleteListener for Changeset {
+    fn delete(&mut self, idx: usize, string: &str, _: Direction) {
+        self.delete(idx, string);
+    }
+}
 impl ChangeListener for Changeset {
     fn insert_char(&mut self, idx: usize, c: char) {
         self.insert(idx, c);
@@ -288,8 +306,8 @@ impl ChangeListener for Changeset {
     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);
+    fn replace(&mut self, idx: usize, old: &str, new: &str) {
+        self.replace(idx, old, new);
     }
 }
 
@@ -346,7 +364,7 @@ mod tests {
         let mut cs = Changeset::new();
         assert_eq!(buf.as_str(), "Hello");
 
-        cs.delete(5, ", world!".to_owned());
+        cs.delete(5, ", world!");
 
         cs.undo(&mut buf);
         assert_eq!(buf.as_str(), "Hello, world!");
@@ -361,8 +379,8 @@ mod tests {
         buf.insert_str(0, "Hlo");
 
         let mut cs = Changeset::new();
-        cs.delete(1, "e".to_owned());
-        cs.delete(1, "l".to_owned());
+        cs.delete(1, "e");
+        cs.delete(1, "l");
         assert_eq!(1, cs.undos.len());
 
         cs.undo(&mut buf);
@@ -375,15 +393,15 @@ mod tests {
         buf.insert_str(0, "Hlo");
 
         let mut cs = Changeset::new();
-        cs.delete(2, "l".to_owned());
-        cs.delete(1, "e".to_owned());
+        cs.delete(2, "l");
+        cs.delete(1, "e");
         assert_eq!(1, cs.undos.len());
 
         cs.undo(&mut buf);
         assert_eq!(buf.as_str(), "Hello");
     }
 
-    /*#[test]
+    #[test]
     fn test_undo_replace() {
         let mut buf = LineBuffer::init("", 0, None);
         buf.insert_str(0, "Hello, world!");
@@ -392,12 +410,12 @@ mod tests {
 
         buf.replace(1..5, "i");
         assert_eq!(buf.as_str(), "Hi, world!");
-        cs.replace(1, "ello".to_owned(), "i");
+        cs.replace(1, "ello", "i");
 
         cs.undo(&mut buf);
         assert_eq!(buf.as_str(), "Hello, world!");
 
         cs.redo(&mut buf);
         assert_eq!(buf.as_str(), "Hi, world!");
-    }*/
+    }
 }
-- 
GitLab