datafusion_rustyline/
lib.rs

1//! Readline for Rust
2//!
3//! This implementation is based on [Antirez's
4//! Linenoise](https://github.com/antirez/linenoise)
5//!
6//! # Example
7//!
8//! Usage
9//!
10//! ```
11//! let mut rl = rustyline::Editor::<()>::new();
12//! let readline = rl.readline(">> ");
13//! match readline {
14//!     Ok(line) => println!("Line: {:?}", line),
15//!     Err(_) => println!("No input"),
16//! }
17//! ```
18#![allow(unknown_lints)]
19
20extern crate libc;
21#[macro_use]
22extern crate log;
23#[cfg(unix)]
24extern crate nix;
25extern crate unicode_segmentation;
26extern crate unicode_width;
27#[cfg(windows)]
28extern crate winapi;
29
30pub mod completion;
31pub mod config;
32mod consts;
33mod edit;
34pub mod error;
35pub mod hint;
36pub mod history;
37mod keymap;
38mod kill_ring;
39pub mod line_buffer;
40mod undo;
41
42mod tty;
43
44use std::collections::HashMap;
45use std::fmt;
46use std::io::{self, Write};
47use std::path::Path;
48use std::result;
49use std::sync::{Arc, Mutex, RwLock};
50use unicode_width::UnicodeWidthStr;
51
52use tty::{RawMode, RawReader, Renderer, Term, Terminal};
53
54use completion::{longest_common_prefix, Completer};
55pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
56pub use consts::KeyPress;
57use edit::State;
58use hint::Hinter;
59use history::{Direction, History};
60pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
61use keymap::{InputState, Refresher};
62use kill_ring::{KillRing, Mode};
63use line_buffer::WordAction;
64
65/// The error type for I/O and Linux Syscalls (Errno)
66pub type Result<T> = result::Result<T, error::ReadlineError>;
67
68/// Completes the line/word
69fn complete_line<R: RawReader, C: Completer>(
70    rdr: &mut R,
71    s: &mut State,
72    input_state: &mut InputState,
73    completer: &C,
74    config: &Config,
75) -> Result<Option<Cmd>> {
76    // get a list of completions
77    let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
78    // if no completions, we are done
79    if candidates.is_empty() {
80        try!(s.out.beep());
81        Ok(None)
82    } else if CompletionType::Circular == config.completion_type() {
83        let mark = s.changes.borrow_mut().begin();
84        // Save the current edited line before overwriting it
85        let backup = s.line.as_str().to_owned();
86        let backup_pos = s.line.pos();
87        let mut cmd;
88        let mut i = 0;
89        loop {
90            // Show completion or original buffer
91            if i < candidates.len() {
92                completer.update(&mut s.line, start, &candidates[i]);
93                try!(s.refresh_line());
94            } else {
95                // Restore current edited line
96                s.line.update(&backup, backup_pos);
97                try!(s.refresh_line());
98            }
99
100            cmd = try!(s.next_cmd(input_state, rdr, true));
101            match cmd {
102                Cmd::Complete => {
103                    i = (i + 1) % (candidates.len() + 1); // Circular
104                    if i == candidates.len() {
105                        try!(s.out.beep());
106                    }
107                }
108                Cmd::Abort => {
109                    // Re-show original buffer
110                    if i < candidates.len() {
111                        s.line.update(&backup, backup_pos);
112                        try!(s.refresh_line());
113                    }
114                    s.changes.borrow_mut().truncate(mark);
115                    return Ok(None);
116                }
117                _ => {
118                    s.changes.borrow_mut().end();
119                    break;
120                }
121            }
122        }
123        Ok(Some(cmd))
124    } else if CompletionType::List == config.completion_type() {
125        // beep if ambiguous
126        if candidates.len() > 1 {
127            try!(s.out.beep());
128        }
129        if let Some(lcp) = longest_common_prefix(&candidates) {
130            // if we can extend the item, extend it and return to main loop
131            if lcp.len() > s.line.pos() - start {
132                completer.update(&mut s.line, start, lcp);
133                try!(s.refresh_line());
134                return Ok(None);
135            }
136        }
137        // we can't complete any further, wait for second tab
138        let mut cmd = try!(s.next_cmd(input_state, rdr, true));
139        // if any character other than tab, pass it to the main loop
140        if cmd != Cmd::Complete {
141            return Ok(Some(cmd));
142        }
143        // move cursor to EOL to avoid overwriting the command line
144        let save_pos = s.line.pos();
145        try!(s.edit_move_end());
146        s.line.set_pos(save_pos);
147        // we got a second tab, maybe show list of possible completions
148        let show_completions = if candidates.len() > config.completion_prompt_limit() {
149            let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
150            try!(s.out.write_and_flush(msg.as_bytes()));
151            s.old_rows += 1;
152            while cmd != Cmd::SelfInsert(1, 'y')
153                && cmd != Cmd::SelfInsert(1, 'Y')
154                && cmd != Cmd::SelfInsert(1, 'n')
155                && cmd != Cmd::SelfInsert(1, 'N')
156                && cmd != Cmd::Kill(Movement::BackwardChar(1))
157            {
158                cmd = try!(s.next_cmd(input_state, rdr, false));
159            }
160            match cmd {
161                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') => true,
162                _ => false,
163            }
164        } else {
165            true
166        };
167        if show_completions {
168            page_completions(rdr, s, input_state, &candidates)
169        } else {
170            try!(s.refresh_line());
171            Ok(None)
172        }
173    } else {
174        Ok(None)
175    }
176}
177
178fn page_completions<R: RawReader>(
179    rdr: &mut R,
180    s: &mut State,
181    input_state: &mut InputState,
182    candidates: &[String],
183) -> Result<Option<Cmd>> {
184    use std::cmp;
185
186    let min_col_pad = 2;
187    let cols = s.out.get_columns();
188    let max_width = cmp::min(
189        cols,
190        candidates
191            .into_iter()
192            .map(|s| s.as_str().width())
193            .max()
194            .unwrap() + min_col_pad,
195    );
196    let num_cols = cols / max_width;
197
198    let mut pause_row = s.out.get_rows() - 1;
199    let num_rows = (candidates.len() + num_cols - 1) / num_cols;
200    let mut ab = String::new();
201    for row in 0..num_rows {
202        if row == pause_row {
203            try!(s.out.write_and_flush(b"\n--More--"));
204            let mut cmd = Cmd::Noop;
205            while cmd != Cmd::SelfInsert(1, 'y')
206                && cmd != Cmd::SelfInsert(1, 'Y')
207                && cmd != Cmd::SelfInsert(1, 'n')
208                && cmd != Cmd::SelfInsert(1, 'N')
209                && cmd != Cmd::SelfInsert(1, 'q')
210                && cmd != Cmd::SelfInsert(1, 'Q')
211                && cmd != Cmd::SelfInsert(1, ' ')
212                && cmd != Cmd::Kill(Movement::BackwardChar(1))
213                && cmd != Cmd::AcceptLine
214            {
215                cmd = try!(s.next_cmd(input_state, rdr, false));
216            }
217            match cmd {
218                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') | Cmd::SelfInsert(1, ' ') => {
219                    pause_row += s.out.get_rows() - 1;
220                }
221                Cmd::AcceptLine => {
222                    pause_row += 1;
223                }
224                _ => break,
225            }
226            try!(s.out.write_and_flush(b"\n"));
227        } else {
228            try!(s.out.write_and_flush(b"\n"));
229        }
230        ab.clear();
231        for col in 0..num_cols {
232            let i = (col * num_rows) + row;
233            if i < candidates.len() {
234                let candidate = &candidates[i];
235                ab.push_str(candidate);
236                let width = candidate.as_str().width();
237                if ((col + 1) * num_rows) + row < candidates.len() {
238                    for _ in width..max_width {
239                        ab.push(' ');
240                    }
241                }
242            }
243        }
244        try!(s.out.write_and_flush(ab.as_bytes()));
245    }
246    try!(s.out.write_and_flush(b"\n"));
247    try!(s.refresh_line());
248    Ok(None)
249}
250
251/// Incremental search
252fn reverse_incremental_search<R: RawReader>(
253    rdr: &mut R,
254    s: &mut State,
255    input_state: &mut InputState,
256    history: &History,
257) -> Result<Option<Cmd>> {
258    if history.is_empty() {
259        return Ok(None);
260    }
261    let mark = s.changes.borrow_mut().begin();
262    // Save the current edited line (and cursor position) before overwriting it
263    let backup = s.line.as_str().to_owned();
264    let backup_pos = s.line.pos();
265
266    let mut search_buf = String::new();
267    let mut history_idx = history.len() - 1;
268    let mut direction = Direction::Reverse;
269    let mut success = true;
270
271    let mut cmd;
272    // Display the reverse-i-search prompt and process chars
273    loop {
274        let prompt = if success {
275            format!("(reverse-i-search)`{}': ", search_buf)
276        } else {
277            format!("(failed reverse-i-search)`{}': ", search_buf)
278        };
279        try!(s.refresh_prompt_and_line(&prompt));
280
281        cmd = try!(s.next_cmd(input_state, rdr, true));
282        if let Cmd::SelfInsert(_, c) = cmd {
283            search_buf.push(c);
284        } else {
285            match cmd {
286                Cmd::Kill(Movement::BackwardChar(_)) => {
287                    search_buf.pop();
288                    continue;
289                }
290                Cmd::ReverseSearchHistory => {
291                    direction = Direction::Reverse;
292                    if history_idx > 0 {
293                        history_idx -= 1;
294                    } else {
295                        success = false;
296                        continue;
297                    }
298                }
299                Cmd::ForwardSearchHistory => {
300                    direction = Direction::Forward;
301                    if history_idx < history.len() - 1 {
302                        history_idx += 1;
303                    } else {
304                        success = false;
305                        continue;
306                    }
307                }
308                Cmd::Abort => {
309                    // Restore current edited line (before search)
310                    s.line.update(&backup, backup_pos);
311                    try!(s.refresh_line());
312                    s.changes.borrow_mut().truncate(mark);
313                    return Ok(None);
314                }
315                Cmd::Move(_) => {
316                    try!(s.refresh_line()); // restore prompt
317                    break;
318                }
319                _ => break,
320            }
321        }
322        success = match history.search(&search_buf, history_idx, direction) {
323            Some(idx) => {
324                history_idx = idx;
325                let entry = history.get(idx).unwrap();
326                let pos = entry.find(&search_buf).unwrap();
327                s.line.update(entry, pos);
328                true
329            }
330            _ => false,
331        };
332    }
333    s.changes.borrow_mut().end();
334    Ok(Some(cmd))
335}
336
337/// Handles reading and editting the readline buffer.
338/// It will also handle special inputs in an appropriate fashion
339/// (e.g., C-c will exit readline)
340#[allow(let_unit_value)]
341fn readline_edit<H: Helper>(
342    prompt: &str,
343    initial: Option<(&str, &str)>,
344    editor: &mut Editor<H>,
345    original_mode: &tty::Mode,
346) -> Result<String> {
347    let completer = editor.helper.as_ref().map(|h| h.completer());
348    let hinter = editor.helper.as_ref().map(|h| h.hinter() as &Hinter);
349
350    let mut stdout = editor.term.create_writer();
351
352    editor.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
353    let mut s = State::new(&mut stdout, prompt, editor.history.len(), hinter);
354    let mut input_state = InputState::new(&editor.config, Arc::clone(&editor.custom_bindings));
355
356    s.line.set_delete_listener(editor.kill_ring.clone());
357    s.line.set_change_listener(s.changes.clone());
358
359    if let Some((left, right)) = initial {
360        s.line
361            .update((left.to_owned() + right).as_ref(), left.len());
362    }
363
364    try!(s.refresh_line());
365
366    let mut rdr = try!(editor.term.create_reader(&editor.config));
367
368    loop {
369        let rc = s.next_cmd(&mut input_state, &mut rdr, false);
370        let mut cmd = try!(rc);
371
372        if cmd.should_reset_kill_ring() {
373            editor.reset_kill_ring();
374        }
375
376        // autocomplete
377        if cmd == Cmd::Complete && completer.is_some() {
378            let next = try!(complete_line(
379                &mut rdr,
380                &mut s,
381                &mut input_state,
382                completer.unwrap(),
383                &editor.config,
384            ));
385            if next.is_some() {
386                cmd = next.unwrap();
387            } else {
388                continue;
389            }
390        }
391
392        if let Cmd::SelfInsert(n, c) = cmd {
393            try!(s.edit_insert(c, n));
394            continue;
395        } else if let Cmd::Insert(n, text) = cmd {
396            try!(s.edit_yank(&input_state, &text, Anchor::Before, n));
397            continue;
398        }
399
400        if cmd == Cmd::ReverseSearchHistory {
401            // Search history backward
402            let next = try!(reverse_incremental_search(
403                &mut rdr,
404                &mut s,
405                &mut input_state,
406                &editor.history,
407            ));
408            if next.is_some() {
409                cmd = next.unwrap();
410            } else {
411                continue;
412            }
413        }
414
415        match cmd {
416            Cmd::Move(Movement::BeginningOfLine) => {
417                // Move to the beginning of line.
418                try!(s.edit_move_home())
419            }
420            Cmd::Move(Movement::ViFirstPrint) => {
421                try!(s.edit_move_home());
422                try!(s.edit_move_to_next_word(At::Start, Word::Big, 1))
423            }
424            Cmd::Move(Movement::BackwardChar(n)) => {
425                // Move back a character.
426                try!(s.edit_move_backward(n))
427            }
428            Cmd::ReplaceChar(n, c) => try!(s.edit_replace_char(c, n)),
429            Cmd::Replace(mvt, text) => {
430                try!(s.edit_kill(&mvt));
431                if let Some(text) = text {
432                    try!(s.edit_insert_text(&text))
433                }
434            }
435            Cmd::Overwrite(c) => {
436                try!(s.edit_overwrite_char(c));
437            }
438            Cmd::EndOfFile => if !input_state.is_emacs_mode() && !s.line.is_empty() {
439                try!(s.edit_move_end());
440                break;
441            } else if s.line.is_empty() {
442                return Err(error::ReadlineError::Eof);
443            } else {
444                try!(s.edit_delete(1))
445            },
446            Cmd::Move(Movement::EndOfLine) => {
447                // Move to the end of line.
448                try!(s.edit_move_end())
449            }
450            Cmd::Move(Movement::ForwardChar(n)) => {
451                // Move forward a character.
452                try!(s.edit_move_forward(n))
453            }
454            Cmd::ClearScreen => {
455                // Clear the screen leaving the current line at the top of the screen.
456                try!(s.out.clear_screen());
457                try!(s.refresh_line())
458            }
459            Cmd::NextHistory => {
460                // Fetch the next command from the history list.
461                try!(s.edit_history_next(&editor.history, false))
462            }
463            Cmd::PreviousHistory => {
464                // Fetch the previous command from the history list.
465                try!(s.edit_history_next(&editor.history, true))
466            }
467            Cmd::HistorySearchBackward => {
468                try!(s.edit_history_search(&editor.history, Direction::Reverse))
469            }
470            Cmd::HistorySearchForward => {
471                try!(s.edit_history_search(&editor.history, Direction::Forward))
472            }
473            Cmd::TransposeChars => {
474                // Exchange the char before cursor with the character at cursor.
475                try!(s.edit_transpose_chars())
476            }
477            #[cfg(unix)]
478            Cmd::QuotedInsert => {
479                // Quoted insert
480                let c = try!(rdr.next_char());
481                try!(s.edit_insert(c, 1)) // FIXME
482            }
483            Cmd::Yank(n, anchor) => {
484                // retrieve (yank) last item killed
485                let mut kill_ring = editor.kill_ring.lock().unwrap();
486                if let Some(text) = kill_ring.yank() {
487                    try!(s.edit_yank(&input_state, text, anchor, n))
488                }
489            }
490            Cmd::ViYankTo(ref mvt) => if let Some(text) = s.line.copy(mvt) {
491                let mut kill_ring = editor.kill_ring.lock().unwrap();
492                kill_ring.kill(&text, Mode::Append)
493            },
494            // TODO CTRL-_ // undo
495            Cmd::AcceptLine => {
496                #[cfg(test)]
497                {
498                    editor.term.cursor = s.cursor.col;
499                }
500                // Accept the line regardless of where the cursor is.
501                try!(s.edit_move_end());
502                if s.hinter.is_some() {
503                    // Force a refresh without hints to leave the previous
504                    // line as the user typed it after a newline.
505                    s.hinter = None;
506                    try!(s.refresh_line());
507                }
508                break;
509            }
510            Cmd::BeginningOfHistory => {
511                // move to first entry in history
512                try!(s.edit_history(&editor.history, true))
513            }
514            Cmd::EndOfHistory => {
515                // move to last entry in history
516                try!(s.edit_history(&editor.history, false))
517            }
518            Cmd::Move(Movement::BackwardWord(n, word_def)) => {
519                // move backwards one word
520                try!(s.edit_move_to_prev_word(word_def, n))
521            }
522            Cmd::CapitalizeWord => {
523                // capitalize word after point
524                try!(s.edit_word(WordAction::CAPITALIZE))
525            }
526            Cmd::Kill(ref mvt) => {
527                try!(s.edit_kill(mvt));
528            }
529            Cmd::Move(Movement::ForwardWord(n, at, word_def)) => {
530                // move forwards one word
531                try!(s.edit_move_to_next_word(at, word_def, n))
532            }
533            Cmd::DowncaseWord => {
534                // lowercase word after point
535                try!(s.edit_word(WordAction::LOWERCASE))
536            }
537            Cmd::TransposeWords(n) => {
538                // transpose words
539                try!(s.edit_transpose_words(n))
540            }
541            Cmd::UpcaseWord => {
542                // uppercase word after point
543                try!(s.edit_word(WordAction::UPPERCASE))
544            }
545            Cmd::YankPop => {
546                // yank-pop
547                let mut kill_ring = editor.kill_ring.lock().unwrap();
548                if let Some((yank_size, text)) = kill_ring.yank_pop() {
549                    try!(s.edit_yank_pop(yank_size, text))
550                }
551            }
552            Cmd::Move(Movement::ViCharSearch(n, cs)) => try!(s.edit_move_to(cs, n)),
553            Cmd::Undo(n) => {
554                s.line.remove_change_listener();
555                if s.changes.borrow_mut().undo(&mut s.line, n) {
556                    try!(s.refresh_line());
557                }
558                s.line.set_change_listener(s.changes.clone());
559            }
560            Cmd::Interrupt => {
561                return Err(error::ReadlineError::Interrupted);
562            }
563            #[cfg(unix)]
564            Cmd::Suspend => {
565                try!(original_mode.disable_raw_mode());
566                try!(tty::suspend());
567                try!(editor.term.enable_raw_mode()); // TODO original_mode may have changed
568                try!(s.refresh_line());
569                continue;
570            }
571            Cmd::Noop => {}
572            _ => {
573                // Ignore the character typed.
574            }
575        }
576    }
577    Ok(s.line.into_string())
578}
579
580struct Guard<'m>(&'m tty::Mode);
581
582#[allow(unused_must_use)]
583impl<'m> Drop for Guard<'m> {
584    fn drop(&mut self) {
585        let Guard(mode) = *self;
586        mode.disable_raw_mode();
587    }
588}
589
590/// Readline method that will enable RAW mode, call the `readline_edit()`
591/// method and disable raw mode
592fn readline_raw<H: Helper>(
593    prompt: &str,
594    initial: Option<(&str, &str)>,
595    editor: &mut Editor<H>,
596) -> Result<String> {
597    let original_mode = try!(editor.term.enable_raw_mode());
598    let guard = Guard(&original_mode);
599    let user_input = readline_edit(prompt, initial, editor, &original_mode);
600    if editor.config.auto_add_history() {
601        if let Ok(ref line) = user_input {
602            editor.add_history_entry(line.as_ref());
603        }
604    }
605    drop(guard); // try!(disable_raw_mode(original_mode));
606    println!();
607    user_input
608}
609
610fn readline_direct() -> Result<String> {
611    let mut line = String::new();
612    if try!(io::stdin().read_line(&mut line)) > 0 {
613        Ok(line)
614    } else {
615        Err(error::ReadlineError::Eof)
616    }
617}
618
619/// Syntax specific helper.
620///
621/// TODO Tokenizer/parser used for both completion, suggestion, highlighting
622pub trait Helper {
623    type Completer: Completer;
624    type Hinter: Hinter;
625
626    fn completer(&self) -> &Self::Completer;
627    fn hinter(&self) -> &Self::Hinter;
628}
629
630impl<C: Completer, H: Hinter> Helper for (C, H) {
631    type Completer = C;
632    type Hinter = H;
633
634    fn completer(&self) -> &C {
635        &self.0
636    }
637    fn hinter(&self) -> &H {
638        &self.1
639    }
640}
641impl<C: Completer> Helper for C {
642    type Completer = C;
643    type Hinter = ();
644
645    fn completer(&self) -> &C {
646        self
647    }
648    fn hinter(&self) -> &() {
649        &()
650    }
651}
652
653/// Line editor
654pub struct Editor<H: Helper> {
655    term: Terminal,
656    history: History,
657    helper: Option<H>,
658    kill_ring: Arc<Mutex<KillRing>>,
659    config: Config,
660    custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
661}
662
663impl<H: Helper> Editor<H> {
664    /// Create an editor with the default configuration
665    pub fn new() -> Editor<H> {
666        Self::with_config(Config::default())
667    }
668
669    /// Create an editor with a specific configuration.
670    pub fn with_config(config: Config) -> Editor<H> {
671        let term = Terminal::new();
672        Editor {
673            term,
674            history: History::with_config(config),
675            helper: None,
676            kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
677            config,
678            custom_bindings: Arc::new(RwLock::new(HashMap::new())),
679        }
680    }
681
682    /// This method will read a line from STDIN and will display a `prompt`.
683    ///
684    /// It uses terminal-style interaction if `stdin` is connected to a
685    /// terminal.
686    /// Otherwise (e.g., if `stdin` is a pipe or the terminal is not supported),
687    /// it uses file-style interaction.
688    pub fn readline(&mut self, prompt: &str) -> Result<String> {
689        self.readline_with(prompt, None)
690    }
691    /// This function behaves in the exact same manner as `readline`, except
692    /// that it pre-populates the input area.
693    ///
694    /// The text that resides in the input area is given as a 2-tuple.
695    /// The string on the left of the tuple is what will appear to the left of
696    /// the cursor and the string on the right is what will appear to the
697    /// right of the cursor.
698    pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
699        self.readline_with(prompt, Some(initial))
700    }
701
702    fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
703        if self.term.is_unsupported() {
704            debug!(target: "rustyline", "unsupported terminal");
705            // Write prompt and flush it to stdout
706            let mut stdout = io::stdout();
707            try!(stdout.write_all(prompt.as_bytes()));
708            try!(stdout.flush());
709
710            readline_direct()
711        } else if !self.term.is_stdin_tty() {
712            debug!(target: "rustyline", "stdin is not a tty");
713            // Not a tty: read from file / pipe.
714            readline_direct()
715        } else {
716            readline_raw(prompt, initial, self)
717        }
718    }
719
720    /// Load the history from the specified file.
721    pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
722        self.history.load(path)
723    }
724    /// Save the history in the specified file.
725    pub fn save_history<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
726        self.history.save(path)
727    }
728    /// Add a new entry in the history.
729    pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
730        self.history.add(line)
731    }
732    /// Clear history.
733    pub fn clear_history(&mut self) {
734        self.history.clear()
735    }
736    /// Return a mutable reference to the history object.
737    pub fn get_history(&mut self) -> &mut History {
738        &mut self.history
739    }
740    /// Return an immutable reference to the history object.
741    pub fn get_history_const(&self) -> &History {
742        &self.history
743    }
744
745    /// Register a callback function to be called for tab-completion
746    /// or to show hints to the user at the right of the prompt.
747    pub fn set_helper(&mut self, helper: Option<H>) {
748        self.helper = helper;
749    }
750
751    #[deprecated(since = "2.0.0", note = "Use set_helper instead")]
752    pub fn set_completer(&mut self, completer: Option<H>) {
753        self.helper = completer;
754    }
755
756    /// Bind a sequence to a command.
757    pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
758        let mut bindings = self.custom_bindings.write().unwrap();
759        bindings.insert(key_seq, cmd)
760    }
761    /// Remove a binding for the given sequence.
762    pub fn unbind_sequence(&mut self, key_seq: KeyPress) -> Option<Cmd> {
763        let mut bindings = self.custom_bindings.write().unwrap();
764        bindings.remove(&key_seq)
765    }
766
767    /// ```
768    /// let mut rl = rustyline::Editor::<()>::new();
769    /// for readline in rl.iter("> ") {
770    ///     match readline {
771    ///         Ok(line) => {
772    ///             println!("Line: {}", line);
773    ///         }
774    ///         Err(err) => {
775    ///             println!("Error: {:?}", err);
776    ///             break;
777    ///         }
778    ///     }
779    /// }
780    /// ```
781    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter<H> {
782        Iter {
783            editor: self,
784            prompt,
785        }
786    }
787
788    fn reset_kill_ring(&self) {
789        let mut kill_ring = self.kill_ring.lock().unwrap();
790        kill_ring.reset();
791    }
792}
793
794impl<H: Helper> fmt::Debug for Editor<H> {
795    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
796        f.debug_struct("Editor")
797            .field("term", &self.term)
798            .field("config", &self.config)
799            .finish()
800    }
801}
802
803/// Edited lines iterator
804pub struct Iter<'a, H: Helper>
805where
806    H: 'a,
807{
808    editor: &'a mut Editor<H>,
809    prompt: &'a str,
810}
811
812impl<'a, H: Helper> Iterator for Iter<'a, H> {
813    type Item = Result<String>;
814
815    fn next(&mut self) -> Option<Result<String>> {
816        let readline = self.editor.readline(self.prompt);
817        match readline {
818            Ok(l) => Some(Ok(l)),
819            Err(error::ReadlineError::Eof) => None,
820            e @ Err(_) => Some(e),
821        }
822    }
823}
824
825#[cfg(test)]
826#[macro_use]
827extern crate assert_matches;
828#[cfg(test)]
829mod test;