diff --git a/README.md b/README.md
index 85eeb6d75fd3176f7b1e79b7dba4f94240d1cd23..88e8eb368fce3c38320bf14625552405565e3ccd 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ Meta-Y       | See Ctrl-Y
 Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word
 
 [Readline Emacs Editing Mode Cheat Sheet](http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf)
+[Terminal codes (ANSI/VT100)](http://wiki.bash-hackers.org/scripting/terminalcodes)
 
 ## ToDo
 
diff --git a/src/config.rs b/src/config.rs
index 2708527e8d12a01f1bb010ba8b720ddb1bbd39bd..2585c3e33ac178a283c4bf85428367cb777165a3 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -13,6 +13,7 @@ pub struct Config {
     completion_prompt_limit: usize,
     /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence.
     keyseq_timeout: i32,
+    edit_mode: EditMode,
 }
 
 impl Config {
@@ -48,6 +49,10 @@ impl Config {
     pub fn keyseq_timeout(&self) -> i32 {
         self.keyseq_timeout
     }
+
+    pub fn edit_mode(&self) -> EditMode {
+        self.edit_mode
+    }
 }
 
 impl Default for Config {
@@ -59,6 +64,7 @@ impl Default for Config {
             completion_type: CompletionType::Circular, // TODO Validate
             completion_prompt_limit: 100,
             keyseq_timeout: 500,
+            edit_mode: EditMode::Emacs,
         }
     }
 }
@@ -79,6 +85,12 @@ pub enum CompletionType {
     List,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum EditMode {
+    Emacs,
+    Vi,
+}
+
 #[derive(Debug)]
 pub struct Builder {
     p: Config,
@@ -130,6 +142,11 @@ impl Builder {
         self
     }
 
+    pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder {
+        self.p.edit_mode = edit_mode;
+        self
+    }
+
     pub fn build(self) -> Config {
         self.p
     }
diff --git a/src/keymap.rs b/src/keymap.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5acf4c1b1907786f9765fb7707e3cce9eb318bcf
--- /dev/null
+++ b/src/keymap.rs
@@ -0,0 +1,338 @@
+use super::Config;
+use super::EditMode;
+use super::KeyPress;
+use super::RawReader;
+use super::Result;
+
+//#[derive(Clone)]
+pub enum Cmd {
+    Abort, // Miscellaneous Command
+    AcceptLine, // Command For History
+    BackwardChar, // Command For Moving
+    BackwardDeleteChar, // Command For Text
+    BackwardKillWord, // Command For Killing
+    BackwardWord, // Command For Moving
+    BeginningOfHistory, // Command For History
+    BeginningOfLine, // Command For Moving
+    CapitalizeWord, // Command For Text
+    CharacterSearch(bool), // Miscellaneous Command (TODO Move right to the next occurance of c)
+    CharacterSearchBackward(bool), /* Miscellaneous Command (TODO Move left to the previous occurance of c) */
+    ClearScreen, // Command For Moving
+    Complete, // Command For Completion
+    DeleteChar, // Command For Text
+    DowncaseWord, // Command For Text
+    EndOfFile, // Command For Text
+    EndOfHistory, // Command For History
+    EndOfLine, // Command For Moving
+    ForwardChar, // Command For Moving
+    ForwardSearchHistory, // Command For History
+    ForwardWord, // Command For Moving
+    KillLine, // Command For Killing
+    KillWholeLine, // Command For Killing (TODO Delete current line)
+    KillWord, // Command For Killing
+    NextHistory, // Command For History
+    Noop,
+    PreviousHistory, // Command For History
+    QuotedInsert, // Command For Text
+    Replace, // TODO DeleteChar + SelfInsert
+    ReverseSearchHistory, // Command For History
+    SelfInsert, // Command For Text
+    TransposeChars, // Command For Text
+    TransposeWords, // Command For Text
+    Unknown,
+    UnixLikeDiscard, // Command For Killing
+    UnixWordRubout, // Command For Killing
+    UpcaseWord, // Command For Text
+    Yank, // Command For Killing
+    YankPop, // Command For Killing
+}
+
+// TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
+pub struct EditState {
+    mode: EditMode,
+    // TODO Validate Vi Command, Insert, Visual mode
+    insert: bool, // vi only ?
+}
+
+impl EditState {
+    pub fn new(config: &Config) -> EditState {
+        EditState {
+            mode: config.edit_mode(),
+            insert: true,
+        }
+    }
+
+    pub fn next_cmd<R: RawReader>(&mut self,
+                                  rdr: &mut R,
+                                  config: &Config)
+                                  -> Result<(KeyPress, Cmd)> {
+        match self.mode {
+            EditMode::Emacs => self.emacs(rdr, config),
+            EditMode::Vi if self.insert => self.vi_insert(rdr, config),
+            EditMode::Vi => self.vi_command(rdr, config),
+        }
+    }
+
+    fn emacs<R: RawReader>(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> {
+        let key = try!(rdr.next_key(config.keyseq_timeout()));
+        let cmd = match key {
+            KeyPress::Char(_) => Cmd::SelfInsert,
+            KeyPress::Esc => Cmd::Abort, // TODO Validate
+            KeyPress::Ctrl('A') => Cmd::BeginningOfLine,
+            KeyPress::Home => Cmd::BeginningOfLine,
+            KeyPress::Ctrl('B') => Cmd::BackwardChar,
+            KeyPress::Left => Cmd::BackwardChar,
+            // KeyPress::Ctrl('D') if s.line.is_empty() => Cmd::EndOfFile,
+            KeyPress::Ctrl('D') => Cmd::DeleteChar,
+            KeyPress::Delete => Cmd::DeleteChar,
+            KeyPress::Ctrl('E') => Cmd::EndOfLine,
+            KeyPress::End => Cmd::EndOfLine,
+            KeyPress::Ctrl('F') => Cmd::ForwardChar,
+            KeyPress::Right => Cmd::ForwardChar,
+            KeyPress::Ctrl('G') => Cmd::Abort,
+            KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar,
+            KeyPress::Backspace => Cmd::BackwardDeleteChar,
+            KeyPress::Tab => Cmd::Complete,
+            KeyPress::Ctrl('J') => Cmd::AcceptLine,
+            KeyPress::Enter => Cmd::AcceptLine,
+            KeyPress::Ctrl('K') => Cmd::KillLine,
+            KeyPress::Ctrl('L') => Cmd::ClearScreen,
+            KeyPress::Ctrl('N') => Cmd::NextHistory,
+            KeyPress::Down => Cmd::NextHistory,
+            KeyPress::Ctrl('P') => Cmd::PreviousHistory,
+            KeyPress::Up => Cmd::PreviousHistory,
+            KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution
+            KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory,
+            KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution
+            KeyPress::Ctrl('T') => Cmd::TransposeChars,
+            KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard,
+            KeyPress::Ctrl('V') => Cmd::QuotedInsert,
+            KeyPress::Ctrl('W') => Cmd::UnixWordRubout,
+            KeyPress::Ctrl('Y') => Cmd::Yank,
+            KeyPress::Meta('\x08') => Cmd::BackwardKillWord,
+            KeyPress::Meta('\x7f') => Cmd::BackwardKillWord,
+            // KeyPress::Meta('-') => { // digit-argument
+            // }
+            // KeyPress::Meta('0'...'9') => { // digit-argument
+            // }
+            KeyPress::Meta('<') => Cmd::BeginningOfHistory,
+            KeyPress::Meta('>') => Cmd::EndOfHistory,
+            KeyPress::Meta('B') => Cmd::BackwardWord,
+            KeyPress::Meta('C') => Cmd::CapitalizeWord,
+            KeyPress::Meta('D') => Cmd::KillWord,
+            KeyPress::Meta('F') => Cmd::ForwardWord,
+            KeyPress::Meta('L') => Cmd::DowncaseWord,
+            KeyPress::Meta('T') => Cmd::TransposeWords,
+            KeyPress::Meta('U') => Cmd::UpcaseWord,
+            KeyPress::Meta('Y') => Cmd::YankPop,
+            _ => Cmd::Unknown,
+        };
+        Ok((key, cmd))
+    }
+
+    fn vi_command<R: RawReader>(&mut self,
+                                rdr: &mut R,
+                                config: &Config)
+                                -> Result<(KeyPress, Cmd)> {
+        let key = try!(rdr.next_key(config.keyseq_timeout()));
+        let cmd = match key {
+            KeyPress::Char('$') => Cmd::EndOfLine,
+            // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket
+            KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line.
+            // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit
+            KeyPress::Char('^') => Cmd::BeginningOfLine, // TODO Move to the first non-blank character of line.
+            KeyPress::Char('a') => {
+                // vi-append-mode: Vi enter insert mode after the cursor.
+                self.insert = true;
+                Cmd::ForwardChar
+            }
+            KeyPress::Char('A') => {
+                // vi-append-eol: Vi enter insert mode at end of line.
+                self.insert = true;
+                Cmd::EndOfLine
+            }
+            KeyPress::Char('b') => Cmd::BackwardWord,
+            // TODO KeyPress::Char('B') => Cmd::???, Move one non-blank word left.
+            KeyPress::Char('c') => {
+                self.insert = true;
+                let mvt = try!(rdr.next_key(config.keyseq_timeout()));
+                match mvt {
+                    KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line.
+                    KeyPress::Char('0') => Cmd::UnixLikeDiscard,
+                    KeyPress::Char('c') => Cmd::KillWholeLine,
+                    // TODO KeyPress::Char('f') => ???,
+                    // TODO KeyPress::Char('F') => ???,
+                    KeyPress::Char('h') => Cmd::BackwardDeleteChar,
+                    KeyPress::Char('l') => Cmd::DeleteChar,
+                    KeyPress::Char(' ') => Cmd::DeleteChar,
+                    // TODO KeyPress::Char('t') => ???,
+                    // TODO KeyPress::Char('T') => ???,
+                    KeyPress::Char('w') => Cmd::KillWord,
+                    _ => Cmd::Unknown,
+                }
+            }
+            KeyPress::Char('C') => {
+                self.insert = true;
+                Cmd::KillLine
+            }
+            KeyPress::Char('d') => {
+                let mvt = try!(rdr.next_key(config.keyseq_timeout()));
+                match mvt {
+                    KeyPress::Char('$') => Cmd::KillLine,
+                    KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor.
+                    KeyPress::Char('d') => Cmd::KillWholeLine,
+                    // TODO KeyPress::Char('f') => ???,
+                    // TODO KeyPress::Char('F') => ???,
+                    KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace).
+                    KeyPress::Char('l') => Cmd::DeleteChar,
+                    KeyPress::Char(' ') => Cmd::DeleteChar,
+                    // TODO KeyPress::Char('t') => ???,
+                    // TODO KeyPress::Char('T') => ???,
+                    KeyPress::Char('w') => Cmd::KillWord,
+                    _ => Cmd::Unknown,
+                }
+            }
+            KeyPress::Char('D') => Cmd::KillLine,
+            // TODO KeyPress::Char('e') => Cmd::???, vi-to-end-word: Vi move to the end of the current word. Move to the end of the current word.
+            // TODO KeyPress::Char('E') => Cmd::???, vi-end-word: Vi move to the end of the current space delimited word. Move to the end of the current non-blank word.
+            KeyPress::Char('i') => {
+                // vi-insert: Vi enter insert mode.
+                self.insert = true;
+                Cmd::Noop
+            }
+            KeyPress::Char('I') => {
+                // vi-insert-at-bol: Vi enter insert mode at the beginning of line.
+                self.insert = true;
+                Cmd::BeginningOfLine
+            }
+            KeyPress::Char('f') => {
+                // vi-next-char: Vi move to the character specified next.
+                let ch = try!(rdr.next_key(config.keyseq_timeout()));
+                match ch {
+                    KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(false))),
+                    _ => Cmd::Unknown,
+                }
+            }
+            KeyPress::Char('F') => {
+                // vi-prev-char: Vi move to the character specified previous.
+                let ch = try!(rdr.next_key(config.keyseq_timeout()));
+                match ch {
+                    KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(false))),
+                    _ => Cmd::Unknown,
+                }
+            }
+            // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n
+            KeyPress::Char('p') => Cmd::Yank, // vi-paste-next: Vi paste previous deletion to the right of the cursor.
+            KeyPress::Char('P') => Cmd::Yank, // vi-paste-prev: Vi paste previous deletion to the left of the cursor. TODO Insert the yanked text before the cursor.
+            KeyPress::Char('r') => {
+                // vi-replace-char: Vi replace character under the cursor with the next character typed.
+                let ch = try!(rdr.next_key(config.keyseq_timeout()));
+                match ch {
+                    KeyPress::Char(_) => return Ok((ch, Cmd::Replace)),
+                    KeyPress::Esc => Cmd::Noop,
+                    _ => Cmd::Unknown,
+                }
+            }
+            // TODO KeyPress::Char('R') => Cmd::???, vi-replace-mode: Vi enter replace mode. Replaces characters under the cursor. (overwrite-mode)
+            KeyPress::Char('s') => {
+                // vi-substitute-char: Vi replace character under the cursor and enter insert mode.
+                self.insert = true;
+                Cmd::DeleteChar
+            }
+            KeyPress::Char('S') => {
+                // vi-substitute-line: Vi substitute entire line.
+                self.insert = true;
+                Cmd::KillWholeLine
+            }
+            KeyPress::Char('t') => {
+                // vi-to-next-char: Vi move up to the character specified next.
+                let ch = try!(rdr.next_key(config.keyseq_timeout()));
+                match ch {
+                    KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(true))),
+                    _ => Cmd::Unknown,
+                }
+            }
+            KeyPress::Char('T') => {
+                // vi-to-prev-char: Vi move up to the character specified previous.
+                let ch = try!(rdr.next_key(config.keyseq_timeout()));
+                match ch {
+                    KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(true))),
+                    _ => Cmd::Unknown,
+                }
+            }
+            // KeyPress::Char('U') => Cmd::???, // revert-line
+            KeyPress::Char('w') => Cmd::ForwardWord, // vi-next-word: Vi move to the next word.
+            // TODO KeyPress::Char('W') => Cmd::???, // vi-next-space-word: Vi move to the next space delimited word. Move one non-blank word right.
+            KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol
+            KeyPress::Char('X') => Cmd::BackwardDeleteChar,
+            KeyPress::Home => Cmd::BeginningOfLine,
+            KeyPress::Char('h') => Cmd::BackwardChar,
+            KeyPress::Left => Cmd::BackwardChar,
+            KeyPress::Ctrl('D') => Cmd::EndOfFile,
+            KeyPress::Delete => Cmd::DeleteChar,
+            KeyPress::End => Cmd::EndOfLine,
+            KeyPress::Ctrl('G') => Cmd::Abort,
+            KeyPress::Ctrl('H') => Cmd::BackwardChar,
+            KeyPress::Char('l') => Cmd::ForwardChar,
+            KeyPress::Char(' ') => Cmd::ForwardChar,
+            KeyPress::Right => Cmd::ForwardChar,
+            KeyPress::Ctrl('L') => Cmd::ClearScreen,
+            KeyPress::Ctrl('J') => Cmd::AcceptLine,
+            KeyPress::Enter => Cmd::AcceptLine,
+            KeyPress::Char('+') => Cmd::NextHistory,
+            KeyPress::Char('j') => Cmd::NextHistory,
+            KeyPress::Ctrl('N') => Cmd::NextHistory,
+            KeyPress::Down => Cmd::NextHistory,
+            KeyPress::Char('-') => Cmd::PreviousHistory,
+            KeyPress::Char('k') => Cmd::PreviousHistory,
+            KeyPress::Ctrl('P') => Cmd::PreviousHistory,
+            KeyPress::Up => Cmd::PreviousHistory,
+            KeyPress::Ctrl('K') => Cmd::KillLine,
+            KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution
+            KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory,
+            KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory,
+            KeyPress::Ctrl('T') => Cmd::TransposeChars,
+            KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard,
+            KeyPress::Ctrl('V') => Cmd::QuotedInsert,
+            KeyPress::Ctrl('W') => Cmd::UnixWordRubout,
+            KeyPress::Ctrl('Y') => Cmd::Yank,
+            KeyPress::Esc => Cmd::Noop,
+            _ => Cmd::Unknown,
+        };
+        Ok((key, cmd))
+    }
+
+    fn vi_insert<R: RawReader>(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> {
+        let key = try!(rdr.next_key(config.keyseq_timeout()));
+        let cmd = match key {
+            KeyPress::Char(_) => Cmd::SelfInsert,
+            KeyPress::Home => Cmd::BeginningOfLine,
+            KeyPress::Left => Cmd::BackwardChar,
+            KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe
+            KeyPress::Delete => Cmd::DeleteChar,
+            KeyPress::End => Cmd::EndOfLine,
+            KeyPress::Right => Cmd::ForwardChar,
+            KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar,
+            KeyPress::Backspace => Cmd::BackwardDeleteChar,
+            KeyPress::Tab => Cmd::Complete,
+            KeyPress::Ctrl('J') => Cmd::AcceptLine,
+            KeyPress::Enter => Cmd::AcceptLine,
+            KeyPress::Down => Cmd::NextHistory,
+            KeyPress::Up => Cmd::PreviousHistory,
+            KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory,
+            KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory,
+            KeyPress::Ctrl('T') => Cmd::TransposeChars,
+            KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard,
+            KeyPress::Ctrl('V') => Cmd::QuotedInsert,
+            KeyPress::Ctrl('W') => Cmd::UnixWordRubout,
+            KeyPress::Ctrl('Y') => Cmd::Yank,
+            KeyPress::Esc => {
+                // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings).
+                self.insert = false;
+                Cmd::Noop
+            }
+            _ => Cmd::Unknown,
+        };
+        Ok((key, cmd))
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 59ae361f624b9ce02a0bc28acccbee65a2ac9765..ca2bb251931c84a80f7a20360bbc0603c6bf0505 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -31,6 +31,7 @@ pub mod completion;
 mod consts;
 pub mod error;
 pub mod history;
+mod keymap;
 mod kill_ring;
 pub mod line_buffer;
 pub mod config;
@@ -49,7 +50,7 @@ use consts::KeyPress;
 use history::{Direction, History};
 use line_buffer::{LineBuffer, MAX_LINE, WordAction};
 use kill_ring::{Mode, KillRing};
-pub use config::{CompletionType, Config, HistoryDuplicates};
+pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
 
 /// The error type for I/O and Linux Syscalls (Errno)
 pub type Result<T> = result::Result<T, error::ReadlineError>;
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index abfe1856dc83b78175fbf3098d2e68a860601e6e..613fce1cd0148cb03177e591105de665067835c8 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -183,6 +183,11 @@ impl LineBuffer {
         }
     }
 
+    /// Replace a single character under the cursor (Vi mode)
+    pub fn replace_char(&mut self, ch: char) -> Option<bool> {
+        if self.delete() { self.insert(ch) } else { None }
+    }
+
     /// Delete the character at the right of the cursor without altering the cursor
     /// position. Basically this is what happens with the "Delete" keyboard key.
     pub fn delete(&mut self) -> bool {
@@ -206,6 +211,12 @@ impl LineBuffer {
         }
     }
 
+    /// Kill all characters on the current line.
+    pub fn kill_whole_line(&mut self) -> Option<String> {
+        self.move_home();
+        self.kill_line()
+    }
+
     /// Kill the text from point to the end of the line.
     pub fn kill_line(&mut self) -> Option<String> {
         if !self.buf.is_empty() && self.pos < self.buf.len() {
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 4e7f5f18ee54f81984a372f66e7cfede08731571..b51cdba20805760794701f7896757b629a35fe50 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -152,6 +152,8 @@ impl PosixRawReader {
             // TODO ESC-R (r): Undo all changes made to this line.
             match seq1 {
                 '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace
+                '-' => return Ok(KeyPress::Meta('-')),
+                '0'...'9' => return Ok(KeyPress::Meta(seq1)),
                 '<' => Ok(KeyPress::Meta('<')),
                 '>' => Ok(KeyPress::Meta('>')),
                 'b' | 'B' => Ok(KeyPress::Meta('B')),
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 6cbb0c3d90e0c10ad4bb14d69901745d3cedaec9..1ebf108d9dca2473f7e6833b482971bcf46f0d6b 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -151,6 +151,10 @@ impl RawReader for ConsoleRawReader {
                 let c = try!(orc.unwrap());
                 if meta {
                     match c {
+                        '-' => return Ok(KeyPress::Meta('-')),
+                        '0'...'9' => return Ok(KeyPress::Meta(c)),
+                        '<' => Ok(KeyPress::Meta('<')),
+                        '>' => Ok(KeyPress::Meta('>')),
                         'b' | 'B' => return Ok(KeyPress::Meta('B')),
                         'c' | 'C' => return Ok(KeyPress::Meta('C')),
                         'd' | 'D' => return Ok(KeyPress::Meta('D')),