| //! Command processor |
| |
| use std::cell::RefCell; |
| use std::fmt; |
| use std::rc::Rc; |
| use unicode_segmentation::UnicodeSegmentation; |
| use unicode_width::UnicodeWidthChar; |
| |
| use super::Result; |
| use hint::Hinter; |
| use history::{Direction, History}; |
| use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; |
| use keymap::{InputState, Refresher}; |
| use line_buffer::{LineBuffer, WordAction, MAX_LINE}; |
| use tty::{Position, RawReader, Renderer}; |
| use undo::Changeset; |
| |
| /// Represent the state during line editing. |
| /// Implement rendering. |
| pub struct State<'out, 'prompt> { |
| pub out: &'out mut Renderer, |
| prompt: &'prompt str, // Prompt to display (rl_prompt) |
| prompt_size: Position, // Prompt Unicode/visible width and height |
| pub line: LineBuffer, // Edited line buffer |
| pub cursor: Position, /* Cursor position (relative to the start of the prompt |
| * for `row`) */ |
| pub old_rows: usize, // Number of rows used so far (from start of prompt to end of input) |
| history_index: usize, // The history index we are currently editing |
| saved_line_for_history: LineBuffer, // Current edited line before history browsing |
| byte_buffer: [u8; 4], |
| pub changes: Rc<RefCell<Changeset>>, // changes to line, for undo/redo |
| pub hinter: Option<&'out Hinter>, |
| } |
| |
| impl<'out, 'prompt> State<'out, 'prompt> { |
| pub fn new( |
| out: &'out mut Renderer, |
| prompt: &'prompt str, |
| history_index: usize, |
| hinter: Option<&'out Hinter>, |
| ) -> State<'out, 'prompt> { |
| let capacity = MAX_LINE; |
| let prompt_size = out.calculate_position(prompt, Position::default()); |
| State { |
| out, |
| prompt, |
| prompt_size, |
| line: LineBuffer::with_capacity(capacity), |
| cursor: prompt_size, |
| old_rows: 0, |
| history_index, |
| saved_line_for_history: LineBuffer::with_capacity(capacity), |
| byte_buffer: [0; 4], |
| changes: Rc::new(RefCell::new(Changeset::new())), |
| hinter, |
| } |
| } |
| |
| pub fn next_cmd<R: RawReader>( |
| &mut self, |
| input_state: &mut InputState, |
| rdr: &mut R, |
| single_esc_abort: bool, |
| ) -> Result<Cmd> { |
| loop { |
| let rc = input_state.next_cmd(rdr, self, single_esc_abort); |
| if rc.is_err() && self.out.sigwinch() { |
| self.out.update_size(); |
| try!(self.refresh_line()); |
| continue; |
| } |
| if let Ok(Cmd::Replace(_, _)) = rc { |
| self.changes.borrow_mut().begin(); |
| } |
| return rc; |
| } |
| } |
| |
| pub fn backup(&mut self) { |
| self.saved_line_for_history |
| .update(self.line.as_str(), self.line.pos()); |
| } |
| pub fn restore(&mut self) { |
| self.line.update( |
| self.saved_line_for_history.as_str(), |
| self.saved_line_for_history.pos(), |
| ); |
| } |
| |
| pub fn move_cursor(&mut self) -> Result<()> { |
| // calculate the desired position of the cursor |
| let cursor = self |
| .out |
| .calculate_position(&self.line[..self.line.pos()], self.prompt_size); |
| if self.cursor == cursor { |
| return Ok(()); |
| } |
| try!(self.out.move_cursor(self.cursor, cursor)); |
| self.cursor = cursor; |
| Ok(()) |
| } |
| |
| fn refresh(&mut self, prompt: &str, prompt_size: Position, hint: Option<String>) -> Result<()> { |
| let (cursor, end_pos) = try!(self.out.refresh_line( |
| prompt, |
| prompt_size, |
| &self.line, |
| hint, |
| self.cursor.row, |
| self.old_rows, |
| )); |
| |
| self.cursor = cursor; |
| self.old_rows = end_pos.row; |
| Ok(()) |
| } |
| |
| fn hint(&self) -> Option<String> { |
| if let Some(hinter) = self.hinter { |
| hinter.hint(self.line.as_str(), self.line.pos()) |
| } else { |
| None |
| } |
| } |
| } |
| |
| impl<'out, 'prompt> Refresher for State<'out, 'prompt> { |
| fn refresh_line(&mut self) -> Result<()> { |
| let prompt_size = self.prompt_size; |
| let hint = self.hint(); |
| self.refresh(self.prompt, prompt_size, hint) |
| } |
| |
| fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { |
| let prompt_size = self.out.calculate_position(prompt, Position::default()); |
| let hint = self.hint(); |
| self.refresh(prompt, prompt_size, hint) |
| } |
| fn doing_insert(&mut self) { |
| self.changes.borrow_mut().begin(); |
| } |
| fn done_inserting(&mut self) { |
| self.changes.borrow_mut().end(); |
| } |
| fn last_insert(&self) -> Option<String> { |
| self.changes.borrow().last_insert() |
| } |
| } |
| |
| impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| f.debug_struct("State") |
| .field("prompt", &self.prompt) |
| .field("prompt_size", &self.prompt_size) |
| .field("buf", &self.line) |
| .field("cursor", &self.cursor) |
| .field("cols", &self.out.get_columns()) |
| .field("old_rows", &self.old_rows) |
| .field("history_index", &self.history_index) |
| .field("saved_line_for_history", &self.saved_line_for_history) |
| .finish() |
| } |
| } |
| |
| impl<'out, 'prompt> State<'out, 'prompt> { |
| /// Insert the character `ch` at cursor current position. |
| pub fn edit_insert(&mut self, ch: char, n: RepeatCount) -> Result<()> { |
| if let Some(push) = self.line.insert(ch, n) { |
| if push { |
| let prompt_size = self.prompt_size; |
| let hint = self.hint(); |
| if n == 1 |
| && self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns() |
| && hint.is_none() |
| { |
| // Avoid a full update of the line in the trivial case. |
| let cursor = self |
| .out |
| .calculate_position(&self.line[..self.line.pos()], self.prompt_size); |
| self.cursor = cursor; |
| let bits = ch.encode_utf8(&mut self.byte_buffer); |
| let bits = bits.as_bytes(); |
| self.out.write_and_flush(bits) |
| } else { |
| self.refresh(self.prompt, prompt_size, hint) |
| } |
| } else { |
| self.refresh_line() |
| } |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Replace a single (or n) character(s) under the cursor (Vi mode) |
| pub fn edit_replace_char(&mut self, ch: char, n: RepeatCount) -> Result<()> { |
| self.changes.borrow_mut().begin(); |
| let succeed = if let Some(chars) = self.line.delete(n) { |
| let count = chars.graphemes(true).count(); |
| self.line.insert(ch, count); |
| self.line.move_backward(1); |
| true |
| } else { |
| false |
| }; |
| self.changes.borrow_mut().end(); |
| if succeed { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Overwrite the character under the cursor (Vi mode) |
| pub fn edit_overwrite_char(&mut self, ch: char) -> Result<()> { |
| if let Some(end) = self.line.next_pos(1) { |
| { |
| let text = ch.encode_utf8(&mut self.byte_buffer); |
| let start = self.line.pos(); |
| self.line.replace(start..end, text); |
| } |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| // Yank/paste `text` at current position. |
| pub fn edit_yank( |
| &mut self, |
| input_state: &InputState, |
| text: &str, |
| anchor: Anchor, |
| n: RepeatCount, |
| ) -> Result<()> { |
| if let Anchor::After = anchor { |
| self.line.move_forward(1); |
| } |
| if self.line.yank(text, n).is_some() { |
| if !input_state.is_emacs_mode() { |
| self.line.move_backward(1); |
| } |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| // Delete previously yanked text and yank/paste `text` at current position. |
| pub fn edit_yank_pop(&mut self, yank_size: usize, text: &str) -> Result<()> { |
| self.changes.borrow_mut().begin(); |
| let result = if self.line.yank_pop(yank_size, text).is_some() { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| }; |
| self.changes.borrow_mut().end(); |
| result |
| } |
| |
| /// Move cursor on the left. |
| pub fn edit_move_backward(&mut self, n: RepeatCount) -> Result<()> { |
| if self.line.move_backward(n) { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Move cursor on the right. |
| pub fn edit_move_forward(&mut self, n: RepeatCount) -> Result<()> { |
| if self.line.move_forward(n) { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Move cursor to the start of the line. |
| pub fn edit_move_home(&mut self) -> Result<()> { |
| if self.line.move_home() { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Move cursor to the end of the line. |
| pub fn edit_move_end(&mut self) -> Result<()> { |
| if self.line.move_end() { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_kill(&mut self, mvt: &Movement) -> Result<()> { |
| if self.line.kill(mvt) { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_insert_text(&mut self, text: &str) -> Result<()> { |
| if !text.is_empty() { |
| let cursor = self.line.pos(); |
| self.line.insert_str(cursor, text); |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_delete(&mut self, n: RepeatCount) -> Result<()> { |
| if self.line.delete(n).is_some() { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Exchange the char before cursor with the character at cursor. |
| pub fn edit_transpose_chars(&mut self) -> Result<()> { |
| self.changes.borrow_mut().begin(); |
| let succeed = self.line.transpose_chars(); |
| self.changes.borrow_mut().end(); |
| if succeed { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_move_to_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> { |
| if self.line.move_to_prev_word(word_def, n) { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> { |
| if self.line.move_to_next_word(at, word_def, n) { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_move_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> { |
| if self.line.move_to(cs, n) { |
| self.move_cursor() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_word(&mut self, a: WordAction) -> Result<()> { |
| self.changes.borrow_mut().begin(); |
| let succeed = self.line.edit_word(a); |
| self.changes.borrow_mut().end(); |
| if succeed { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn edit_transpose_words(&mut self, n: RepeatCount) -> Result<()> { |
| self.changes.borrow_mut().begin(); |
| let succeed = self.line.transpose_words(n); |
| self.changes.borrow_mut().end(); |
| if succeed { |
| self.refresh_line() |
| } else { |
| Ok(()) |
| } |
| } |
| |
| /// Substitute the currently edited line with the next or previous history |
| /// entry. |
| pub fn edit_history_next(&mut self, history: &History, prev: bool) -> Result<()> { |
| if history.is_empty() { |
| return Ok(()); |
| } |
| if self.history_index == history.len() { |
| if prev { |
| // Save the current edited line before overwriting it |
| self.backup(); |
| } else { |
| return Ok(()); |
| } |
| } else if self.history_index == 0 && prev { |
| return Ok(()); |
| } |
| if prev { |
| self.history_index -= 1; |
| } else { |
| self.history_index += 1; |
| } |
| if self.history_index < history.len() { |
| let buf = history.get(self.history_index).unwrap(); |
| self.changes.borrow_mut().begin(); |
| self.line.update(buf, buf.len()); |
| self.changes.borrow_mut().end(); |
| } else { |
| // Restore current edited line |
| self.restore(); |
| } |
| self.refresh_line() |
| } |
| |
| // Non-incremental, anchored search |
| pub fn edit_history_search(&mut self, history: &History, dir: Direction) -> Result<()> { |
| if history.is_empty() { |
| return self.out.beep(); |
| } |
| if self.history_index == history.len() && dir == Direction::Forward |
| || self.history_index == 0 && dir == Direction::Reverse |
| { |
| return self.out.beep(); |
| } |
| if dir == Direction::Reverse { |
| self.history_index -= 1; |
| } else { |
| self.history_index += 1; |
| } |
| if let Some(history_index) = history.starts_with( |
| &self.line.as_str()[..self.line.pos()], |
| self.history_index, |
| dir, |
| ) { |
| self.history_index = history_index; |
| let buf = history.get(history_index).unwrap(); |
| self.changes.borrow_mut().begin(); |
| self.line.update(buf, buf.len()); |
| self.changes.borrow_mut().end(); |
| self.refresh_line() |
| } else { |
| self.out.beep() |
| } |
| } |
| |
| /// Substitute the currently edited line with the first/last history entry. |
| pub fn edit_history(&mut self, history: &History, first: bool) -> Result<()> { |
| if history.is_empty() { |
| return Ok(()); |
| } |
| if self.history_index == history.len() { |
| if first { |
| // Save the current edited line before overwriting it |
| self.backup(); |
| } else { |
| return Ok(()); |
| } |
| } else if self.history_index == 0 && first { |
| return Ok(()); |
| } |
| if first { |
| self.history_index = 0; |
| let buf = history.get(self.history_index).unwrap(); |
| self.changes.borrow_mut().begin(); |
| self.line.update(buf, buf.len()); |
| self.changes.borrow_mut().end(); |
| } else { |
| self.history_index = history.len(); |
| // Restore current edited line |
| self.restore(); |
| } |
| self.refresh_line() |
| } |
| } |
| |
| #[cfg(test)] |
| pub fn init_state<'out>(out: &'out mut Renderer, line: &str, pos: usize) -> State<'out, 'static> { |
| State { |
| out, |
| prompt: "", |
| prompt_size: Position::default(), |
| line: LineBuffer::init(line, pos, None), |
| cursor: Position::default(), |
| old_rows: 0, |
| history_index: 0, |
| saved_line_for_history: LineBuffer::with_capacity(100), |
| byte_buffer: [0; 4], |
| changes: Rc::new(RefCell::new(Changeset::new())), |
| hinter: None, |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::init_state; |
| use history::History; |
| use tty::Sink; |
| |
| #[test] |
| fn edit_history_next() { |
| let mut out = Sink::new(); |
| let line = "current edited line"; |
| let mut s = init_state(&mut out, line, 6); |
| let mut history = History::new(); |
| history.add("line0"); |
| history.add("line1"); |
| s.history_index = history.len(); |
| |
| for _ in 0..2 { |
| s.edit_history_next(&history, false).unwrap(); |
| assert_eq!(line, s.line.as_str()); |
| } |
| |
| s.edit_history_next(&history, true).unwrap(); |
| assert_eq!(line, s.saved_line_for_history.as_str()); |
| assert_eq!(1, s.history_index); |
| assert_eq!("line1", s.line.as_str()); |
| |
| for _ in 0..2 { |
| s.edit_history_next(&history, true).unwrap(); |
| assert_eq!(line, s.saved_line_for_history.as_str()); |
| assert_eq!(0, s.history_index); |
| assert_eq!("line0", s.line.as_str()); |
| } |
| |
| s.edit_history_next(&history, false).unwrap(); |
| assert_eq!(line, s.saved_line_for_history.as_str()); |
| assert_eq!(1, s.history_index); |
| assert_eq!("line1", s.line.as_str()); |
| |
| s.edit_history_next(&history, false).unwrap(); |
| // assert_eq!(line, s.saved_line_for_history); |
| assert_eq!(2, s.history_index); |
| assert_eq!(line, s.line.as_str()); |
| } |
| } |