From 3c97b9dc343b27e15b486363d26135bf75578a68 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Sep 2017 12:40:22 +0200 Subject: [PATCH] Revert change to Completer and introduce an Helper trait --- examples/example.rs | 7 +-- src/completion.rs | 44 +++++++++++-- src/hint.rs | 8 ++- src/history.rs | 6 +- src/lib.rs | 146 ++++++++++++++++++++++++++------------------ src/line_buffer.rs | 12 ++-- src/tty/mod.rs | 2 +- src/tty/windows.rs | 10 +-- src/undo.rs | 8 +-- 9 files changed, 153 insertions(+), 90 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 9bd3ccbdd2..745bff1b5f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,9 +1,7 @@ extern crate log; extern crate rustyline; -use std::cell::RefCell; use std::io::{self, Write}; -use std::rc::Rc; use log::{LogLevel, LogLevelFilter, LogMetadata, LogRecord, SetLoggerError}; use rustyline::completion::FilenameCompleter; @@ -23,7 +21,7 @@ static PROMPT: &'static str = ">> "; struct Hints {} impl Hinter for Hints { - fn hint(&mut self, line: &str, _pos: usize) -> Option { + fn hint(&self, line: &str, _pos: usize) -> Option { if line == "hello" { Some(" \x1b[1mWorld\x1b[m".to_owned()) } else { @@ -41,8 +39,7 @@ fn main() { .build(); let c = FilenameCompleter::new(); let mut rl = Editor::with_config(config); - rl.set_completer(Some(Rc::new(RefCell::new(c)))); - rl.set_hinter(Some(Rc::new(RefCell::new(Hints {})))); + rl.set_helper(Some((c, Hints {}))); rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); if rl.load_history("history.txt").is_err() { diff --git a/src/completion.rs b/src/completion.rs index 677d3a3d50..95f610107c 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -19,7 +19,7 @@ pub trait Completer { /// partial word to be completed. /// /// "ls /usr/loc" => Ok((3, vec!["/usr/local/"])) - fn complete(&mut self, line: &str, pos: usize) -> Result<(usize, Vec)>; + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)>; /// Updates the edited `line` with the `elected` candidate. fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { let end = line.pos(); @@ -27,6 +27,42 @@ pub trait Completer { } } +impl Completer for () { + fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec)> { + Ok((0, Vec::with_capacity(0))) + } + fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str) { + unreachable!() + } +} + +impl<'c, C: ?Sized + Completer> Completer for &'c C { + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { + (**self).complete(line, pos) + } + fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { + (**self).update(line, start, elected) + } +} +macro_rules! box_completer { + ($($id: ident)*) => { + $( + impl Completer for $id { + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { + (**self).complete(line, pos) + } + fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { + (**self).update(line, start, elected) + } + } + )* + } +} + +use std::rc::Rc; +use std::sync::Arc; +box_completer! { Box Rc Arc } + /// A `Completer` for file and folder names. pub struct FilenameCompleter { break_chars: BTreeSet, @@ -94,7 +130,7 @@ impl Default for FilenameCompleter { } impl Completer for FilenameCompleter { - fn complete(&mut self, line: &str, pos: usize) -> Result<(usize, Vec)> { + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars); let path = unescape(path, ESCAPE_CHAR); let matches = try!(filename_complete(&path, ESCAPE_CHAR, &self.break_chars)); @@ -250,8 +286,8 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) { let b1 = c1.as_bytes(); let b2 = candidates[i + 1].as_bytes(); - if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || - b1[longest_common_prefix] != b2[longest_common_prefix] + if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix + || b1[longest_common_prefix] != b2[longest_common_prefix] { break 'o; } diff --git a/src/hint.rs b/src/hint.rs index 84342dcc67..c15db9c82d 100644 --- a/src/hint.rs +++ b/src/hint.rs @@ -5,5 +5,11 @@ pub trait Hinter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the string that should be displayed or `None` /// if no hint is available for the text the user currently typed. - fn hint(&mut self, line: &str, pos: usize) -> Option; + fn hint(&self, line: &str, pos: usize) -> Option; +} + +impl Hinter for () { + fn hint(&self, _line: &str, _pos: usize) -> Option { + None + } } diff --git a/src/history.rs b/src/history.rs index e26b60bc0a..14af49e3e7 100644 --- a/src/history.rs +++ b/src/history.rs @@ -56,9 +56,9 @@ impl History { if self.max_len == 0 { return false; } - if line.as_ref().is_empty() || - (self.ignore_space && - line.as_ref() + if line.as_ref().is_empty() + || (self.ignore_space + && line.as_ref() .chars() .next() .map_or(true, |c| c.is_whitespace())) diff --git a/src/lib.rs b/src/lib.rs index 31011eb1bc..6116ca4429 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! Usage //! //! ``` -//! let mut rl = rustyline::Editor::new(); +//! let mut rl = rustyline::Editor::<()>::new(); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -86,7 +86,7 @@ struct State<'out, 'prompt> { byte_buffer: [u8; 4], edit_state: EditState, changes: Rc>, - hinter: Option>>, + hinter: Option<&'out Hinter>, } impl<'out, 'prompt> State<'out, 'prompt> { @@ -96,7 +96,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { prompt: &'prompt str, history_index: usize, custom_bindings: Rc>>, - hinter: Option>>, + hinter: Option<&'out Hinter>, ) -> State<'out, 'prompt> { let capacity = MAX_LINE; let prompt_size = out.calculate_position(prompt, Position::default()); @@ -181,10 +181,8 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn hint(&self) -> Option { - if let Some(ref hinter) = self.hinter { - hinter - .borrow_mut() - .hint(self.line.as_str(), self.line.pos()) + if let Some(hinter) = self.hinter { + hinter.hint(self.line.as_str(), self.line.pos()) } else { None } @@ -212,8 +210,8 @@ fn edit_insert(s: &mut State, ch: char, n: RepeatCount) -> Result<()> { if push { let prompt_size = s.prompt_size; let hint = s.hint(); - if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.out.get_columns() && - hint.is_none() + if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.out.get_columns() + && hint.is_none() { // Avoid a full update of the line in the trivial case. let cursor = s.out @@ -541,10 +539,10 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { } /// Completes the line/word -fn complete_line( +fn complete_line( rdr: &mut R, s: &mut State, - completer: &mut C, + completer: &C, config: &Config, ) -> Result> { // get a list of completions @@ -623,10 +621,10 @@ fn complete_line( let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(s.out.write_and_flush(msg.as_bytes())); s.old_rows += 1; - while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') && - cmd != Cmd::SelfInsert(1, 'n') && - cmd != Cmd::SelfInsert(1, 'N') && - cmd != Cmd::Kill(Movement::BackwardChar(1)) + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') + && cmd != Cmd::SelfInsert(1, 'n') + && cmd != Cmd::SelfInsert(1, 'N') + && cmd != Cmd::Kill(Movement::BackwardChar(1)) { cmd = try!(s.next_cmd(rdr)); } @@ -674,14 +672,14 @@ fn page_completions( if row == pause_row { try!(s.out.write_and_flush(b"\n--More--")); let mut cmd = Cmd::Noop; - while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') && - cmd != Cmd::SelfInsert(1, 'n') && - cmd != Cmd::SelfInsert(1, 'N') && - cmd != Cmd::SelfInsert(1, 'q') && - cmd != Cmd::SelfInsert(1, 'Q') && - cmd != Cmd::SelfInsert(1, ' ') && - cmd != Cmd::Kill(Movement::BackwardChar(1)) && - cmd != Cmd::AcceptLine + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') + && cmd != Cmd::SelfInsert(1, 'n') + && cmd != Cmd::SelfInsert(1, 'N') + && cmd != Cmd::SelfInsert(1, 'q') + && cmd != Cmd::SelfInsert(1, 'Q') + && cmd != Cmd::SelfInsert(1, ' ') + && cmd != Cmd::Kill(Movement::BackwardChar(1)) + && cmd != Cmd::AcceptLine { cmd = try!(s.next_cmd(rdr)); } @@ -804,14 +802,14 @@ fn reverse_incremental_search( /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) #[allow(let_unit_value)] -fn readline_edit( +fn readline_edit( prompt: &str, initial: Option<(&str, &str)>, - editor: &mut Editor, + editor: &mut Editor, original_mode: tty::Mode, ) -> Result { - let completer = editor.completer.as_ref(); - let hinter = editor.hinter.as_ref().cloned(); + let completer = editor.helper.as_ref().map(|h| h.completer()); + let hinter = editor.helper.as_ref().map(|h| h.hinter() as &Hinter); let mut stdout = editor.term.create_writer(); @@ -821,7 +819,7 @@ fn readline_edit( &editor.config, prompt, editor.history.len(), - editor.custom_bindings.clone(), + Rc::clone(&editor.custom_bindings), hinter, ); @@ -847,11 +845,10 @@ fn readline_edit( // autocomplete if cmd == Cmd::Complete && completer.is_some() { - use std::ops::DerefMut; let next = try!(complete_line( &mut rdr, &mut s, - completer.unwrap().borrow_mut().deref_mut(), + completer.unwrap(), &editor.config, )); if next.is_some() { @@ -1093,10 +1090,10 @@ impl Drop for Guard { /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode -fn readline_raw( +fn readline_raw( prompt: &str, initial: Option<(&str, &str)>, - editor: &mut Editor, + editor: &mut Editor, ) -> Result { let original_mode = try!(editor.term.enable_raw_mode()); let guard = Guard(original_mode); @@ -1120,34 +1117,64 @@ fn readline_direct() -> Result { } } +pub trait Helper { + type Completer: Completer; + type Hinter: Hinter; + + fn completer(&self) -> &Self::Completer; + fn hinter(&self) -> &Self::Hinter; +} + +impl Helper for (C, H) { + type Completer = C; + type Hinter = H; + + fn completer(&self) -> &C { + &self.0 + } + fn hinter(&self) -> &H { + &self.1 + } +} +impl Helper for C { + type Completer = C; + type Hinter = (); + + + fn completer(&self) -> &C { + self + } + fn hinter(&self) -> &() { + &() + } +} + /// Line editor -pub struct Editor { +pub struct Editor { term: Terminal, history: History, - completer: Option>>, + helper: Option, kill_ring: Rc>, config: Config, custom_bindings: Rc>>, - hinter: Option>>, } -impl Editor { +impl Editor { /// Create an editor with the default configuration - pub fn new() -> Editor { + pub fn new() -> Editor { Self::with_config(Config::default()) } /// Create an editor with a specific configuration. - pub fn with_config(config: Config) -> Editor { + pub fn with_config(config: Config) -> Editor { let term = Terminal::new(); Editor { term: term, history: History::with_config(config), - completer: None, + helper: None, kill_ring: Rc::new(RefCell::new(KillRing::new(60))), config: config, custom_bindings: Rc::new(RefCell::new(HashMap::new())), - hinter: None, } } @@ -1215,15 +1242,10 @@ impl Editor { &self.history } - /// Register a callback function to be called for tab-completion. - pub fn set_completer(&mut self, completer: Option>>) { - self.completer = completer; - } - - /// Register a hints function to be called to show hints to the uer at the - /// right of the prompt. - pub fn set_hinter(&mut self, hinter: Option>>) { - self.hinter = hinter; + /// Register a callback function to be called for tab-completion + /// or to show hints to the user at the right of the prompt. + pub fn set_helper(&mut self, helper: Option) { + self.helper = helper; } /// Bind a sequence to a command. @@ -1236,7 +1258,7 @@ impl Editor { } /// ``` - /// let mut rl = rustyline::Editor::new(); + /// let mut rl = rustyline::Editor::<()>::new(); /// for readline in rl.iter("> ") { /// match readline { /// Ok(line) => { @@ -1249,7 +1271,7 @@ impl Editor { /// } /// } /// ``` - pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { + pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { Iter { editor: self, prompt: prompt, @@ -1261,7 +1283,7 @@ impl Editor { } } -impl fmt::Debug for Editor { +impl fmt::Debug for Editor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Editor") .field("term", &self.term) @@ -1271,12 +1293,15 @@ impl fmt::Debug for Editor { } /// Edited lines iterator -pub struct Iter<'a> { - editor: &'a mut Editor, +pub struct Iter<'a, H: Helper> +where + H: 'a, +{ + editor: &'a mut Editor, prompt: &'a str, } -impl<'a> Iterator for Iter<'a> { +impl<'a, H: Helper> Iterator for Iter<'a, H> { type Item = Result; fn next(&mut self) -> Option> { @@ -1322,8 +1347,8 @@ mod test { } } - fn init_editor(keys: &[KeyPress]) -> Editor { - let mut editor = Editor::new(); + fn init_editor(keys: &[KeyPress]) -> Editor<()> { + let mut editor = Editor::<()>::new(); editor.term.keys.extend(keys.iter().cloned()); editor } @@ -1368,7 +1393,7 @@ mod test { struct SimpleCompleter; impl Completer for SimpleCompleter { - fn complete(&mut self, line: &str, _pos: usize) -> Result<(usize, Vec)> { + fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec)> { Ok((0, vec![line.to_owned() + "t"])) } } @@ -1379,9 +1404,8 @@ mod test { let mut s = init_state(&mut out, "rus", 3); let keys = &[KeyPress::Enter]; let mut rdr = keys.iter(); - let mut completer = SimpleCompleter; - let cmd = - super::complete_line(&mut rdr, &mut s, &mut completer, &Config::default()).unwrap(); + let completer = SimpleCompleter; + let cmd = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(Cmd::AcceptLine), cmd); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 703790395d..4c09c23822 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -500,8 +500,8 @@ impl LineBuffer { CharSearch::BackwardAfter(c) => pos + c.len_utf8(), CharSearch::Forward(_) => shift + pos, CharSearch::ForwardBefore(_) => { - shift + pos - - self.buf[..shift + pos] + shift + pos + - self.buf[..shift + pos] .chars() .next_back() .unwrap() @@ -759,12 +759,12 @@ impl Deref for LineBuffer { } 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)) + (!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)) + (!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_word_char(word_def: Word, grapheme: &str) -> bool { diff --git a/src/tty/mod.rs b/src/tty/mod.rs index a33f081aec..95cd762bd8 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -13,7 +13,7 @@ pub trait RawMode: Copy + Sized { } /// Translate bytes read from stdin to keys. -pub trait RawReader: Sized { +pub trait RawReader { /// Blocking read of key pressed. fn next_key(&mut self) -> Result; /// For CTRL-V support diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 0dba4c59f7..fcdb8eb246 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -120,8 +120,8 @@ impl RawReader for ConsoleRawReader { } let key_event = unsafe { rec.KeyEvent() }; // writeln!(io::stderr(), "key_event: {:?}", key_event).unwrap(); - if key_event.bKeyDown == 0 && - key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD + if key_event.bKeyDown == 0 + && key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { continue; } @@ -450,9 +450,9 @@ impl Term for Console { } let original_mode = try!(get_console_mode(self.stdin_handle)); // Disable these modes - let raw = original_mode & - !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | - winapi::wincon::ENABLE_PROCESSED_INPUT); + let raw = original_mode + & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT + | winapi::wincon::ENABLE_PROCESSED_INPUT); // Enable these modes let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; diff --git a/src/undo.rs b/src/undo.rs index 910aa1d102..14a540a787 100644 --- a/src/undo.rs +++ b/src/undo.rs @@ -162,8 +162,8 @@ impl Changeset { debug!(target: "rustyline", "Changeset::delete({}, {:?})", indx, string); self.redos.clear(); - if !Self::single_char(string.as_ref()) || - !self.undos + if !Self::single_char(string.as_ref()) + || !self.undos .last() .map_or(false, |lc| lc.delete_seq(indx, string.as_ref().len())) { @@ -196,8 +196,8 @@ impl Changeset { let mut graphemes = s.graphemes(true); graphemes .next() - .map_or(false, |grapheme| grapheme.is_alphanumeric()) && - graphemes.next().is_none() + .map_or(false, |grapheme| grapheme.is_alphanumeric()) + && graphemes.next().is_none() } pub fn replace + Into + Debug>(&mut self, indx: usize, old_: S, new_: S) {