From ac74a32209828d13b2cba56c06613e2b0a32e736 Mon Sep 17 00:00:00 2001 From: gwenn <gtreguier@gmail.com> Date: Thu, 25 Aug 2016 21:33:11 +0200 Subject: [PATCH] Introduce Config struct. BREAKING CHANGE --- examples/example.rs | 7 ++- src/config.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++ src/history.rs | 43 +++++---------- src/lib.rs | 84 +++++++++--------------------- 4 files changed, 168 insertions(+), 90 deletions(-) create mode 100644 src/config.rs diff --git a/examples/example.rs b/examples/example.rs index 62fb4fe6..18bf58b4 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::Editor; +use rustyline::{Config, Editor}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -14,8 +14,11 @@ static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m "; static PROMPT: &'static str = ">> "; fn main() { + let config = Config::builder() + .history_ignore_space(true) + .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new().history_ignore_space(true); + let mut rl = Editor::new(config); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..49add02a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,124 @@ +//! Customize line editor +use std::default::Default; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Config { + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + max_history_size: usize, + history_duplicates: HistoryDuplicates, + history_ignore_space: bool, + completion_type: CompletionType, + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + completion_prompt_limit: usize, +} + +impl Config { + pub fn builder() -> Builder { + Builder::new() + } + + /// Tell the maximum length for the history. + pub fn max_history_size(&self) -> usize { + self.max_history_size + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_duplicates(&self) -> HistoryDuplicates { + self.history_duplicates + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(&self) -> bool { + self.history_ignore_space + } + + pub fn completion_type(&self) -> CompletionType { + self.completion_type + } + + pub fn completion_prompt_limit(&self) -> usize { + self.completion_prompt_limit + } +} + +impl Default for Config { + fn default() -> Config { + Config { + max_history_size: 100, + history_duplicates: HistoryDuplicates::IgnoreConsecutive, + history_ignore_space: false, + completion_type: CompletionType::Circular, // TODO Validate + completion_prompt_limit: 100, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HistoryDuplicates { + AlwaysAdd, + IgnoreConsecutive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompletionType { + /// Complete the next full match (like in Vim by default) + Circular, + /// Complete till longest match. + /// When more than one match, list all matches + /// (like in Bash/Readline). + List, +} + +#[derive(Debug)] +pub struct Builder { + p: Config, +} + +impl Builder { + pub fn new() -> Builder { + Builder { p: Config::default() } + } + + /// Set the maximum length for the history. + pub fn max_history_size(mut self, max_size: usize) -> Builder { + self.p.max_history_size = max_size; + self + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_ignore_dups(mut self, yes: bool) -> Builder { + self.p.history_duplicates = if yes { + HistoryDuplicates::IgnoreConsecutive + } else { + HistoryDuplicates::AlwaysAdd + }; + self + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(mut self, yes: bool) -> Builder { + self.p.history_ignore_space = yes; + self + } + + /// Set `completion_type`. + pub fn completion_type(mut self, completion_type: CompletionType) -> Builder { + self.p.completion_type = completion_type; + self + } + + pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder { + self.p.completion_prompt_limit = completion_prompt_limit; + self + } + + pub fn build(self) -> Config { + self.p + } +} diff --git a/src/history.rs b/src/history.rs index 5e7d89c4..59f31262 100644 --- a/src/history.rs +++ b/src/history.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::path::Path; use super::Result; +use config::{Config, HistoryDuplicates}; pub struct History { entries: VecDeque<String>, @@ -13,30 +14,16 @@ pub struct History { ignore_dups: bool, } -const DEFAULT_HISTORY_MAX_LEN: usize = 100; - impl History { - pub fn new() -> History { + pub fn new(config: Config) -> History { History { entries: VecDeque::new(), - max_len: DEFAULT_HISTORY_MAX_LEN, - ignore_space: false, - ignore_dups: true, + max_len: config.max_history_size(), + ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive, + ignore_dups: config.history_ignore_space(), } } - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn ignore_space(&mut self, yes: bool) { - self.ignore_space = yes; - } - - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn ignore_dups(&mut self, yes: bool) { - self.ignore_dups = yes; - } - /// Return the history entry at position `index`, starting from 0. pub fn get(&self, index: usize) -> Option<&String> { self.entries.get(index) @@ -147,19 +134,14 @@ impl History { } } -impl Default for History { - fn default() -> History { - History::new() - } -} - #[cfg(test)] mod tests { extern crate tempdir; use std::path::Path; + use config::Config; fn init() -> super::History { - let mut history = super::History::new(); + let mut history = super::History::new(Config::default()); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -168,15 +150,18 @@ mod tests { #[test] fn new() { - let history = super::History::new(); - assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len); + let config = Config::default(); + let history = super::History::new(config); + assert_eq!(config.max_history_size(), history.max_len); assert_eq!(0, history.entries.len()); } #[test] fn add() { - let mut history = super::History::new(); - history.ignore_space(true); + let config = Config::builder() + .history_ignore_space(true) + .build(); + let mut history = super::History::new(config); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index b115ed9b..bcbcd1bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ //! Usage //! //! ``` -//! let mut rl = rustyline::Editor::<()>::new(); +//! let config = rustyline::Config::default(); +//! let mut rl = rustyline::Editor::<()>::new(config); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -34,6 +35,7 @@ mod kill_ring; pub mod line_buffer; #[cfg(unix)] mod char_iter; +pub mod config; #[macro_use] mod tty; @@ -53,6 +55,7 @@ use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::KillRing; +pub use config::{CompletionType, Config, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result<T> = result::Result<T, error::ReadlineError>; @@ -85,7 +88,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { -> State<'out, 'prompt> { let capacity = MAX_LINE; let cols = tty::get_columns(output_handle); - let prompt_size = calculate_position(prompt, Default::default(), cols); + let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, prompt: prompt, @@ -116,7 +119,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { - let prompt_size = calculate_position(prompt, Default::default(), self.cols); + let prompt_size = calculate_position(prompt, Position::default(), self.cols); self.refresh(prompt, prompt_size) } @@ -526,21 +529,11 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { s.refresh_line() } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CompletionMode { - /// Complete the next full match (like in Vim by default) - Circular, - /// Complete till longest match. - /// When more than one match, list all matches - /// (like in Bash/Readline). - List, -} - /// Completes the line/word fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>, s: &mut State, completer: &Completer, - completion_mode: CompletionMode) + completion_type: CompletionType) -> Result<Option<KeyPress>> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); @@ -548,7 +541,7 @@ fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>, if candidates.is_empty() { try!(beep()); Ok(None) - } else if CompletionMode::Circular == completion_mode { + } else if CompletionType::Circular == completion_type { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -587,7 +580,7 @@ fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>, } } Ok(Some(key)) - } else if CompletionMode::List == completion_mode { + } else if CompletionType::List == completion_type { // beep if ambiguous if candidates.len() > 1 { try!(beep()); @@ -604,7 +597,7 @@ fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>, let key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { - return Ok(Some(key)) + return Ok(Some(key)); } // we got a second tab, maybe show list of possible completions // TODO ... @@ -726,8 +719,10 @@ fn readline_edit<C: Completer>(prompt: &str, // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = - try!(complete_line(&mut rdr, &mut s, completer.unwrap(), editor.completion_mode)); + let next = try!(complete_line(&mut rdr, + &mut s, + completer.unwrap(), + editor.config.completion_type())); if next.is_some() { editor.kill_ring.reset(); key = next.unwrap(); @@ -974,19 +969,19 @@ pub struct Editor<C: Completer> { history: History, completer: Option<C>, kill_ring: KillRing, - completion_mode: CompletionMode, + config: Config, } impl<C: Completer> Editor<C> { - pub fn new() -> Editor<C> { + pub fn new(config: Config) -> Editor<C> { let editor = Editor { unsupported_term: tty::is_unsupported_term(), stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), stdout_isatty: tty::is_a_tty(tty::STDOUT_FILENO), - history: History::new(), + history: History::new(config), completer: None, kill_ring: KillRing::new(60), - completion_mode: CompletionMode::Circular, + config: config, }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { tty::install_sigwinch_handler(); @@ -1010,20 +1005,6 @@ impl<C: Completer> Editor<C> { } } - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn history_ignore_dups(mut self, yes: bool) -> Editor<C> { - self.history.ignore_dups(yes); - self - } - - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn history_ignore_space(mut self, yes: bool) -> Editor<C> { - self.history.ignore_space(yes); - self - } - /// Load the history from the specified file. pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> { self.history.load(path) @@ -1036,10 +1017,6 @@ impl<C: Completer> Editor<C> { pub fn add_history_entry(&mut self, line: &str) -> bool { self.history.add(line) } - /// Set the maximum length for the history. - pub fn set_history_max_len(&mut self, max_len: usize) { - self.history.set_max_len(max_len) - } /// Clear history. pub fn clear_history(&mut self) { self.history.clear() @@ -1053,17 +1030,6 @@ impl<C: Completer> Editor<C> { pub fn set_completer(&mut self, completer: Option<C>) { self.completer = completer; } - - /// Set completion mode. - pub fn set_completion_mode(&mut self, completion_mode: CompletionMode) { - self.completion_mode = completion_mode; - } -} - -impl<C: Completer> Default for Editor<C> { - fn default() -> Editor<C> { - Editor::new() - } } impl<C: Completer> fmt::Debug for Editor<C> { @@ -1081,8 +1047,8 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; - use CompletionMode; - use State; + use config::{Config, CompletionType}; + use {Position, State}; use super::Result; use tty::Handle; @@ -1098,9 +1064,9 @@ mod test { State { out: out, prompt: "", - prompt_size: Default::default(), + prompt_size: Position::default(), line: LineBuffer::init(line, pos), - cursor: Default::default(), + cursor: Position::default(), cols: cols, old_rows: 0, history_index: 0, @@ -1114,7 +1080,7 @@ mod test { let mut out = ::std::io::sink(); let line = "current edited line"; let mut s = init_state(&mut out, line, 6, 80); - let mut history = History::new(); + let mut history = History::new(Config::default()); history.add("line0"); history.add("line1"); s.history_index = history.len(); @@ -1164,7 +1130,7 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionMode::Circular) + let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionType::Circular) .unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); @@ -1173,7 +1139,7 @@ mod test { #[test] fn prompt_with_ansi_escape_codes() { - let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Default::default(), 80); + let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 80); assert_eq!(3, pos.col); assert_eq!(0, pos.row); } -- GitLab