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