diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index b1b1d608ca3bbab2e264987001b918742500cb55..49efca9676fb189860235d353b3ad1083a3b90cc 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -295,30 +295,36 @@ impl LineBuffer {
         if pos == 0 {
             return None;
         }
-        let test = is_break_char(word_def);
-        let mut pos = pos;
-        for _ in 0..n {
-            // eat any spaces on the left
-            pos -= self.buf[..pos]
-                .chars()
-                .rev()
-                .take_while(|ch| test(ch))
-                .map(char::len_utf8)
-                .sum();
-            if pos > 0 {
-                // eat any non-spaces on the left
-                pos -= self.buf[..pos]
-                    .chars()
-                    .rev()
-                    .take_while(|ch| !test(ch))
-                    .map(char::len_utf8)
-                    .sum();
-            }
-            if pos == 0 {
-                break;
+        let mut sow = 0;
+        let mut gis = self.buf[..pos]
+            .grapheme_indices(true)
+            .rev();
+        'outer: for _ in 0..n {
+            let mut gj = gis.next();
+            'inner: loop {
+                match gj {
+                    Some((j, y)) => {
+                        let gi = gis.next();
+                        match gi {
+                            Some((_, x)) => {
+                                if is_start_of_word(word_def, x, y) {
+                                    sow = j;
+                                    break 'inner;
+                                }
+                                gj = gi;
+                            }
+                            None => {
+                                break 'outer;
+                            }
+                        }
+                    }
+                    None => {
+                        break 'outer;
+                    }
+                }
             }
         }
-        Some(pos)
+        Some(sow)
     }
 
     /// Moves the cursor to the beginning of previous word.
@@ -344,89 +350,54 @@ impl LineBuffer {
     }
 
     fn next_word_pos(&self, pos: usize, at: At, word_def: Word, n: RepeatCount) -> Option<usize> {
-        match at {
-            At::End => {
-                match self.next_end_of_word_pos(pos, word_def, n) {
-                    Some((_, end)) => Some(end),
-                    _ => None,
-                }
-            }
-            At::Start => self.next_start_of_word_pos(pos, word_def, n),
-        }
-    }
-
-    /// Go right until start of word
-    fn next_start_of_word_pos(&self, pos: usize, word_def: Word, n: RepeatCount) -> Option<usize> {
         if pos == self.buf.len() {
             return None;
         }
-        let test = is_break_char(word_def);
-        let mut pos = pos;
-        for _ in 0..n {
-            // eat any non-spaces
-            pos += self.buf[pos..]
-                .chars()
-                .take_while(|ch| !test(ch))
-                .map(char::len_utf8)
-                .sum();
-            if pos < self.buf.len() {
-                // eat any spaces
-                pos += self.buf[pos..]
-                    .chars()
-                    .take_while(test)
-                    .map(char::len_utf8)
-                    .sum();
-            }
-            if pos == self.buf.len() {
-                break;
-            }
-        }
-        Some(pos)
-    }
-
-    /// Go right until end of word
-    /// Returns the position (start, end) of the next word.
-    fn next_end_of_word_pos(&self,
-                            pos: usize,
-                            word_def: Word,
-                            n: RepeatCount)
-                            -> Option<(usize, usize)> {
-        if pos == self.buf.len() {
-            return None;
+        let mut wp = self.buf.len() - pos;
+        let mut gis = self.buf[pos..].grapheme_indices(true);
+        if at == At::End {
+            // TODO Validate
+            gis.next();
         }
-        let test = is_break_char(word_def);
-        let mut pos = pos;
-        let mut start = pos;
-        for _ in 0..n {
-            // eat any spaces
-            pos += self.buf[pos..]
-                .chars()
-                .take_while(test)
-                .map(char::len_utf8)
-                .sum();
-            start = pos;
-            if pos < self.buf.len() {
-                // eat any non-spaces
-                pos += self.buf[pos..]
-                    .chars()
-                    .take_while(|ch| !test(ch))
-                    .map(char::len_utf8)
-                    .sum();
-            }
-            if pos == self.buf.len() {
-                break;
+        'outer: for _ in 0..n {
+            let mut gi = gis.next();
+            'inner: loop {
+                match gi {
+                    Some((i, x)) => {
+                        let gj = gis.next();
+                        match gj {
+                            Some((j, y)) => {
+                                if at == At::Start && is_start_of_word(word_def, x, y) {
+                                    wp = j;
+                                    break 'inner;
+                                } else if at == At::End && is_end_of_word(word_def, x, y) {
+                                    if word_def == Word::Emacs {
+                                        wp = j;
+                                    } else {
+                                        wp = i;
+                                    }
+                                    break 'inner;
+                                }
+                                gi = gj;
+                            }
+                            None => {
+                                break 'outer;
+                            }
+                        }
+                    }
+                    None => {
+                        break 'outer;
+                    }
+                }
             }
         }
-        Some((start, pos))
+        Some(wp + pos)
     }
 
     /// Moves the cursor to the end of next word.
     pub fn move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> bool {
         if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) {
             self.pos = pos;
-            if at == At::End && word_def != Word::Emacs {
-                self.move_backward(1);
-            }
             true
         } else {
             false
@@ -486,7 +457,8 @@ impl LineBuffer {
         }
     }
 
-    /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word.
+    /// Kill from the cursor to the end of the current word,
+    /// or, if between words, to the end of the next word.
     pub fn delete_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Option<String> {
         if let Some(pos) = self.next_word_pos(self.pos, at, word_def, n) {
             let word = self.buf.drain(self.pos..pos).collect();
@@ -520,26 +492,27 @@ impl LineBuffer {
 
     /// Alter the next word.
     pub fn edit_word(&mut self, a: WordAction) -> bool {
-        if let Some((start, end)) = self.next_end_of_word_pos(self.pos, Word::Emacs, 1) {
-            if start == end {
-                return false;
-            }
-            let word = self.buf.drain(start..end).collect::<String>();
-            let result = match a {
-                WordAction::CAPITALIZE => {
-                    let ch = (&word).graphemes(true).next().unwrap();
-                    let cap = ch.to_uppercase();
-                    cap + &word[ch.len()..].to_lowercase()
+        if let Some(start) = self.next_word_pos(self.pos, At::Start, Word::Emacs, 1) {
+            if let Some(end) = self.next_word_pos(self.pos, At::End, Word::Emacs, 1) {
+                if start == end {
+                    return false;
                 }
-                WordAction::LOWERCASE => word.to_lowercase(),
-                WordAction::UPPERCASE => word.to_uppercase(),
-            };
-            self.insert_str(start, &result);
-            self.pos = start + result.len();
-            true
-        } else {
-            false
+                let word = self.buf.drain(start..end).collect::<String>();
+                let result = match a {
+                    WordAction::CAPITALIZE => {
+                        let ch = (&word).graphemes(true).next().unwrap();
+                        let cap = ch.to_uppercase();
+                        cap + &word[ch.len()..].to_lowercase()
+                    }
+                    WordAction::LOWERCASE => word.to_lowercase(),
+                    WordAction::UPPERCASE => word.to_uppercase(),
+                };
+                self.insert_str(start, &result);
+                self.pos = start + result.len();
+                return true;
+            }
         }
+        false
     }
 
     /// Transpose two words
@@ -569,7 +542,8 @@ impl LineBuffer {
         true
     }
 
-    /// Replaces the content between [`start`..`end`] with `text` and positions the cursor to the end of text.
+    /// Replaces the content between [`start`..`end`] with `text`
+    /// and positions the cursor to the end of text.
     pub fn replace(&mut self, range: Range<usize>, text: &str) {
         let start = range.start;
         self.buf.drain(range);
@@ -686,22 +660,27 @@ fn insert_str(buf: &mut String, idx: usize, s: &str) {
     }
 }
 
-fn is_break_char(word_def: Word) -> fn(&char) -> bool {
-    match word_def {
-        Word::Emacs => is_not_alphanumeric,
-        Word::Vi => is_not_alphanumeric_and_underscore,
-        Word::Big => is_whitespace,
-    }
+fn is_start_of_word(word_def: Word, previous: &str, grapheme: &str) -> bool {
+    (!is_word_char(word_def, previous) && is_word_char(word_def, grapheme)) ||
+    (word_def == Word::Vi && !is_other_char(previous) && is_other_char(grapheme))
+}
+fn is_end_of_word(word_def: Word, grapheme: &str, next: &str) -> bool {
+    (!is_word_char(word_def, next) && is_word_char(word_def, grapheme)) ||
+    (word_def == Word::Vi && !is_other_char(next) && is_other_char(grapheme))
 }
 
-fn is_not_alphanumeric(ch: &char) -> bool {
-    !ch.is_alphanumeric()
+fn is_word_char(word_def: Word, grapheme: &str) -> bool {
+    match word_def {
+        Word::Emacs => grapheme.chars().all(|c| c.is_alphanumeric()),
+        Word::Vi => is_vi_word_char(grapheme),
+        Word::Big => !grapheme.chars().all(|c| c.is_whitespace()),
+    }
 }
-fn is_not_alphanumeric_and_underscore(ch: &char) -> bool {
-    !ch.is_alphanumeric() && *ch != '_'
+fn is_vi_word_char(grapheme: &str) -> bool {
+    grapheme.chars().all(|c| c.is_alphanumeric()) || grapheme == "_"
 }
-fn is_whitespace(ch: &char) -> bool {
-    ch.is_whitespace()
+fn is_other_char(grapheme: &str) -> bool {
+    !(grapheme.chars().all(|c| c.is_whitespace()) || is_vi_word_char(grapheme))
 }
 
 #[cfg(test)]
@@ -869,6 +848,50 @@ mod test {
         assert_eq!(true, ok);
     }
 
+    #[test]
+    fn move_to_prev_vi_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(17, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(15, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(12, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(11, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(7, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(6, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(0, s.pos);
+        let ok = s.move_to_prev_word(Word::Vi, 1);
+        assert!(!ok);
+    }
+
+    #[test]
+    fn move_to_prev_big_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 19);
+        let ok = s.move_to_prev_word(Word::Big, 1);
+        assert!(ok);
+        assert_eq!(17, s.pos);
+        let ok = s.move_to_prev_word(Word::Big, 1);
+        assert!(ok);
+        assert_eq!(6, s.pos);
+        let ok = s.move_to_prev_word(Word::Big, 1);
+        assert!(ok);
+        assert_eq!(0, s.pos);
+        let ok = s.move_to_prev_word(Word::Big, 1);
+        assert!(!ok);
+    }
+
     #[test]
     fn move_to_forward() {
         let mut s = LineBuffer::init("αßγδε", 2);
@@ -922,6 +945,50 @@ mod test {
         assert_eq!(true, ok);
     }
 
+    #[test]
+    fn move_to_end_of_vi_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(4, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(6, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(10, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(11, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(14, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(15, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(19, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Vi, 1);
+        assert!(!ok);
+    }
+
+    #[test]
+    fn move_to_end_of_big_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let ok = s.move_to_next_word(At::End, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(4, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(15, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(19, s.pos);
+        let ok = s.move_to_next_word(At::End, Word::Big, 1);
+        assert!(!ok);
+    }
+
     #[test]
     fn move_to_start_of_word() {
         let mut s = LineBuffer::init("a ß  c", 2);
@@ -931,6 +998,50 @@ mod test {
         assert_eq!(true, ok);
     }
 
+    #[test]
+    fn move_to_start_of_vi_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(6, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(7, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(11, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(12, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(15, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(17, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(ok);
+        assert_eq!(19, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Vi, 1);
+        assert!(!ok);
+    }
+
+    #[test]
+    fn move_to_start_of_big_word() {
+        let mut s = LineBuffer::init("alpha ,beta/rho; mu", 0);
+        let ok = s.move_to_next_word(At::Start, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(6, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(17, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Big, 1);
+        assert!(ok);
+        assert_eq!(19, s.pos);
+        let ok = s.move_to_next_word(At::Start, Word::Big, 1);
+        assert!(!ok);
+    }
+
     #[test]
     fn delete_word() {
         let mut s = LineBuffer::init("a ß  c", 1);