Skip to content

Commit

Permalink
Merge branch 'Nemo157-word_movement'
Browse files Browse the repository at this point in the history
  • Loading branch information
gchp committed Jan 7, 2015
2 parents ac2a7f8 + 31e3a82 commit 7ee9981
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 29 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Greg Chapple <[email protected]>"]

[dependencies]
docopt = "0.6.23"
docopt = "0.6.25"
rustbox = "0.2.4"
rustc-serialize = "0.2.4"

Expand Down
87 changes: 78 additions & 9 deletions src/iota/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//FIXME: Check unicode support
// stdlib dependencies
use std::cmp;
use std::{ cmp, char };
use std::collections::HashMap;
use std::io::{File, Reader, BufferedReader};

Expand All @@ -17,9 +17,16 @@ pub enum Mark {
DisplayMark(uint), //For using in determining some display of characters.
}

#[derive(Copy, PartialEq, Eq, Show)]
pub enum WordEdgeMatch {
Alphabet,
Whitespace,
}

#[derive(Copy, PartialEq, Eq, Show)]
pub enum Direction {
Up(uint), Down(uint), Left(uint), Right(uint),
LeftWord(uint, WordEdgeMatch), RightWord(uint, WordEdgeMatch),
LineStart, LineEnd,
}

Expand Down Expand Up @@ -170,21 +177,49 @@ impl Buffer {
(cmp::min(line_idx + nlines[n-1] + 1, last), line_idx)
} else { (cmp::min(line_idx + nlines[n-1] + 1, nlines[n]), line_idx) }
}
Direction::RightWord(n_words, edger) => {
if let Some(new_idx) = get_words(idx, n_words, edger, text) {
if new_idx < last { (new_idx, new_idx - get_line(new_idx, text).unwrap()) }
else { (last, last - get_line(last, text).unwrap()) }
} else { (last, last - get_line(last, text).unwrap()) }
}
Direction::LeftWord(n_words, edger) => {
if let Some(new_idx) = get_words_rev(idx, n_words, edger, text) {
if new_idx > 0 { (new_idx, new_idx - get_line(new_idx, text).unwrap()) }
else { (0, 0) }
} else { (0, 0) }
}
Direction::LineStart => { (line, 0) }
Direction::LineEnd => { (line_end, line_end - line) }
}
}
}
}

/// Remove the char at the mark.
pub fn remove_char(&mut self, mark: Mark) -> Option<u8> {
/// Remove the chars at the mark.
pub fn remove_chars(&mut self, mark: Mark, direction: Direction) -> Option<Vec<u8>> {
let text = &mut self.text;
if let Some(&(idx, _)) = self.marks.get(&mark) {
if let Some(ch) = self.text.remove(idx) {
let mut transaction = self.log.start(idx);
transaction.log(Change::Remove(idx, ch), idx);
Some(ch)
} else { None }
let range = match direction {
Direction::Left(n) => range(cmp::max(0, idx - n), idx),
Direction::Right(n) => range(idx, cmp::min(idx + n, text.len())),
Direction::RightWord(n_words, edger) => {
range(idx, get_words(idx, n_words, edger, text).unwrap_or(text.len()))
}
Direction::LeftWord(n_words, edger) => {
range(get_words_rev(idx, n_words, edger, text).unwrap_or(0), idx)
}
_ => unimplemented!()
};
let mut transaction = self.log.start(idx);
let mut vec = range
.rev()
.filter_map(|idx| text.remove(idx).map(|ch| (idx, ch)))
.inspect(|&(idx, ch)| transaction.log(Change::Remove(idx, ch), idx))
.map(|(_, ch)| ch)
.collect::<Vec<u8>>();
vec.reverse();
Some(vec)
} else { None }
}

Expand Down Expand Up @@ -215,6 +250,40 @@ impl Buffer {

}

fn is_alphanumeric_or_(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}

impl WordEdgeMatch {
/// If c1 -> c2 is the start of a word.
/// If end of word matching is wanted then pass the chars in reversed.
fn is_word_edge(&self, c1: &u8, c2: &u8) -> bool {
match (self, char::from_u32(*c1 as u32).unwrap(), char::from_u32(*c2 as u32).unwrap()) {
(_, '\n', '\n') => true, // Blank lines are always counted as a word
(&WordEdgeMatch::Whitespace, c1, c2) => c1.is_whitespace() && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if c1.is_whitespace() => !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if is_alphanumeric_or_(c1) => !is_alphanumeric_or_(c2) && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, c1, c2) if !is_alphanumeric_or_(c1) => is_alphanumeric_or_(c2) && !c2.is_whitespace(),
(&WordEdgeMatch::Alphabet, _, _) => false,
}
}
}

fn get_words(mark: uint, n_words: uint, edger: WordEdgeMatch, text: &GapBuffer<u8>) -> Option<uint> {
range(mark + 1, text.len() - 1)
.filter(|idx| edger.is_word_edge(&text[*idx - 1], &text[*idx]))
.take(n_words)
.next()
}

fn get_words_rev(mark: uint, n_words: uint, edger: WordEdgeMatch, text: &GapBuffer<u8>) -> Option<uint> {
range(1, mark)
.rev()
.filter(|idx| edger.is_word_edge(&text[*idx - 1], &text[*idx]))
.take(n_words)
.next()
}

/// Returns the index of the first character of the line the mark is in.
/// Newline prior to mark (EXCLUSIVE) + 1.
/// None if mark is outside of the len of text.
Expand Down Expand Up @@ -306,7 +375,7 @@ mod test {
#[test]
fn test_remove() {
let mut buffer = setup_buffer("ABCD");
buffer.remove_char(Mark::Cursor(0));
buffer.remove_chars(Mark::Cursor(0), Direction::Right(1));

assert_eq!(buffer.len(), 4);
assert_eq!(buffer.lines().next().unwrap(), [b'B', b'C', b'D']);
Expand Down
1 change: 1 addition & 0 deletions src/iota/modes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub use super::editor::Command;
pub use super::view::View;
pub use super::keymap::KeyMapState;
pub use super::buffer::Direction;
pub use super::buffer::WordEdgeMatch;
pub use super::Response;
pub use super::utils;
pub use super::overlay::{Overlay, OverlayType, OverlayEvent};
Expand Down
12 changes: 11 additions & 1 deletion src/iota/modes/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::View;
use super::KeyMapState;
use super::EventStatus;
use super::Direction;
use super::WordEdgeMatch;
use super::Response;
use super::utils;
use super::{Overlay, OverlayType, OverlayEvent};
Expand Down Expand Up @@ -35,11 +36,20 @@ impl NormalMode {
keymap.bind_key(Key::Char('j'), Command::MoveCursor(Direction::Down(1)));
keymap.bind_key(Key::Char('k'), Command::MoveCursor(Direction::Up(1)));
keymap.bind_key(Key::Char('l'), Command::MoveCursor(Direction::Right(1)));
keymap.bind_key(Key::Char('W'), Command::MoveCursor(Direction::RightWord(1, WordEdgeMatch::Whitespace)));
keymap.bind_key(Key::Char('B'), Command::MoveCursor(Direction::LeftWord(1, WordEdgeMatch::Whitespace)));
keymap.bind_key(Key::Char('w'), Command::MoveCursor(Direction::RightWord(1, WordEdgeMatch::Alphabet)));
keymap.bind_key(Key::Char('b'), Command::MoveCursor(Direction::LeftWord(1, WordEdgeMatch::Alphabet)));
keymap.bind_key(Key::Char('^'), Command::LineStart);
keymap.bind_key(Key::Char('$'), Command::LineEnd);

// editing
keymap.bind_keys(&[Key::Char('d'), Key::Char('W')], Command::Delete(Direction::RightWord(1, WordEdgeMatch::Whitespace)));
keymap.bind_keys(&[Key::Char('d'), Key::Char('B')], Command::Delete(Direction::LeftWord(1, WordEdgeMatch::Whitespace)));
keymap.bind_keys(&[Key::Char('d'), Key::Char('w')], Command::Delete(Direction::RightWord(1, WordEdgeMatch::Alphabet)));
keymap.bind_keys(&[Key::Char('d'), Key::Char('b')], Command::Delete(Direction::LeftWord(1, WordEdgeMatch::Alphabet)));
keymap.bind_key(Key::Char('x'), Command::Delete(Direction::Right(1)));
keymap.bind_key(Key::Char('X'), Command::Delete(Direction::Left(1)));
keymap.bind_key(Key::Char('u'), Command::Undo);
keymap.bind_key(Key::Ctrl('r'), Command::Redo);

Expand All @@ -61,7 +71,7 @@ impl NormalMode {
Command::LineStart => view.move_cursor_to_line_start(),

// Editing
Command::Delete(dir) => view.delete_char(dir),
Command::Delete(dir) => view.delete_chars(dir),
Command::Redo => view.redo(),
Command::Undo => view.undo(),

Expand Down
2 changes: 1 addition & 1 deletion src/iota/modes/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl StandardMode {
Command::LineStart => view.move_cursor_to_line_start(),

// Editing
Command::Delete(dir) => view.delete_char(dir),
Command::Delete(dir) => view.delete_chars(dir),
Command::InsertTab => view.insert_tab(),
Command::InsertChar(c) => view.insert_char(c),
Command::Redo => view.redo(),
Expand Down
24 changes: 12 additions & 12 deletions src/iota/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,14 @@ impl<'v> View<'v> {

//----- TEXT EDIT METHODS ----------------------------------------------------------------------

pub fn delete_char(&mut self, direction: Direction) {
match direction {
Direction::Left(1) if self.buffer.get_mark_idx(self.cursor) != Some(0) => {
self.move_cursor(direction);
self.buffer.remove_char(self.cursor);
pub fn delete_chars(&mut self, direction: Direction) {
let chars = self.buffer.remove_chars(self.cursor, direction);
match (chars, direction) {
(Some(chars), Direction::Left(..)) => {
self.move_cursor(Direction::Left(chars.len()));
}
Direction::Right(1) if self.buffer.get_mark_idx(self.cursor) != Some(self.buffer.len()) => {
self.buffer.remove_char(self.cursor);
(Some(chars), Direction::LeftWord(..)) => {
self.move_cursor(Direction::Left(chars.len()));
}
_ => {}
}
Expand Down Expand Up @@ -295,7 +295,7 @@ mod tests {
#[test]
fn test_delete_char_to_right() {
let mut view = setup_view("test\nsecond");
view.delete_char(Direction::Right(1));
view.delete_chars(Direction::Right(1));

assert_eq!(view.buffer.lines().next().unwrap(), b"est\n"[]);
}
Expand All @@ -304,7 +304,7 @@ mod tests {
fn test_delete_char_to_left() {
let mut view = setup_view("test\nsecond");
view.move_cursor(Direction::Right(1));
view.delete_char(Direction::Left(1));
view.delete_chars(Direction::Left(1));

assert_eq!(view.buffer.lines().next().unwrap(), b"est\n"[]);
}
Expand All @@ -314,7 +314,7 @@ mod tests {
fn test_delete_char_at_start_of_line() {
let mut view = setup_view("test\nsecond");
view.move_cursor(Direction::Down(1));
view.delete_char(Direction::Left(1));
view.delete_chars(Direction::Left(1));

assert_eq!(view.buffer.lines().next().unwrap(), b"testsecond"[]);
}
Expand All @@ -323,15 +323,15 @@ mod tests {
fn test_delete_char_at_end_of_line() {
let mut view = setup_view("test\nsecond");
view.move_cursor(Direction::Right(4));
view.delete_char(Direction::Right(1));
view.delete_chars(Direction::Right(1));

assert_eq!(view.buffer.lines().next().unwrap(), b"testsecond"[]);
}

#[test]
fn deleting_backward_at_start_of_first_line_does_nothing() {
let mut view = setup_view("test\nsecond");
view.delete_char(Direction::Left(1));
view.delete_chars(Direction::Left(1));

let lines: Vec<&[u8]> = view.buffer.lines().collect();

Expand Down

0 comments on commit 7ee9981

Please sign in to comment.