Repository: kkawakam/rustyline
Pull request #334 (2020-02-12 13:54:48)

Bugfixes/consistency improvements for multiline stuff
- Movement::{BeginningOfLine,EndOfLine,etc} now actually go to beginning/end of the line.
- Movement::BeginningOfBuffer, etc was added to get the original functionality back.
- Fix some off-by-one bugs in the line movement code.
- Make it easier to test the multiline functionality by adding a matching bracket validator, and using it in the examples.

gwenn: zsh seems to escape only '\n':
```
echo "\test\
t"
echo "\test\\
t"
```
gwenn: But zsh is buggy if line ends with a backslash:
```sh
$ echo test\\
$ # a space is appended in history 
```
thomcc: That part is just so that if you have a multiline history item, after saving/loading it doesn't come back as individual lines.
gwenn: With bash, Ctrl-A/Ctrl-E|Ctrl-U/Ctrl-K moves|kills to begin/end of buffer even with multiline input.
Maybe we should keep the current behaviour ?
thomcc: It's a mix with what does what -- fish-shell and... every text editor supporting these have it go to the beginning of the line.

Also, as an anecdote: I use these very commonly and find going to the beginning of the buffer very frustrating (as it behaves differently than text editors).
gwenn: Ok.
tailhook: Anything left for this PR to get merged (except resolving conflicts of course)? We already use the code from the branch, and are pretty happy with it.
thomcc: Sorry, I was mistaken that I'd have time last weekend, but I have nothing else planned for this weekend/tomorrow afternoon and will rebase it.

Patch:
diff --git a/examples/example.rs b/examples/example.rs
index 4d15a43..37582e1 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -5,19 +5,22 @@ use rustyline::config::OutputStreamType;
 use rustyline::error::ReadlineError;
 use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
 use rustyline::highlight::{PromptInfo};
+use rustyline::validate::{self, Validator, MatchingBracketValidator};
 use rustyline::hint::{Hinter, HistoryHinter};
 use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
-use rustyline_derive::{Helper, Validator};
+use rustyline_derive::Helper;
 
-#[derive(Helper, Validator)]
+#[derive(Helper)]
 struct MyHelper {
     completer: FilenameCompleter,
     highlighter: MatchingBracketHighlighter,
+    validator: MatchingBracketValidator,
     hinter: HistoryHinter,
     colored_prompt: String,
     continuation_prompt: String,
 }
 
+
 impl Completer for MyHelper {
     type Candidate = Pair;
 
@@ -71,6 +74,16 @@ impl Highlighter for MyHelper {
     }
 }
 
+impl Validator for MyHelper {
+    fn validate(&self, ctx: &mut validate::ValidationContext) -> rustyline::Result<validate::ValidationResult> {
+        self.validator.validate(ctx)
+    }
+
+    fn validate_while_typing(&self) -> bool {
+        self.validator.validate_while_typing()
+    }
+}
+
 // To debug rustyline:
 // RUST_LOG=rustyline=debug cargo run --example example 2> debug.log
 fn main() -> rustyline::Result<()> {
@@ -87,6 +100,7 @@ fn main() -> rustyline::Result<()> {
         hinter: HistoryHinter {},
         colored_prompt: "  0> ".to_owned(),
         continuation_prompt: "\x1b[1;32m...> \x1b[0m".to_owned(),
+        validator: MatchingBracketValidator::new(),
     };
     let mut rl = Editor::with_config(config);
     rl.set_helper(Some(h));
diff --git a/src/edit.rs b/src/edit.rs
index 89f56da..902da6f 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -451,6 +451,24 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
         }
     }
 
+    /// Move cursor to the start of the buffer.
+    pub fn edit_move_buffer_start(&mut self) -> Result<()> {
+        if self.line.move_buffer_start() {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Move cursor to the end of the buffer.
+    pub fn edit_move_buffer_end(&mut self) -> Result<()> {
+        if self.line.move_buffer_end() {
+            self.move_cursor()
+        } else {
+            Ok(())
+        }
+    }
+
     pub fn edit_kill(&mut self, mvt: &Movement) -> Result<()> {
         if self.line.kill(mvt) {
             self.refresh_line()
diff --git a/src/history.rs b/src/history.rs
index b1f9661..81fb520 100644
--- a/src/history.rs
+++ b/src/history.rs
@@ -110,6 +110,10 @@ impl History {
         }
     }
 
+    // New multiline-aware history files start with `#V2\n` and have newlines
+    // and backslashes escaped in them.
+    const FILE_VERSION_V2: &'static str = "#V2";
+
     /// Save the history in the specified file.
     // TODO append_history
     // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
@@ -127,10 +131,12 @@ impl History {
         let file = f?;
         fix_perm(&file);
         let mut wtr = BufWriter::new(file);
+        wtr.write_all(Self::FILE_VERSION_V2.as_bytes())?;
         for entry in &self.entries {
-            wtr.write_all(entry.as_bytes())?;
             wtr.write_all(b"\n")?;
+            wtr.write_all(entry.replace('\\', "\\\\").replace('\n', "\\n").as_bytes())?;
         }
+        wtr.write_all(b"\n")?;
         // https://github.com/rust-lang/rust/issues/32677#issuecomment-204833485
         wtr.flush()?;
         Ok(())
@@ -145,8 +151,26 @@ impl History {
 
         let file = File::open(&path)?;
         let rdr = BufReader::new(file);
-        for line in rdr.lines() {
-            self.add(line?); // TODO truncate to MAX_LINE
+        let mut lines = rdr.lines();
+        let mut v2 = false;
+        if let Some(first) = lines.next() {
+            let line = first?;
+            if line == Self::FILE_VERSION_V2 {
+                v2 = true;
+            } else {
+                self.add(line);
+            }
+        }
+        for line in lines {
+            let line = if v2 {
+                line?.replace("\\n", "\n").replace("\\\\", "\\")
+            } else {
+                line?
+            };
+            if line.is_empty() {
+                continue;
+            }
+            self.add(line); // TODO truncate to MAX_LINE
         }
         Ok(())
     }
@@ -315,11 +339,41 @@ mod tests {
     #[test]
     fn save() {
         let mut history = init();
+        assert!(history.add("line\nfour \\ abc"));
         let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
         let history_path = td.path().join(".history");
 
         history.save(&history_path).unwrap();
+        let mut history2 = History::new();
+        history2.load(&history_path).unwrap();
+        for (a, b) in history.entries.iter().zip(history2.entries.iter()) {
+            assert_eq!(a, b);
+        }
+
+        td.close().unwrap();
+    }
+
+    #[test]
+    fn load_legacy() {
+        use std::io::Write;
+        let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
+        let history_path = td.path().join(".history_v1");
+        {
+            let mut legacy = std::fs::File::create(&history_path).unwrap();
+            // Some data we'd accidentally corrupt if we got the version wrong
+            let data = b"\
+                test\\n \\abc \\123\n\
+                123\\n\\\\n\n\
+                abcde
+            ";
+            legacy.write_all(data).unwrap();
+            legacy.flush().unwrap();
+        }
+        let mut history = History::new();
         history.load(&history_path).unwrap();
+        assert_eq!(history.entries[0], "test\\n \\abc \\123");
+        assert_eq!(history.entries[1], "123\\n\\\\n");
+        assert_eq!(history.entries[2], "abcde");
         td.close().unwrap();
     }
 
diff --git a/src/keymap.rs b/src/keymap.rs
index ffe0700..987dc35 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -255,6 +255,11 @@ pub enum Movement {
     LineUp(RepeatCount),
     /// move to the same column on the next line
     LineDown(RepeatCount),
+    WholeBuffer, // not really a movement
+    /// beginning-of-buffer
+    BeginningOfBuffer,
+    /// end-of-buffer
+    EndOfBuffer,
 }
 
 impl Movement {
@@ -278,6 +283,9 @@ impl Movement {
             Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)),
             Movement::LineUp(previous) => Movement::LineUp(repeat_count(previous, new)),
             Movement::LineDown(previous) => Movement::LineDown(repeat_count(previous, new)),
+            Movement::WholeBuffer => Movement::WholeBuffer,
+            Movement::BeginningOfBuffer => Movement::BeginningOfBuffer,
+            Movement::EndOfBuffer => Movement::EndOfBuffer,
         }
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 015932e..7fcfde8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -620,6 +620,14 @@ fn readline_edit<H: Helper>(
             Cmd::Move(Movement::LineDown(n)) => {
                 s.edit_move_line_down(n)?;
             }
+            Cmd::Move(Movement::BeginningOfBuffer) => {
+                // Move to the start of the buffer.
+                s.edit_move_buffer_start()?
+            }
+            Cmd::Move(Movement::EndOfBuffer) => {
+                // Move to the end of the buffer.
+                s.edit_move_buffer_end()?
+            }
             Cmd::DowncaseWord => {
                 // lowercase word after point
                 s.edit_word(WordAction::LOWERCASE)?
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 2cb4ed5..d0dd26a 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -160,6 +160,23 @@ impl LineBuffer {
         }
     }
 
+    fn end_of_line(&self) -> usize {
+        if let Some(n) = self.buf[self.pos..].find('\n') {
+            n + self.pos
+        } else {
+            self.buf.len()
+        }
+    }
+
+    fn start_of_line(&self) -> usize {
+        if let Some(i) = self.buf[..self.pos].rfind('\n') {
+            // `i` is before the new line, e.g. at the end of the previous one.
+            i + 1
+        } else {
+            0
+        }
+    }
+
     /// Returns the character at current cursor position.
     pub(crate) fn grapheme_at_cursor(&self) -> Option<&str> {
         if self.pos == self.buf.len() {
@@ -274,8 +291,8 @@ impl LineBuffer {
         }
     }
 
-    /// Move cursor to the start of the line.
-    pub fn move_home(&mut self) -> bool {
+    /// Move cursor to the start of the buffer.
+    pub fn move_buffer_start(&mut self) -> bool {
         if self.pos > 0 {
             self.pos = 0;
             true
@@ -284,8 +301,8 @@ impl LineBuffer {
         }
     }
 
-    /// Move cursor to the end of the line.
-    pub fn move_end(&mut self) -> bool {
+    /// Move cursor to the end of the buffer.
+    pub fn move_buffer_end(&mut self) -> bool {
         if self.pos == self.buf.len() {
             false
         } else {
@@ -294,6 +311,28 @@ impl LineBuffer {
         }
     }
 
+    /// Move cursor to the start of the line.
+    pub fn move_home(&mut self) -> bool {
+        let start = self.start_of_line();
+        if self.pos > start {
+            self.pos = start;
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Move cursor to the end of the line.
+    pub fn move_end(&mut self) -> bool {
+        let end = self.end_of_line();
+        if self.pos == end {
+            false
+        } else {
+            self.pos = end;
+            true
+        }
+    }
+
     /// Is cursor at the end of input (whitespaces after cursor is discarded)
     pub fn is_end_of_input(&self) -> bool {
         self.pos >= self.buf.trim_end().len()
@@ -332,6 +371,22 @@ impl LineBuffer {
 
     /// Kill the text from point to the end of the line.
     pub fn kill_line(&mut self) -> bool {
+        if !self.buf.is_empty() && self.pos < self.buf.len() {
+            let start = self.pos;
+            let end = self.end_of_line();
+            if start == end {
+                self.delete(1);
+            } else {
+                self.drain(start..end, Direction::Forward);
+            }
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Kill the text from point to the end of the buffer.
+    pub fn kill_buffer(&mut self) -> bool {
         if !self.buf.is_empty() && self.pos < self.buf.len() {
             let start = self.pos;
             let end = self.buf.len();
@@ -344,6 +399,23 @@ impl LineBuffer {
 
     /// Kill backward from point to the beginning of the line.
     pub fn discard_line(&mut self) -> bool {
+        if self.pos > 0 && !self.buf.is_empty() {
+            let start = self.start_of_line();
+            let end = self.pos;
+            if end == start {
+                self.backspace(1)
+            } else {
+                self.drain(start..end, Direction::Backward);
+                self.pos = start;
+                true
+            }
+        } else {
+            false
+        }
+    }
+
+    /// Kill backward from point to the beginning of the buffer.
+    pub fn discard_buffer(&mut self) -> bool {
         if self.pos > 0 && !self.buf.is_empty() {
             let end = self.pos;
             self.drain(0..end, Direction::Backward);
@@ -491,19 +563,21 @@ impl LineBuffer {
             Some(off) => {
                 let column = self.buf[off + 1..self.pos].graphemes(true).count();
 
-                let mut dest_start = self.buf[..off].rfind('\n').unwrap_or(0);
+                let mut dest_start = self.buf[..off].rfind('\n').map(|n| n + 1).unwrap_or(0);
                 let mut dest_end = off;
                 for _ in 1..n {
                     if dest_start == 0 {
                         break;
                     }
                     dest_end = dest_start - 1;
-                    dest_start = self.buf[..dest_end].rfind('\n').unwrap_or(0);
+                    dest_start = self.buf[..dest_end].rfind('\n').map(|n| n + 1)
+                        .unwrap_or(0);
                 }
-
-                self.pos = self.buf[dest_start..dest_end]
+                let gidx =self.buf[dest_start..dest_end]
                     .grapheme_indices(true)
-                    .nth(column)
+                    .nth(column);
+
+                self.pos = gidx
                     .map(|(idx, _)| dest_start + idx)
                     .unwrap_or(off); // if there's no enough columns
                 true
@@ -561,9 +635,13 @@ impl LineBuffer {
     pub fn move_to_line_down(&mut self, n: RepeatCount) -> bool {
         match self.buf[self.pos..].find('\n') {
             Some(off) => {
-                let line_start = self.buf[..self.pos].rfind('\n').unwrap_or(0);
-                let column = self.buf[line_start..self.pos].graphemes(true).count();
-                let mut dest_start = self.pos + off + 1;
+                let line_start = self.buf[..self.pos]
+                    .rfind('\n')
+                    .map(|n| n + 1)
+                    .unwrap_or(0);
+                let column = self.buf[line_start..self.pos]
+                    .graphemes(true).count();
+                let mut dest_start = self.pos + off+1;
                 let mut dest_end = self.buf[dest_start..]
                     .find('\n')
                     .map(|v| dest_start + v)
@@ -826,12 +904,21 @@ impl LineBuffer {
             return None;
         }
         match *mvt {
-            Movement::WholeLine => Some(self.buf.clone()),
+            Movement::WholeLine => {
+                let start = self.start_of_line();
+                let end = self.end_of_line();
+                if start == end {
+                    None
+                } else {
+                    Some(self.buf[start..self.pos].to_owned())
+                }
+            },
             Movement::BeginningOfLine => {
-                if self.pos == 0 {
+                let start = self.start_of_line();
+                if self.pos == start {
                     None
                 } else {
-                    Some(self.buf[..self.pos].to_owned())
+                    Some(self.buf[start..self.pos].to_owned())
                 }
             }
             Movement::ViFirstPrint => {
@@ -844,12 +931,34 @@ impl LineBuffer {
                 }
             }
             Movement::EndOfLine => {
+                let end = self.end_of_line();
+                if self.pos == end {
+                    None
+                } else {
+                    Some(self.buf[self.pos..end].to_owned())
+                }
+            }
+            Movement::EndOfBuffer => {
                 if self.pos == self.buf.len() {
                     None
                 } else {
                     Some(self.buf[self.pos..].to_owned())
                 }
             }
+            Movement::WholeBuffer => {
+                if self.buf.is_empty() {
+                    None
+                } else {
+                    Some(self.buf.clone())
+                }
+            }
+            Movement::BeginningOfBuffer => {
+                if self.pos == 0 {
+                    None
+                } else {
+                    Some(self.buf[..self.pos].to_owned())
+                }
+            }
             Movement::BackwardWord(n, word_def) => {
                 if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) {
                     Some(self.buf[pos..self.pos].to_owned())
@@ -972,6 +1081,18 @@ impl LineBuffer {
             Movement::ViFirstPrint => {
                 false // TODO
             }
+            Movement::EndOfBuffer => {
+                // Kill the text from point to the end of the buffer.
+                self.kill_buffer()
+            }
+            Movement::BeginningOfBuffer => {
+                // Kill backward from point to the beginning of the buffer.
+                self.discard_buffer()
+            }
+            Movement::WholeBuffer => {
+                self.move_buffer_start();
+                self.kill_buffer()
+            }
         };
         if notify {
             if let Some(dl) = self.dl.as_ref() {
@@ -1141,6 +1262,56 @@ mod test {
         assert_eq!(true, ok);
     }
 
+    #[test]
+    fn move_home_end_multiline() {
+        let text = "αa\nsdf ßc\nasdf";
+        let mut s = LineBuffer::init(text, 7, None);
+        let ok = s.move_home();
+        assert_eq!(text, s.buf);
+        assert_eq!(4, s.pos);
+        assert_eq!(true, ok);
+
+        let ok = s.move_home();
+        assert_eq!(text, s.buf);
+        assert_eq!(4, s.pos);
+        assert_eq!(false, ok);
+
+        let ok = s.move_end();
+        assert_eq!(text, s.buf);
+        assert_eq!(11, s.pos);
+        assert_eq!(true, ok);
+
+        let ok = s.move_end();
+        assert_eq!(text, s.buf);
+        assert_eq!(11, s.pos);
+        assert_eq!(false, ok);
+    }
+
+    #[test]
+    fn move_buffer_multiline() {
+        let text = "αa\nsdf ßc\nasdf";
+        let mut s = LineBuffer::init(text, 7, None);
+        let ok = s.move_buffer_start();
+        assert_eq!(text, s.buf);
+        assert_eq!(0, s.pos);
+        assert_eq!(true, ok);
+
+        let ok = s.move_buffer_start();
+        assert_eq!(text, s.buf);
+        assert_eq!(0, s.pos);
+        assert_eq!(false, ok);
+
+        let ok = s.move_buffer_end();
+        assert_eq!(text, s.buf);
+        assert_eq!(text.len(), s.pos);
+        assert_eq!(true, ok);
+
+        let ok = s.move_buffer_end();
+        assert_eq!(text, s.buf);
+        assert_eq!(text.len(), s.pos);
+        assert_eq!(false, ok);
+    }
+
     #[test]
     fn move_grapheme() {
         let mut s = LineBuffer::init("ag̈", 4, None);
@@ -1188,6 +1359,62 @@ mod test {
         cl.borrow().assert_deleted_str_eq("αß");
     }
 
+    #[test]
+    fn kill_multiline() {
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αß\nγδ 12\nε f4", 7, Some(cl.clone()));
+
+        let ok = s.kill_line();
+        assert_eq!("αß\nγ\nε f4", s.buf);
+        assert_eq!(7, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("δ 12");
+
+        let ok = s.kill_line();
+        assert_eq!("αß\nγε f4", s.buf);
+        assert_eq!(7, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("\n");
+
+        let ok = s.kill_line();
+        assert_eq!("αß\nγ", s.buf);
+        assert_eq!(7, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("ε f4");
+
+        let ok = s.kill_line();
+        assert_eq!(7, s.pos);
+        assert_eq!(false, ok);
+    }
+
+    #[test]
+    fn discard_multiline() {
+        let cl = Listener::new();
+        let mut s = LineBuffer::init("αß\nc γδε", 9, Some(cl.clone()));
+
+        let ok = s.discard_line();
+        assert_eq!("αß\nδε", s.buf);
+        assert_eq!(5, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("c γ");
+
+        let ok = s.discard_line();
+        assert_eq!("αßδε", s.buf);
+        assert_eq!(4, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("\n");
+
+        let ok = s.discard_line();
+        assert_eq!("δε", s.buf);
+        assert_eq!(0, s.pos);
+        assert_eq!(true, ok);
+        cl.borrow().assert_deleted_str_eq("αß");
+
+        let ok = s.discard_line();
+        assert_eq!(0, s.pos);
+        assert_eq!(false, ok);
+    }
+
     #[test]
     fn transpose() {
         let mut s = LineBuffer::init("aßc", 1, None);
@@ -1550,4 +1777,44 @@ mod test {
         let mut s = LineBuffer::init("ßeta / __", 9, None);
         assert!(!s.transpose_words(1));
     }
+
+    #[test]
+    fn move_by_line() {
+        let text = "aa123\nsdf bc\nasdf";
+        let mut s = LineBuffer::init(text, 14, None);
+        // move up
+        let ok = s.move_to_line_up(1);
+        assert_eq!(7, s.pos);
+        assert!(ok);
+
+        let ok = s.move_to_line_up(1);
+        assert_eq!(1, s.pos);
+        assert!(ok);
+
+        let ok = s.move_to_line_up(1);
+        assert_eq!(1, s.pos);
+        assert!(!ok);
+
+        // move down
+        let ok = s.move_to_line_down(1);
+        assert_eq!(7, s.pos);
+        assert!(ok);
+
+        let ok = s.move_to_line_down(1);
+        assert_eq!(14, s.pos);
+        assert!(ok);
+
+        let ok = s.move_to_line_down(1);
+        assert_eq!(14, s.pos);
+        assert!(!ok);
+
+        // move by multiple steps
+        let ok = s.move_to_line_up(2);
+        assert_eq!(1, s.pos);
+        assert!(ok);
+
+        let ok = s.move_to_line_down(2);
+        assert_eq!(14, s.pos);
+        assert!(ok);
+    }
 }
diff --git a/src/validate.rs b/src/validate.rs
index e718aa0..b6348a3 100644
--- a/src/validate.rs
+++ b/src/validate.rs
@@ -79,3 +79,51 @@ impl<'v, V: ?Sized + Validator> Validator for &'v V {
         (**self).validate_while_typing()
     }
 }
+
+/// Simple matching bracket validator.
+#[derive(Default)]
+pub struct MatchingBracketValidator {
+    _priv: (),
+}
+
+impl MatchingBracketValidator {
+    pub fn new() -> Self {
+        Self { _priv: () }
+    }
+}
+
+impl Validator for MatchingBracketValidator {
+    fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
+        Ok(validate_brackets(ctx.input()))
+    }
+}
+
+fn validate_brackets(input: &str) -> ValidationResult {
+    let mut stack = vec![];
+    for c in input.chars() {
+        match c {
+            '(' | '[' | '{' => stack.push(c),
+            ')' | ']' | '}' => match (stack.pop(), c) {
+                (Some('('), ')') | (Some('['), ']') | (Some('{'), '}') => {}
+                (Some(wanted), _) => {
+                    return ValidationResult::Invalid(Some(format!(
+                        "Mismatched brackets: {:?} is not properly closed",
+                        wanted
+                    )))
+                }
+                (None, c) => {
+                    return ValidationResult::Invalid(Some(format!(
+                        "Mismatched brackets: {:?} is unpaired",
+                        c
+                    )))
+                }
+            },
+            _ => {}
+        }
+    }
+    if stack.is_empty() {
+        ValidationResult::Valid(None)
+    } else {
+        ValidationResult::Incomplete
+    }
+}


Test patch:
diff --git a/src/test/emacs.rs b/src/test/emacs.rs
index dbd9d33..b9d9e99 100644
--- a/src/test/emacs.rs
+++ b/src/test/emacs.rs
@@ -11,6 +11,12 @@ fn ctrl_a() {
         &[KeyPress::Ctrl('A'), KeyPress::Enter],
         ("", "Hi"),
     );
+    assert_cursor(
+        EditMode::Emacs,
+        ("test test\n123", "foo"),
+        &[KeyPress::Ctrl('A'), KeyPress::Enter],
+        ("test test\n", "123foo"),
+    );
 }
 
 #[test]
@@ -21,6 +27,12 @@ fn ctrl_e() {
         &[KeyPress::Ctrl('E'), KeyPress::Enter],
         ("Hi", ""),
     );
+    assert_cursor(
+        EditMode::Emacs,
+        ("foo", "test test\n123"),
+        &[KeyPress::Ctrl('E'), KeyPress::Enter],
+        ("footest test", "\n123"),
+    );
 }
 
 #[test]
@@ -146,8 +158,66 @@ fn ctrl_k() {
         &[KeyPress::Ctrl('K'), KeyPress::Enter],
         ("B", ""),
     );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", "foo\nbar"),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("Hi", "\nbar"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", "\nbar"),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("Hi", "bar"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", "bar"),
+        &[KeyPress::Ctrl('K'), KeyPress::Enter],
+        ("Hi", ""),
+    );
 }
 
+
+#[test]
+fn ctrl_u() {
+    assert_cursor(
+        EditMode::Emacs,
+        ("", "Hi"),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("", "Hi"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("Hi", ""),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("", ""),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("B", "ye"),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("", "ye"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("foo\nbar", "Hi"),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("foo\n", "Hi"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("foo\n", "Hi"),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("foo", "Hi"),
+    );
+    assert_cursor(
+        EditMode::Emacs,
+        ("foo", "Hi"),
+        &[KeyPress::Ctrl('U'), KeyPress::Enter],
+        ("", "Hi"),
+    );
+}
 #[test]
 fn ctrl_n() {
     assert_history(