| //! Bindings from keys to command for Emacs and Vi modes |
| use config::Config; |
| use config::EditMode; |
| use consts::KeyPress; |
| use tty::RawReader; |
| use super::Result; |
| |
| pub type RepeatCount = usize; |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum Cmd { |
| Abort, // Miscellaneous Command |
| AcceptLine, |
| BeginningOfHistory, |
| CapitalizeWord, |
| ClearScreen, |
| Complete, |
| DowncaseWord, |
| EndOfFile, |
| EndOfHistory, |
| ForwardSearchHistory, |
| Insert(RepeatCount, String), |
| Interrupt, |
| Kill(Movement), |
| Move(Movement), |
| NextHistory, |
| Noop, |
| PreviousHistory, |
| QuotedInsert, |
| Replace(RepeatCount, char), |
| ReverseSearchHistory, |
| SelfInsert(RepeatCount, char), |
| Suspend, |
| TransposeChars, |
| TransposeWords(RepeatCount), |
| Undo, |
| Unknown, |
| UpcaseWord, |
| ViYankTo(Movement), |
| Yank(RepeatCount, Anchor), |
| YankPop, |
| } |
| |
| impl Cmd { |
| fn is_repeatable_change(&self) -> bool { |
| match *self { |
| Cmd::Insert(_, _) => true, |
| Cmd::Kill(_) => true, |
| Cmd::Replace(_, _) => true, |
| Cmd::SelfInsert(_, _) => true, |
| Cmd::TransposeChars => false, // TODO Validate |
| Cmd::ViYankTo(_) => true, |
| Cmd::Yank(_, _) => true, |
| _ => false, |
| } |
| } |
| |
| fn redo(&self, new: Option<RepeatCount>) -> Cmd { |
| match *self { |
| Cmd::Insert(previous, ref text) => { |
| Cmd::Insert(repeat_count(previous, new), text.clone()) |
| } |
| Cmd::Kill(ref mvt) => Cmd::Kill(mvt.redo(new)), |
| Cmd::Replace(previous, c) => Cmd::Replace(repeat_count(previous, new), c), |
| Cmd::SelfInsert(previous, c) => Cmd::SelfInsert(repeat_count(previous, new), c), |
| //Cmd::TransposeChars => Cmd::TransposeChars, |
| Cmd::ViYankTo(ref mvt) => Cmd::ViYankTo(mvt.redo(new)), |
| Cmd::Yank(previous, anchor) => Cmd::Yank(repeat_count(previous, new), anchor), |
| _ => unreachable!(), |
| } |
| } |
| } |
| |
| fn repeat_count(previous: RepeatCount, new: Option<RepeatCount>) -> RepeatCount { |
| match new { |
| Some(n) => n, |
| None => previous, |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Copy)] |
| pub enum Word { |
| // non-blanks characters |
| Big, |
| // alphanumeric characters |
| Emacs, |
| // alphanumeric (and '_') characters |
| Vi, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Copy)] |
| pub enum At { |
| Start, |
| BeforeEnd, |
| AfterEnd, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Copy)] |
| pub enum Anchor { |
| After, |
| Before, |
| } |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum CharSearch { |
| Forward(char), |
| // until |
| ForwardBefore(char), |
| Backward(char), |
| // until |
| BackwardAfter(char), |
| } |
| |
| impl CharSearch { |
| fn opposite(&self) -> CharSearch { |
| match *self { |
| CharSearch::Forward(c) => CharSearch::Backward(c), |
| CharSearch::ForwardBefore(c) => CharSearch::BackwardAfter(c), |
| CharSearch::Backward(c) => CharSearch::Forward(c), |
| CharSearch::BackwardAfter(c) => CharSearch::ForwardBefore(c), |
| } |
| } |
| } |
| |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum Movement { |
| WholeLine, // not really a movement |
| BeginningOfLine, |
| EndOfLine, |
| BackwardWord(RepeatCount, Word), // Backward until start of word |
| ForwardWord(RepeatCount, At, Word), // Forward until start/end of word |
| ViCharSearch(RepeatCount, CharSearch), |
| ViFirstPrint, |
| BackwardChar(RepeatCount), |
| ForwardChar(RepeatCount), |
| } |
| |
| impl Movement { |
| fn redo(&self, new: Option<RepeatCount>) -> Movement { |
| match *self { |
| Movement::WholeLine => Movement::WholeLine, |
| Movement::BeginningOfLine => Movement::BeginningOfLine, |
| Movement::ViFirstPrint => Movement::ViFirstPrint, |
| Movement::EndOfLine => Movement::EndOfLine, |
| Movement::BackwardWord(previous, word) => { |
| Movement::BackwardWord(repeat_count(previous, new), word) |
| } |
| Movement::ForwardWord(previous, at, word) => { |
| Movement::ForwardWord(repeat_count(previous, new), at, word) |
| } |
| Movement::ViCharSearch(previous, ref char_search) => { |
| Movement::ViCharSearch(repeat_count(previous, new), char_search.clone()) |
| } |
| Movement::BackwardChar(previous) => Movement::BackwardChar(repeat_count(previous, new)), |
| Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)), |
| } |
| } |
| } |
| |
| pub struct EditState { |
| mode: EditMode, |
| // Vi Command/Alternate, Insert/Input mode |
| insert: bool, // vi only ? |
| // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 |
| num_args: i16, |
| last_cmd: Cmd, // vi only |
| consecutive_insert: bool, |
| last_char_search: Option<CharSearch>, // vi only |
| } |
| |
| impl EditState { |
| pub fn new(config: &Config) -> EditState { |
| EditState { |
| mode: config.edit_mode(), |
| insert: true, |
| num_args: 0, |
| last_cmd: Cmd::Noop, |
| consecutive_insert: false, |
| last_char_search: None, |
| } |
| } |
| |
| pub fn is_emacs_mode(&self) -> bool { |
| self.mode == EditMode::Emacs |
| } |
| |
| pub fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> { |
| match self.mode { |
| EditMode::Emacs => self.emacs(rdr), |
| EditMode::Vi if self.insert => self.vi_insert(rdr), |
| EditMode::Vi => self.vi_command(rdr), |
| } |
| } |
| |
| // TODO dynamic prompt (arg: ?) |
| fn emacs_digit_argument<R: RawReader>(&mut self, rdr: &mut R, digit: char) -> Result<KeyPress> { |
| match digit { |
| '0'...'9' => { |
| self.num_args = digit.to_digit(10).unwrap() as i16; |
| } |
| '-' => { |
| self.num_args = -1; |
| } |
| _ => unreachable!(), |
| } |
| loop { |
| let key = try!(rdr.next_key()); |
| match key { |
| KeyPress::Char(digit @ '0'...'9') | |
| KeyPress::Meta(digit @ '0'...'9') => { |
| if self.num_args == -1 { |
| self.num_args *= digit.to_digit(10).unwrap() as i16; |
| } else { |
| self.num_args = self.num_args |
| .saturating_mul(10) |
| .saturating_add(digit.to_digit(10).unwrap() as i16); |
| } |
| } |
| _ => return Ok(key), |
| }; |
| } |
| } |
| |
| fn emacs<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> { |
| let mut key = try!(rdr.next_key()); |
| if let KeyPress::Meta(digit @ '-') = key { |
| key = try!(self.emacs_digit_argument(rdr, digit)); |
| } else if let KeyPress::Meta(digit @ '0'...'9') = key { |
| key = try!(self.emacs_digit_argument(rdr, digit)); |
| } |
| let (n, positive) = self.emacs_num_args(); // consume them in all cases |
| let cmd = match key { |
| KeyPress::Char(c) => { |
| if positive { |
| Cmd::SelfInsert(n, c) |
| } else { |
| Cmd::Unknown |
| } |
| } |
| KeyPress::Ctrl('A') => Cmd::Move(Movement::BeginningOfLine), |
| KeyPress::Ctrl('B') => { |
| if positive { |
| Cmd::Move(Movement::BackwardChar(n)) |
| } else { |
| Cmd::Move(Movement::ForwardChar(n)) |
| } |
| } |
| KeyPress::Ctrl('E') => Cmd::Move(Movement::EndOfLine), |
| KeyPress::Ctrl('F') => { |
| if positive { |
| Cmd::Move(Movement::ForwardChar(n)) |
| } else { |
| Cmd::Move(Movement::BackwardChar(n)) |
| } |
| } |
| KeyPress::Ctrl('G') | |
| KeyPress::Esc => Cmd::Abort, |
| KeyPress::Ctrl('H') | |
| KeyPress::Backspace => { |
| if positive { |
| Cmd::Kill(Movement::BackwardChar(n)) |
| } else { |
| Cmd::Kill(Movement::ForwardChar(n)) |
| } |
| } |
| KeyPress::Tab => Cmd::Complete, |
| KeyPress::Ctrl('K') => { |
| if positive { |
| Cmd::Kill(Movement::EndOfLine) |
| } else { |
| Cmd::Kill(Movement::BeginningOfLine) |
| } |
| } |
| KeyPress::Ctrl('L') => Cmd::ClearScreen, |
| KeyPress::Ctrl('N') => Cmd::NextHistory, |
| KeyPress::Ctrl('P') => Cmd::PreviousHistory, |
| KeyPress::Ctrl('X') => { |
| let snd_key = try!(rdr.next_key()); |
| match snd_key { |
| KeyPress::Ctrl('G') => Cmd::Abort, |
| KeyPress::Ctrl('U') => Cmd::Undo, |
| _ => Cmd::Unknown, |
| } |
| } |
| KeyPress::Meta('\x08') | |
| KeyPress::Meta('\x7f') => { |
| if positive { |
| Cmd::Kill(Movement::BackwardWord(n, Word::Emacs)) |
| } else { |
| Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) |
| } |
| } |
| KeyPress::Meta('<') => Cmd::BeginningOfHistory, |
| KeyPress::Meta('>') => Cmd::EndOfHistory, |
| KeyPress::Meta('B') => { |
| if positive { |
| Cmd::Move(Movement::BackwardWord(n, Word::Emacs)) |
| } else { |
| Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) |
| } |
| } |
| KeyPress::Meta('C') => Cmd::CapitalizeWord, |
| KeyPress::Meta('D') => { |
| if positive { |
| Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) |
| } else { |
| Cmd::Kill(Movement::BackwardWord(n, Word::Emacs)) |
| } |
| } |
| KeyPress::Meta('F') => { |
| if positive { |
| Cmd::Move(Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)) |
| } else { |
| Cmd::Move(Movement::BackwardWord(n, Word::Emacs)) |
| } |
| } |
| KeyPress::Meta('L') => Cmd::DowncaseWord, |
| KeyPress::Meta('T') => Cmd::TransposeWords(n), |
| KeyPress::Meta('U') => Cmd::UpcaseWord, |
| KeyPress::Meta('Y') => Cmd::YankPop, |
| _ => self.common(key, n, positive), |
| }; |
| debug!(target: "rustyline", "Emacs command: {:?}", cmd); |
| Ok(cmd) |
| } |
| |
| fn vi_arg_digit<R: RawReader>(&mut self, rdr: &mut R, digit: char) -> Result<KeyPress> { |
| self.num_args = digit.to_digit(10).unwrap() as i16; |
| loop { |
| let key = try!(rdr.next_key()); |
| match key { |
| KeyPress::Char(digit @ '0'...'9') => { |
| self.num_args = self.num_args |
| .saturating_mul(10) |
| .saturating_add(digit.to_digit(10).unwrap() as i16); |
| } |
| _ => return Ok(key), |
| }; |
| } |
| } |
| |
| fn vi_command<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> { |
| let mut key = try!(rdr.next_key()); |
| if let KeyPress::Char(digit @ '1'...'9') = key { |
| key = try!(self.vi_arg_digit(rdr, digit)); |
| } |
| let no_num_args = self.num_args == 0; |
| let n = self.vi_num_args(); // consume them in all cases |
| let cmd = match key { |
| KeyPress::Char('$') | |
| KeyPress::End => Cmd::Move(Movement::EndOfLine), |
| KeyPress::Char('.') => { // vi-redo |
| if no_num_args { |
| self.last_cmd.redo(None) |
| } else { |
| self.last_cmd.redo(Some(n)) |
| } |
| }, |
| // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket |
| KeyPress::Char('0') => Cmd::Move(Movement::BeginningOfLine), |
| KeyPress::Char('^') => Cmd::Move(Movement::ViFirstPrint), |
| KeyPress::Char('a') => { |
| // vi-append-mode: Vi enter insert mode after the cursor. |
| self.insert = true; |
| Cmd::Move(Movement::ForwardChar(n)) |
| } |
| KeyPress::Char('A') => { |
| // vi-append-eol: Vi enter insert mode at end of line. |
| self.insert = true; |
| Cmd::Move(Movement::EndOfLine) |
| } |
| KeyPress::Char('b') => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), // vi-prev-word |
| KeyPress::Char('B') => Cmd::Move(Movement::BackwardWord(n, Word::Big)), |
| KeyPress::Char('c') => { |
| self.insert = true; |
| match try!(self.vi_cmd_motion(rdr, key, n)) { |
| Some(mvt) => Cmd::Kill(mvt), |
| None => Cmd::Unknown, |
| } |
| } |
| KeyPress::Char('C') => { |
| self.insert = true; |
| Cmd::Kill(Movement::EndOfLine) |
| } |
| KeyPress::Char('d') => { |
| match try!(self.vi_cmd_motion(rdr, key, n)) { |
| Some(mvt) => Cmd::Kill(mvt), |
| None => Cmd::Unknown, |
| } |
| } |
| KeyPress::Char('D') | |
| KeyPress::Ctrl('K') => Cmd::Kill(Movement::EndOfLine), |
| KeyPress::Char('e') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi)), |
| KeyPress::Char('E') => Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big)), |
| KeyPress::Char('i') => { |
| // vi-insertion-mode |
| self.insert = true; |
| Cmd::Noop |
| } |
| KeyPress::Char('I') => { |
| // vi-insert-beg |
| self.insert = true; |
| Cmd::Move(Movement::BeginningOfLine) |
| } |
| KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { |
| // vi-char-search |
| let cs = try!(self.vi_char_search(rdr, c)); |
| match cs { |
| Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)), |
| None => Cmd::Unknown, |
| } |
| } |
| KeyPress::Char(';') => { |
| match self.last_char_search { |
| Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.clone())), |
| None => Cmd::Noop, |
| } |
| } |
| KeyPress::Char(',') => { |
| match self.last_char_search { |
| Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())), |
| None => Cmd::Noop, |
| } |
| } |
| // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n |
| KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put |
| KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put |
| KeyPress::Char('r') => { |
| // vi-replace-char: Vi replace character under the cursor with the next character typed. |
| let ch = try!(rdr.next_key()); |
| match ch { |
| KeyPress::Char(c) => Cmd::Replace(n, c), |
| 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::Kill(Movement::ForwardChar(n)) |
| } |
| KeyPress::Char('S') => { |
| // vi-substitute-line: Vi substitute entire line. |
| self.insert = true; |
| Cmd::Kill(Movement::WholeLine) |
| } |
| KeyPress::Char('u') => Cmd::Undo, |
| // KeyPress::Char('U') => Cmd::???, // revert-line |
| KeyPress::Char('w') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), // vi-next-word |
| KeyPress::Char('W') => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), // vi-next-word |
| KeyPress::Char('x') => Cmd::Kill(Movement::ForwardChar(n)), // vi-delete: TODO move backward if eol |
| KeyPress::Char('X') => Cmd::Kill(Movement::BackwardChar(n)), // vi-rubout |
| KeyPress::Char('y') => { |
| match try!(self.vi_cmd_motion(rdr, key, n)) { |
| Some(mvt) => Cmd::ViYankTo(mvt), |
| None => Cmd::Unknown, |
| } |
| } |
| // KeyPress::Char('Y') => Cmd::???, // vi-yank-to |
| KeyPress::Char('h') | |
| KeyPress::Ctrl('H') | |
| KeyPress::Backspace => Cmd::Move(Movement::BackwardChar(n)), |
| KeyPress::Ctrl('G') => Cmd::Abort, |
| KeyPress::Char('l') | |
| KeyPress::Char(' ') => Cmd::Move(Movement::ForwardChar(n)), |
| KeyPress::Ctrl('L') => Cmd::ClearScreen, |
| KeyPress::Char('+') | |
| KeyPress::Char('j') | // TODO: move to the start of the line. |
| KeyPress::Ctrl('N') => Cmd::NextHistory, |
| KeyPress::Char('-') | |
| KeyPress::Char('k') | // TODO: move to the start of the line. |
| KeyPress::Ctrl('P') => Cmd::PreviousHistory, |
| KeyPress::Ctrl('R') => { |
| self.insert = true; // TODO Validate |
| Cmd::ReverseSearchHistory |
| } |
| KeyPress::Ctrl('S') => { |
| self.insert = true; // TODO Validate |
| Cmd::ForwardSearchHistory |
| } |
| KeyPress::Esc => Cmd::Noop, |
| _ => self.common(key, n, true), |
| }; |
| debug!(target: "rustyline", "Vi command: {:?}", cmd); |
| if cmd.is_repeatable_change() { |
| self.update_last_cmd(cmd.clone()); |
| } |
| Ok(cmd) |
| } |
| |
| fn vi_insert<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> { |
| let key = try!(rdr.next_key()); |
| let cmd = match key { |
| KeyPress::Char(c) => Cmd::SelfInsert(1, c), |
| KeyPress::Ctrl('H') | |
| KeyPress::Backspace => Cmd::Kill(Movement::BackwardChar(1)), |
| KeyPress::Tab => Cmd::Complete, |
| KeyPress::Esc => { |
| // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). |
| self.insert = false; |
| Cmd::Move(Movement::BackwardChar(1)) |
| } |
| _ => self.common(key, 1, true), |
| }; |
| debug!(target: "rustyline", "Vi insert: {:?}", cmd); |
| if cmd.is_repeatable_change() { |
| self.update_last_cmd(cmd.clone()); |
| } |
| self.consecutive_insert = match cmd { |
| Cmd::SelfInsert(_, _) => true, |
| _ => false, |
| }; |
| Ok(cmd) |
| } |
| |
| fn vi_cmd_motion<R: RawReader>(&mut self, |
| rdr: &mut R, |
| key: KeyPress, |
| n: RepeatCount) |
| -> Result<Option<Movement>> { |
| let mut mvt = try!(rdr.next_key()); |
| if mvt == key { |
| return Ok(Some(Movement::WholeLine)); |
| } |
| let mut n = n; |
| if let KeyPress::Char(digit @ '1'...'9') = mvt { |
| // vi-arg-digit |
| mvt = try!(self.vi_arg_digit(rdr, digit)); |
| n = self.vi_num_args().saturating_mul(n); |
| } |
| Ok(match mvt { |
| KeyPress::Char('$') => Some(Movement::EndOfLine), // vi-change-to-eol: Vi change to end of line. |
| KeyPress::Char('0') => Some(Movement::BeginningOfLine), // vi-kill-line-prev: Vi cut from beginning of line to cursor. |
| KeyPress::Char('^') => Some(Movement::ViFirstPrint), |
| KeyPress::Char('b') => Some(Movement::BackwardWord(n, Word::Vi)), |
| KeyPress::Char('B') => Some(Movement::BackwardWord(n, Word::Big)), |
| KeyPress::Char('e') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)), |
| KeyPress::Char('E') => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)), |
| KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { |
| let cs = try!(self.vi_char_search(rdr, c)); |
| match cs { |
| Some(cs) => Some(Movement::ViCharSearch(n, cs)), |
| None => None, |
| } |
| } |
| KeyPress::Char(';') => { |
| match self.last_char_search { |
| Some(ref cs) => Some(Movement::ViCharSearch(n, cs.clone())), |
| None => None, |
| } |
| } |
| KeyPress::Char(',') => { |
| match self.last_char_search { |
| Some(ref cs) => Some(Movement::ViCharSearch(n, cs.opposite())), |
| None => None, |
| } |
| } |
| KeyPress::Char('h') | |
| KeyPress::Ctrl('H') | |
| KeyPress::Backspace => Some(Movement::BackwardChar(n)), // vi-delete-prev-char: Vi move to previous character (backspace). |
| KeyPress::Char('l') | |
| KeyPress::Char(' ') => Some(Movement::ForwardChar(n)), |
| KeyPress::Char('w') => { |
| // 'cw' is 'ce' |
| if key == KeyPress::Char('c') { |
| Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)) |
| } else { |
| Some(Movement::ForwardWord(n, At::Start, Word::Vi)) |
| } |
| } |
| KeyPress::Char('W') => { |
| // 'cW' is 'cE' |
| if key == KeyPress::Char('c') { |
| Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)) |
| } else { |
| Some(Movement::ForwardWord(n, At::Start, Word::Big)) |
| } |
| } |
| _ => None, |
| }) |
| } |
| |
| fn vi_char_search<R: RawReader>(&mut self, |
| rdr: &mut R, |
| cmd: char) |
| -> Result<Option<CharSearch>> { |
| let ch = try!(rdr.next_key()); |
| Ok(match ch { |
| KeyPress::Char(ch) => { |
| let cs = match cmd { |
| 'f' => CharSearch::Forward(ch), |
| 't' => CharSearch::ForwardBefore(ch), |
| 'F' => CharSearch::Backward(ch), |
| 'T' => CharSearch::BackwardAfter(ch), |
| _ => unreachable!(), |
| }; |
| self.last_char_search = Some(cs.clone()); |
| Some(cs) |
| } |
| _ => None, |
| }) |
| } |
| |
| fn common(&mut self, key: KeyPress, n: RepeatCount, positive: bool) -> Cmd { |
| match key { |
| KeyPress::Home => Cmd::Move(Movement::BeginningOfLine), |
| KeyPress::Left => { |
| if positive { |
| Cmd::Move(Movement::BackwardChar(n)) |
| } else { |
| Cmd::Move(Movement::ForwardChar(n)) |
| } |
| } |
| KeyPress::Ctrl('C') => Cmd::Interrupt, |
| KeyPress::Ctrl('D') => Cmd::EndOfFile, |
| KeyPress::Delete => { |
| if positive { |
| Cmd::Kill(Movement::ForwardChar(n)) |
| } else { |
| Cmd::Kill(Movement::BackwardChar(n)) |
| } |
| } |
| KeyPress::End => Cmd::Move(Movement::EndOfLine), |
| KeyPress::Right => { |
| if positive { |
| Cmd::Move(Movement::ForwardChar(n)) |
| } else { |
| Cmd::Move(Movement::BackwardChar(n)) |
| } |
| } |
| KeyPress::Ctrl('J') | |
| KeyPress::Enter => Cmd::AcceptLine, |
| KeyPress::Down => Cmd::NextHistory, |
| KeyPress::Up => Cmd::PreviousHistory, |
| 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') => { |
| if positive { |
| Cmd::Kill(Movement::BeginningOfLine) |
| } else { |
| Cmd::Kill(Movement::EndOfLine) |
| } |
| }, |
| KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution |
| KeyPress::Ctrl('V') => Cmd::QuotedInsert, |
| KeyPress::Ctrl('W') => { |
| if positive { |
| Cmd::Kill(Movement::BackwardWord(n, Word::Big)) |
| } else { |
| Cmd::Kill(Movement::ForwardWord(n, At::AfterEnd, Word::Big)) |
| } |
| } |
| KeyPress::Ctrl('Y') => { |
| if positive { |
| Cmd::Yank(n, Anchor::Before) |
| } else { |
| Cmd::Unknown // TODO Validate |
| } |
| } |
| KeyPress::Ctrl('Z') => Cmd::Suspend, |
| KeyPress::Ctrl('_') => Cmd::Undo, |
| KeyPress::UnknownEscSeq => Cmd::Noop, |
| _ => Cmd::Unknown, |
| } |
| } |
| |
| fn num_args(&mut self) -> i16 { |
| let num_args = match self.num_args { |
| 0 => 1, |
| _ => self.num_args, |
| }; |
| self.num_args = 0; |
| num_args |
| } |
| |
| fn emacs_num_args(&mut self) -> (RepeatCount, bool) { |
| let num_args = self.num_args(); |
| if num_args < 0 { |
| if let (n, false) = num_args.overflowing_abs() { |
| (n as RepeatCount, false) |
| } else { |
| (RepeatCount::max_value(), false) |
| } |
| } else { |
| (num_args as RepeatCount, true) |
| } |
| } |
| |
| fn vi_num_args(&mut self) -> RepeatCount { |
| let num_args = self.num_args(); |
| if num_args < 0 { |
| unreachable!() |
| } else { |
| num_args.abs() as RepeatCount |
| } |
| } |
| |
| fn update_last_cmd(&mut self, new: Cmd) { |
| // consecutive char inserts are repeatable not only the last one... |
| if !self.consecutive_insert { |
| self.last_cmd = new; |
| } else if let Cmd::SelfInsert(_, c) = new { |
| match self.last_cmd { |
| Cmd::SelfInsert(_, pc) => { |
| let mut text = String::new(); |
| text.push(pc); |
| text.push(c); |
| self.last_cmd = Cmd::Insert(1, text); |
| } |
| Cmd::Insert(_, ref mut text) => { |
| text.push(c); |
| } |
| _ => self.last_cmd = new, |
| } |
| } else { |
| self.last_cmd = new; |
| } |
| } |
| } |