Commit a81a6386 authored by Liam's avatar Liam Committed by GitHub
Browse files

Merge pull request #30 from iamcodemaker/vipull

enhance Vi KeyMap with most Vi movement commands
parents 4ee83a2d 22eeefa0
......@@ -7,6 +7,8 @@ use std::fmt::{self, Write as FmtWrite};
pub enum Action {
Insert { start: usize, text: Vec<char> },
Remove { start: usize, text: Vec<char> },
StartGroup,
EndGroup,
}
impl Action {
......@@ -16,6 +18,7 @@ impl Action {
Action::Remove { start, ref text } => {
buf.remove_raw(start, start + text.len());
}
Action::StartGroup | Action::EndGroup => {}
}
}
......@@ -25,6 +28,7 @@ impl Action {
buf.remove_raw(start, start + text.len());
}
Action::Remove { start, ref text } => buf.insert_raw(start, &text[..]),
Action::StartGroup | Action::EndGroup => {}
}
}
}
......@@ -87,26 +91,66 @@ impl Buffer {
self.undone_actions.clear();
}
pub fn start_undo_group(&mut self) {
self.actions.push(Action::StartGroup);
}
pub fn end_undo_group(&mut self) {
self.actions.push(Action::EndGroup);
}
pub fn undo(&mut self) -> bool {
match self.actions.pop() {
None => false,
Some(act) => {
act.undo(self);
self.undone_actions.push(act);
true
use Action::*;
let did = !self.actions.is_empty();
let mut group_nest = 0;
let mut group_count = 0;
while let Some(act) = self.actions.pop() {
act.undo(self);
self.undone_actions.push(act.clone());
match act {
EndGroup => {
group_nest += 1;
group_count = 0;
}
StartGroup => group_nest -= 1,
// count the actions in this group so we can ignore empty groups below
_ => group_count += 1,
}
// if we aren't in a group, and the last group wasn't empty
if group_nest == 0 && group_count > 0 {
break;
}
}
did
}
pub fn redo(&mut self) -> bool {
match self.undone_actions.pop() {
None => false,
Some(act) => {
act.do_on(self);
self.actions.push(act);
true
use Action::*;
let did = !self.undone_actions.is_empty();
let mut group_nest = 0;
let mut group_count = 0;
while let Some(act) = self.undone_actions.pop() {
act.do_on(self);
self.actions.push(act.clone());
match act {
StartGroup => {
group_nest += 1;
group_count = 0;
}
EndGroup => group_nest -= 1,
// count the actions in this group so we can ignore empty groups below
_ => group_count += 1,
}
// if we aren't in a group, and the last group wasn't empty
if group_nest == 0 && group_count > 0 {
break;
}
}
did
}
pub fn revert(&mut self) -> bool {
......@@ -288,4 +332,62 @@ mod tests {
buf.undo();
assert_eq!(String::from(buf), "abcdefg");
}
#[test]
fn test_undo_group() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
buf.start_undo_group();
buf.remove(0, 1);
buf.remove(0, 1);
buf.remove(0, 1);
buf.end_undo_group();
assert_eq!(buf.undo(), true);
assert_eq!(String::from(buf), "abcdefg");
}
#[test]
fn test_redo_group() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
buf.start_undo_group();
buf.remove(0, 1);
buf.remove(0, 1);
buf.remove(0, 1);
buf.end_undo_group();
assert_eq!(buf.undo(), true);
assert_eq!(buf.redo(), true);
assert_eq!(String::from(buf), "defg");
}
#[test]
fn test_nested_undo_group() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
buf.start_undo_group();
buf.remove(0, 1);
buf.start_undo_group();
buf.remove(0, 1);
buf.end_undo_group();
buf.remove(0, 1);
buf.end_undo_group();
assert_eq!(buf.undo(), true);
assert_eq!(String::from(buf), "abcdefg");
}
#[test]
fn test_nested_redo_group() {
let mut buf = Buffer::new();
buf.insert(0, &['a', 'b', 'c', 'd', 'e', 'f', 'g']);
buf.start_undo_group();
buf.remove(0, 1);
buf.start_undo_group();
buf.remove(0, 1);
buf.end_undo_group();
buf.remove(0, 1);
buf.end_undo_group();
assert_eq!(buf.undo(), true);
assert_eq!(buf.redo(), true);
assert_eq!(String::from(buf), "defg");
}
}
use std::cmp;
use std::io::{self, Write};
use termion::{self, clear, cursor};
use unicode_width::*;
......@@ -444,6 +445,26 @@ impl<'a, W: Write> Editor<'a, W> {
self.print_current_buffer(false)
}
/// Deletes every character from the cursor until the given position.
pub fn delete_until(&mut self, position: usize) -> io::Result<()> {
{
let buf = cur_buf_mut!(self);
buf.remove(cmp::min(self.cursor, position), cmp::max(self.cursor, position));
self.cursor = cmp::min(self.cursor, position);
}
self.print_current_buffer(false)
}
/// Deletes every character from the cursor until the given position, inclusive.
pub fn delete_until_inclusive(&mut self, position: usize) -> io::Result<()> {
{
let buf = cur_buf_mut!(self);
buf.remove(cmp::min(self.cursor, position), cmp::max(self.cursor + 1, position + 1));
self.cursor = cmp::min(self.cursor, position);
}
self.print_current_buffer(false)
}
/// Moves the cursor to the left by `count` characters.
/// The cursor will not go past the start of the buffer.
pub fn move_cursor_left(&mut self, mut count: usize) -> io::Result<()> {
......@@ -500,6 +521,12 @@ impl<'a, W: Write> Editor<'a, W> {
cur_buf!(self)
}
/// Returns a mutable reference to the current buffer being edited.
/// This may be the new buffer or a buffer from history.
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
cur_buf_mut!(self)
}
/// Deletes the displayed prompt and buffer, replacing them with the current prompt and buffer
pub fn print_current_buffer(&mut self, move_cursor_to_end_of_line: bool) -> io::Result<()> {
let buf = cur_buf!(self);
......@@ -626,4 +653,56 @@ mod tests {
ed.move_cursor_right(1).unwrap();
assert_eq!(ed.cursor, 4);
}
#[test]
fn delete_until_backwards() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
assert_eq!(ed.cursor, 5);
ed.delete_until(0).unwrap();
assert_eq!(ed.cursor, 0);
assert_eq!(String::from(ed), "");
}
#[test]
fn delete_until_forwards() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 0;
ed.delete_until(5).unwrap();
assert_eq!(ed.cursor, 0);
assert_eq!(String::from(ed), "");
}
#[test]
fn delete_until() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 4;
ed.delete_until(1).unwrap();
assert_eq!(ed.cursor, 1);
assert_eq!(String::from(ed), "rt");
}
#[test]
fn delete_until_inclusive() {
let mut context = Context::new();
let out = Vec::new();
let mut ed = Editor::new(out, "prompt".to_owned(), &mut context).unwrap();
ed.insert_str_after_cursor("right").unwrap();
ed.cursor = 4;
ed.delete_until_inclusive(1).unwrap();
assert_eq!(ed.cursor, 1);
assert_eq!(String::from(ed), "r");
}
}
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment