From 1bfc4d8efce042f719c4d2e580a2103211b306a7 Mon Sep 17 00:00:00 2001
From: Gwenael Treguier <gwen@cas75-5-78-192-41-37.fbxo.proxad.net>
Date: Wed, 19 Aug 2015 21:41:58 +0200
Subject: [PATCH] Fix edit_history_next.

---
 src/history.rs |   5 ++
 src/lib.rs     | 225 +++++++++++++++++++++++++++++++------------------
 2 files changed, 150 insertions(+), 80 deletions(-)

diff --git a/src/history.rs b/src/history.rs
index 003481e7..cbca95eb 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -18,6 +18,11 @@ impl History {
         History { entries: VecDeque::new(), max_len: DEFAULT_HISTORY_MAX_LEN }
     }
 
+    /// Return the history entry at position `index`, starting from 0.
+    pub fn get(& self, index: usize) -> Option<&String> {
+        return self.entries.get(index)
+    }
+
     /// Add a new entry in the history.
     pub fn add(&mut self, line: &str) -> bool {
         if self.max_len == 0 {
diff --git a/src/lib.rs b/src/lib.rs
index 63050646..d5c56bd5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -39,25 +39,29 @@ use history::History;
 pub type Result<T> = result::Result<T, error::ReadlineError>;
 
 // Represent the state during line editing.
-struct State<'prompt> {
+struct State<'out, 'prompt> {
+    out: &'out mut Write,
     prompt: &'prompt str, // Prompt to display
     prompt_width: usize, // Prompt Unicode width
     buf: String, // Edited line buffer
     pos: usize, // Current cursor position (byte position)
     cols: usize, // Number of columns in terminal
     history_index: usize, // The history index we are currently editing.
+    history_end: String, // Current edited line before history browsing
     bytes: [u8; 4],
 }
 
-impl<'prompt> State<'prompt> {
-    fn new(prompt: &'prompt str, capacity: usize, cols: usize) -> State<'prompt> {
+impl<'out, 'prompt> State<'out, 'prompt> {
+    fn new(out: &'out mut Write, prompt: &'prompt str, capacity: usize, cols: usize, history_index: usize) -> State<'out, 'prompt> {
         State {
+            out: out,
             prompt: prompt,
             prompt_width: unicode_width::UnicodeWidthStr::width(prompt),
             buf: String::with_capacity(capacity),
             pos: 0,
             cols: cols,
-            history_index: 0,
+            history_index: history_index,
+            history_end: String::new(),
             bytes: [0; 4],
         }
     }
@@ -162,8 +166,8 @@ fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> {
 }
 
 /// Clear the screen. Used to handle ctrl+l
-fn clear_screen(stdout: &mut io::Stdout) -> Result<()> {
-    write_and_flush(stdout, b"\x1b[H\x1b[2J")
+fn clear_screen(out: &mut Write) -> Result<()> {
+    write_and_flush(out, b"\x1b[H\x1b[2J")
 }
 
 /// Beep, used for completion when there is nothing to complete or when all
@@ -179,7 +183,7 @@ fn width(s: &str) -> usize {
 
 /// Rewrite the currently edited line accordingly to the buffer content,
 /// cursor position, and number of columns of the terminal.
-fn refresh_line(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn refresh_line(s: &mut State) -> Result<()> {
     use std::fmt::Write;
     use unicode_width::UnicodeWidthChar;
 
@@ -209,11 +213,11 @@ fn refresh_line(s: &mut State, stdout: &mut Write) -> Result<()> {
     ab.push_str("\x1b[0K");
     // Move cursor to original position.
     ab.write_fmt(format_args!("\r\x1b[{}C", w1 + s.prompt_width)).unwrap();
-    write_and_flush(stdout, ab.as_bytes())
+    write_and_flush(s.out, ab.as_bytes())
 }
 
 /// Insert the character `ch` at cursor current position.
-fn edit_insert(s: &mut State, stdout: &mut Write, ch: char) -> Result<()> {
+fn edit_insert(s: &mut State, ch: char) -> Result<()> {
     if s.buf.len() < s.buf.capacity() {
         if s.buf.len() == s.pos {
             s.buf.push(ch);
@@ -221,14 +225,14 @@ fn edit_insert(s: &mut State, stdout: &mut Write, ch: char) -> Result<()> {
             s.pos += size;
             if s.prompt_width + width(&s.buf) < s.cols {
                 // Avoid a full update of the line in the trivial case.
-                write_and_flush(stdout, &mut s.bytes[0..size])
+                write_and_flush(s.out, &mut s.bytes[0..size])
             } else {
-                refresh_line(s, stdout)
+                refresh_line(s)
             }
         } else {
             s.buf.insert(s.pos, ch);
             s.pos += ch.len_utf8();
-            refresh_line(s, stdout)
+            refresh_line(s)
         }
     } else {
         Ok(())
@@ -236,42 +240,42 @@ fn edit_insert(s: &mut State, stdout: &mut Write, ch: char) -> Result<()> {
 }
 
 /// Move cursor on the left.
-fn edit_move_left(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_move_left(s: &mut State) -> Result<()> {
     if s.pos > 0 {
         let ch = s.buf.char_at_reverse(s.pos);
         s.pos -= ch.len_utf8();
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Move cursor on the right.
-fn edit_move_right(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_move_right(s: &mut State) -> Result<()> {
     if s.pos != s.buf.len() {
         let ch = s.buf.char_at(s.pos);
         s.pos += ch.len_utf8();
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Move cursor to the start of the line.
-fn edit_move_home(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_move_home(s: &mut State) -> Result<()> {
     if s.pos > 0 {
         s.pos = 0;
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Move cursor to the end of the line.
-fn edit_move_end(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_move_end(s: &mut State) -> Result<()> {
     if s.pos != s.buf.len() {
         s.pos = s.buf.len();
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
@@ -279,50 +283,50 @@ fn edit_move_end(s: &mut State, stdout: &mut Write) -> Result<()> {
 
 /// Delete the character at the right of the cursor without altering the cursor
 /// position. Basically this is what happens with the "Delete" keyboard key.
-fn edit_delete(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_delete(s: &mut State) -> Result<()> {
     if s.buf.len() > 0 && s.pos < s.buf.len() {
         s.buf.remove(s.pos);
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Backspace implementation.
-fn edit_backspace(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_backspace(s: &mut State) -> Result<()> {
     if s.pos > 0 && s.buf.len() > 0 {
         let ch = s.buf.char_at_reverse(s.pos);
         s.pos -= ch.len_utf8();
         s.buf.remove(s.pos);
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Kill the text from point to the end of the line.
-fn edit_kill_line(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_kill_line(s: &mut State) -> Result<()> {
     if s.buf.len() > 0 && s.pos < s.buf.len() {
         s.buf.drain(s.pos..);
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Kill backward from point to the beginning of the line.
-fn edit_discard_line(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_discard_line(s: &mut State) -> Result<()> {
     if s.pos > 0 && s.buf.len() > 0 {
         s.buf.drain(..s.pos);
         s.pos = 0;
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
 }
 
 /// Exchange the char before cursor with the character at cursor.
-fn edit_transpose_chars(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_transpose_chars(s: &mut State) -> Result<()> {
     if s.pos > 0 && s.pos < s.buf.len() {
         let ch = s.buf.remove(s.pos);
         let size = ch.len_utf8();
@@ -338,7 +342,7 @@ fn edit_transpose_chars(s: &mut State, stdout: &mut Write) -> Result<()> {
                 s.pos -= osize - size;
             }
         }
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
@@ -346,7 +350,7 @@ fn edit_transpose_chars(s: &mut State, stdout: &mut Write) -> Result<()> {
 
 /// Delete the previous word, maintaining the cursor at the start of the
 /// current word.
-fn edit_delete_prev_word(s: &mut State, stdout: &mut Write) -> Result<()> {
+fn edit_delete_prev_word(s: &mut State) -> Result<()> {
     if s.pos > 0 {
         let old_pos = s.pos;
         let mut ch = s.buf.char_at_reverse(s.pos);
@@ -359,7 +363,7 @@ fn edit_delete_prev_word(s: &mut State, stdout: &mut Write) -> Result<()> {
             ch = s.buf.char_at_reverse(s.pos);
         }
         s.buf.drain(s.pos..old_pos);
-        refresh_line(s, stdout)
+        refresh_line(s)
     } else {
         Ok(())
     }
@@ -367,12 +371,30 @@ fn edit_delete_prev_word(s: &mut State, stdout: &mut Write) -> Result<()> {
 
 /// Substitute the currently edited line with the next or previous history
 /// entry.
-fn edit_history_next(s: &mut State, history: &mut History, stdout: &mut Write, prev: bool) -> Result<()> {
+fn edit_history_next(s: &mut State, history: &mut History, prev: bool) -> Result<()> {
     if history.len() > 1 {
-        unimplemented!();
-        //s.buf = ;
-        //s.pos = s.buf.len();
-        //refresh_line(s, stdout)
+        if s.history_index == history.len() {
+            if prev {
+                // Save the current edited line before to overwrite it
+                s.history_end = s.buf.clone();
+            } else {
+                return Ok(());
+            }
+        } else if s.history_index == 0 && prev {
+            return Ok(());
+        }
+        if prev {
+            s.history_index -= 1;
+        } else {
+            s.history_index += 1;
+        }
+        if s.history_index < history.len() {
+            s.buf = history.get(s.history_index).unwrap().clone();
+        } else {
+            s.buf = s.history_end.clone(); // TODO how to avoid cloning?
+        }
+        s.pos = s.buf.len();
+        refresh_line(s)
     } else {
         Ok(())
     }
@@ -385,48 +407,48 @@ fn readline_edit(prompt: &str, history: &mut Option<History>) -> Result<String>
     let mut stdout = io::stdout();
     try!(write_and_flush(&mut stdout, prompt.as_bytes()));
 
-    let mut s = State::new(prompt, MAX_LINE, get_columns());
+    let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.as_mut().map_or(0, |h| h.len()));
     let stdin = io::stdin();
     let mut chars = stdin.lock().chars();
     loop {
         let ch = try!(chars.next().unwrap());
         match char_to_key_press(ch) {
-            KeyPress::CTRL_A => try!(edit_move_home(&mut s, &mut stdout)), // Move to the beginning of line.
-            KeyPress::CTRL_B => try!(edit_move_left(&mut s, &mut stdout)), // Move back a character.
+            KeyPress::CTRL_A => try!(edit_move_home(&mut s)), // Move to the beginning of line.
+            KeyPress::CTRL_B => try!(edit_move_left(&mut s)), // Move back a character.
             KeyPress::CTRL_C => {
                 return Err(from_errno(Errno::EAGAIN))
             },
             KeyPress::CTRL_D => {
                 if s.buf.len() > 0 { // Delete one character at point.
-                    try!(edit_delete(&mut s, &mut stdout))
+                    try!(edit_delete(&mut s))
                 } else {
                     break
                 }
             },
-            KeyPress::CTRL_E => try!(edit_move_end(&mut s, &mut stdout)), // Move to the end of line.
-            KeyPress::CTRL_F => try!(edit_move_right(&mut s, &mut stdout)), // Move forward a character.
-            KeyPress::CTRL_H | KeyPress::BACKSPACE => try!(edit_backspace(&mut s, &mut stdout)), // Delete one character backward.
-            KeyPress::CTRL_K => try!(edit_kill_line(&mut s, &mut stdout)), // Kill the text from point to the end of the line.
+            KeyPress::CTRL_E => try!(edit_move_end(&mut s)), // Move to the end of line.
+            KeyPress::CTRL_F => try!(edit_move_right(&mut s)), // Move forward a character.
+            KeyPress::CTRL_H | KeyPress::BACKSPACE => try!(edit_backspace(&mut s)), // Delete one character backward.
+            KeyPress::CTRL_K => try!(edit_kill_line(&mut s)), // Kill the text from point to the end of the line.
             KeyPress::CTRL_L => { // Clear the screen leaving the current line at the top of the screen.
-                try!(clear_screen(&mut stdout));
-                try!(refresh_line(&mut s, &mut stdout))
+                try!(clear_screen(s.out));
+                try!(refresh_line(&mut s))
             },
             KeyPress::CTRL_N => { // Fetch the next command from the history list.
                 if history.is_some() {
-                    try!(edit_history_next(&mut s, history.as_mut().unwrap(), &mut stdout, false))
+                    try!(edit_history_next(&mut s, history.as_mut().unwrap(), false))
                 }
             },
             KeyPress::CTRL_P => { // Fetch the previous command from the history list.
                 if history.is_some() {
-                    try!(edit_history_next(&mut s, history.as_mut().unwrap(), &mut stdout, true))
+                    try!(edit_history_next(&mut s, history.as_mut().unwrap(), true))
                 }
             },
-            KeyPress::CTRL_T => try!(edit_transpose_chars(&mut s, &mut stdout)), // Exchange the char before cursor with the character at cursor.
-            KeyPress::CTRL_U => try!(edit_discard_line(&mut s, &mut stdout)), // Kill backward from point to the beginning of the line.
-            KeyPress::CTRL_W => try!(edit_delete_prev_word(&mut s, &mut stdout)), // Kill the word behind point, using white space as a word boundary
+            KeyPress::CTRL_T => try!(edit_transpose_chars(&mut s)), // Exchange the char before cursor with the character at cursor.
+            KeyPress::CTRL_U => try!(edit_discard_line(&mut s)), // Kill backward from point to the beginning of the line.
+            KeyPress::CTRL_W => try!(edit_delete_prev_word(&mut s)), // Kill the word behind point, using white space as a word boundary
             KeyPress::ESC    => print!("Pressed esc"),
             KeyPress::ENTER  => break, // Accept the line regardless of where the cursor is.
-            _      => try!(edit_insert(&mut s, &mut stdout, ch)), // Insert the character typed.
+            _      => try!(edit_insert(&mut s, ch)), // Insert the character typed.
         }
     }
     Ok(s.buf)
@@ -467,107 +489,150 @@ pub fn readline(prompt: &str, history: &mut Option<History>) -> Result<String> {
 
 #[cfg(test)]
 mod test {
+    use std::io::Write;
+    use history::History;
     use State;
 
-    fn init_state(line: &str, pos: usize, cols: usize) -> State<'static> {
+    fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> {
         State {
+            out : out,
             prompt: "",
             prompt_width: 0,
             buf: String::from(line),
             pos: pos,
             cols: cols,
             history_index: 0,
+            history_end: String::new(),
             bytes: [0; 4],
         }
     }
 
     #[test]
     fn insert() {
-        let mut s = State::new("", 128, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_insert(&mut s, &mut stdout, 'α').unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = State::new(&mut out, "", 128, 80, 0);
+        super::edit_insert(&mut s, 'α').unwrap();
         assert_eq!("α", s.buf);
         assert_eq!(2, s.pos);
 
-        super::edit_insert(&mut s, &mut stdout, 'ß').unwrap();
+        super::edit_insert(&mut s, 'ß').unwrap();
         assert_eq!("αß", s.buf);
         assert_eq!(4, s.pos);
 
         s.pos = 0;
-        super::edit_insert(&mut s, &mut stdout, 'γ').unwrap();
+        super::edit_insert(&mut s, 'γ').unwrap();
         assert_eq!("γαß", s.buf);
         assert_eq!(2, s.pos);
     }
 
     #[test]
     fn moves() {
-        let mut s = init_state("αß", 4, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_move_left(&mut s, &mut stdout).unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = init_state(&mut out, "αß", 4, 80);
+        super::edit_move_left(&mut s).unwrap();
         assert_eq!("αß", s.buf);
         assert_eq!(2, s.pos);
 
-        super::edit_move_right(&mut s, &mut stdout).unwrap();
+        super::edit_move_right(&mut s).unwrap();
         assert_eq!("αß", s.buf);
         assert_eq!(4, s.pos);
 
-        super::edit_move_home(&mut s, &mut stdout).unwrap();
+        super::edit_move_home(&mut s).unwrap();
         assert_eq!("αß", s.buf);
         assert_eq!(0, s.pos);
 
-        super::edit_move_end(&mut s, &mut stdout).unwrap();
+        super::edit_move_end(&mut s).unwrap();
         assert_eq!("αß", s.buf);
         assert_eq!(4, s.pos);
     }
 
     #[test]
     fn delete() {
-        let mut s = init_state("αß", 2, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_delete(&mut s, &mut stdout).unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = init_state(&mut out, "αß", 2, 80);
+        super::edit_delete(&mut s).unwrap();
         assert_eq!("α", s.buf);
         assert_eq!(2, s.pos);
 
-        super::edit_backspace(&mut s, &mut stdout).unwrap();
+        super::edit_backspace(&mut s).unwrap();
         assert_eq!("", s.buf);
         assert_eq!(0, s.pos);
     }
 
     #[test]
     fn kill() {
-        let mut s = init_state("αßγδε", 6, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_kill_line(&mut s, &mut stdout).unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = init_state(&mut out, "αßγδε", 6, 80);
+        super::edit_kill_line(&mut s).unwrap();
         assert_eq!("αßγ", s.buf);
         assert_eq!(6, s.pos);
 
         s.pos = 4;
-        super::edit_discard_line(&mut s, &mut stdout).unwrap();
+        super::edit_discard_line(&mut s).unwrap();
         assert_eq!("γ", s.buf);
         assert_eq!(0, s.pos);
     }
 
     #[test]
     fn transpose() {
-        let mut s = init_state("aßc", 1, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_transpose_chars(&mut s, &mut stdout).unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = init_state(&mut out, "aßc", 1, 80);
+        super::edit_transpose_chars(&mut s).unwrap();
         assert_eq!("ßac", s.buf);
         assert_eq!(3, s.pos);
 
         s.buf = String::from("aßc");
         s.pos = 3;
-        super::edit_transpose_chars(&mut s, &mut stdout).unwrap();
+        super::edit_transpose_chars(&mut s).unwrap();
         assert_eq!("acß", s.buf);
         assert_eq!(2, s.pos);
     }
 
     #[test]
     fn delete_prev_word() {
-        let mut s = init_state("a ß  c", 6, 80);
-        let mut stdout = ::std::io::sink();
-        super::edit_delete_prev_word(&mut s, &mut stdout).unwrap();
+        let mut out = ::std::io::sink();
+        let mut s = init_state(&mut out, "a ß  c", 6, 80);
+        super::edit_delete_prev_word(&mut s).unwrap();
         assert_eq!("a c", s.buf);
         assert_eq!(2, s.pos);
     }
+
+    #[test]
+    fn edit_history_next() {
+        let mut out = ::std::io::sink();
+        let line = "current edited line";
+        let mut s = init_state(&mut out, line, 6, 80);
+        let mut history = History::new();
+        history.add("line0");
+        history.add("line1");
+        s.history_index = history.len();
+        s.buf = String::from(line);
+
+        for _ in 0..2 {
+            super::edit_history_next(&mut s, &mut history, false).unwrap();
+            assert_eq!(line, s.buf);
+        }
+
+        super::edit_history_next(&mut s, &mut history, true).unwrap();
+        assert_eq!(line, s.history_end);
+        assert_eq!(1, s.history_index);
+        assert_eq!("line1", s.buf);
+
+        for _ in 0..2 {
+            super::edit_history_next(&mut s, &mut history, true).unwrap();
+            assert_eq!(line, s.history_end);
+            assert_eq!(0, s.history_index);
+            assert_eq!("line0", s.buf);
+        }
+
+        super::edit_history_next(&mut s, &mut history, false).unwrap();
+        assert_eq!(line, s.history_end);
+        assert_eq!(1, s.history_index);
+        assert_eq!("line1", s.buf);
+
+        super::edit_history_next(&mut s, &mut history, false).unwrap();
+        assert_eq!(line, s.history_end);
+        assert_eq!(2, s.history_index);
+        assert_eq!(line, s.buf);
+    }
 }
-- 
GitLab