diff --git a/examples/example.rs b/examples/example.rs index 9f047c64d9e7fd86851017cda8519e96977383c7..8f0233736d2100a2de17c52936a914597af6d584 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -6,7 +6,7 @@ use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError}; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::{Config, CompletionType, Editor, EditMode}; +use rustyline::{Cmd, Config, CompletionType, Editor, EditMode, KeyPress}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -27,6 +27,8 @@ fn main() { let c = FilenameCompleter::new(); let mut rl = Editor::with_config(config); rl.set_completer(Some(c)); + rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); + rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); if rl.load_history("history.txt").is_err() { println!("No previous history."); } diff --git a/src/consts.rs b/src/consts.rs index a1fc550b2db07b56e1d3c4fdba9b3af3ec39ca55..aadac847a23da4e52ad543f3044008963a26e2cf 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -53,7 +53,7 @@ pub fn char_to_key_press(c: char) -> KeyPress { '\x19' => KeyPress::Ctrl('Y'), '\x1a' => KeyPress::Ctrl('Z'), '\x1b' => KeyPress::Esc, - '\x7f' => KeyPress::Backspace, // TODO Validate + '\x7f' => KeyPress::Backspace, _ => KeyPress::Null, } } diff --git a/src/history.rs b/src/history.rs index 5bb46d7e4c102db436d03e0a0872e7fcf9a25a0a..e283cb4ab94629fc1717711b2ab541a03079866e 100644 --- a/src/history.rs +++ b/src/history.rs @@ -118,7 +118,7 @@ impl History { fix_perm(&file); let mut wtr = BufWriter::new(file); for entry in &self.entries { - try!(wtr.write_all(&entry.as_bytes())); + try!(wtr.write_all(entry.as_bytes())); try!(wtr.write_all(b"\n")); } Ok(()) @@ -149,6 +149,18 @@ impl History { /// Return None if no entry contains `term` between [start, len -1] for forward search /// or between [0, start] for reverse search. pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> { + let test = |entry: &String| entry.contains(term); + self.search_match(term, start, dir, test) + } + + pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> { + let test = |entry: &String| entry.starts_with(term); + self.search_match(term, start, dir, test) + } + + fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize> + where F: Fn(&String) -> bool + { if term.is_empty() || start >= self.len() { return None; } @@ -158,14 +170,11 @@ impl History { .iter() .rev() .skip(self.entries.len() - 1 - start) - .position(|entry| entry.contains(term)); + .position(test); index.and_then(|index| Some(start - index)) } Direction::Forward => { - let index = self.entries - .iter() - .skip(start) - .position(|entry| entry.contains(term)); + let index = self.entries.iter().skip(start).position(test); index.and_then(|index| Some(index + start)) } } diff --git a/src/keymap.rs b/src/keymap.rs index 0a180ee26b384f364423634b39bcb95b11e25309..e158bb518d939457e31fa9cd806918bd51b53cca 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -23,6 +23,8 @@ pub enum Cmd { EndOfFile, EndOfHistory, ForwardSearchHistory, + HistorySearchBackward, + HistorySearchForward, Insert(RepeatCount, String), Interrupt, Kill(Movement), diff --git a/src/lib.rs b/src/lib.rs index cb69b2d299d4bd0a96356f6733b4c39bd3e38961..19c0baba93f2b78490f294e14da8cb4d7316a25c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -562,6 +562,36 @@ fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> s.refresh_line() } +fn edit_history_search(s: &mut State, history: &History, dir: Direction) -> Result<()> { + if history.is_empty() { + return Ok(()); + } + if s.history_index == history.len() { + if dir == Direction::Reverse { + // Save the current edited line before to overwrite it + s.snapshot(); + } else { + return Ok(()); + } + } else if s.history_index == 0 && dir == Direction::Reverse { + return Ok(()); + } + if dir == Direction::Reverse { + s.history_index -= 1; + } else { + s.history_index += 1; + } + if let Some(history_index) = + history.starts_with(&s.line.as_str()[..s.line.pos()], s.history_index, dir) { + s.history_index = history_index; + let buf = history.get(history_index).unwrap(); + s.line.update(buf, buf.len()); + s.refresh_line() + } else { + Ok(()) + } +} + /// Substitute the currently edited line with the first/last history entry. fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { if history.is_empty() { @@ -966,6 +996,12 @@ fn readline_edit<C: Completer>(prompt: &str, // Fetch the previous command from the history list. try!(edit_history_next(&mut s, &editor.history, true)) } + Cmd::HistorySearchBackward => { + try!(edit_history_search(&mut s, &editor.history, Direction::Reverse)) + } + Cmd::HistorySearchForward => { + try!(edit_history_search(&mut s, &editor.history, Direction::Forward)) + } Cmd::TransposeChars => { // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a91ad9f6a53564375c2990b789b01da18fe846bc..17a439e9511ad5b830973522fa66aece8780aa47 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -161,8 +161,6 @@ impl PosixRawReader { } }) } else { - // TODO ESC-N (n): search history forward not interactively - // TODO ESC-P (p): search history backward not interactively // TODO ESC-R (r): Undo all changes made to this line. Ok(match seq1 { '\x08' => KeyPress::Meta('\x08'), // Backspace @@ -175,6 +173,8 @@ impl PosixRawReader { 'd' | 'D' => KeyPress::Meta('D'), 'f' | 'F' => KeyPress::Meta('F'), 'l' | 'L' => KeyPress::Meta('L'), + 'n' | 'N' => KeyPress::Meta('N'), + 'p' | 'P' => KeyPress::Meta('P'), 't' | 'T' => KeyPress::Meta('T'), 'u' | 'U' => KeyPress::Meta('U'), 'y' | 'Y' => KeyPress::Meta('Y'), diff --git a/src/tty/windows.rs b/src/tty/windows.rs index d9a14e16078b38f834b24f70b8a911cfefde4644..7b73e2ff58d31b63477bb33def2885a744f18d31 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -154,6 +154,8 @@ impl RawReader for ConsoleRawReader { 'd' | 'D' => KeyPress::Meta('D'), 'f' | 'F' => KeyPress::Meta('F'), 'l' | 'L' => KeyPress::Meta('L'), + 'n' | 'N' => KeyPress::Meta('N'), + 'p' | 'P' => KeyPress::Meta('P'), 't' | 'T' => KeyPress::Meta('T'), 'u' | 'U' => KeyPress::Meta('U'), 'y' | 'Y' => KeyPress::Meta('Y'),