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//! # Ok::<(), rustyline::error::ReadlineError>(())
18//! ```
19#![warn(missing_docs)]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21
22#[cfg(feature = "custom-bindings")]
23mod binding;
24mod command;
25pub mod completion;
26pub mod config;
27mod edit;
28pub mod error;
29pub mod highlight;
30pub mod hint;
31pub mod history;
32mod keymap;
33mod keys;
34mod kill_ring;
35mod layout;
36pub mod line_buffer;
37mod tty;
38mod undo;
39pub mod validate;
40
41use std::fmt;
42use std::io::{self, BufRead, Write};
43use std::path::Path;
44use std::result;
45use std::sync::{Arc, Mutex};
46
47use log::debug;
48use unicode_width::UnicodeWidthStr;
49
50use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal};
51
52#[cfg(feature = "custom-bindings")]
53pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
54use crate::completion::{longest_common_prefix, Candidate, Completer};
55pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
56use crate::edit::State;
57use crate::error::ReadlineError;
58use crate::highlight::Highlighter;
59use crate::hint::Hinter;
60use crate::history::{History, SearchDirection};
61pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
62use crate::keymap::{Bindings, InputState, Refresher};
63pub use crate::keys::{KeyCode, KeyEvent, Modifiers};
64use crate::kill_ring::KillRing;
65pub use crate::tty::ExternalPrinter;
66use crate::validate::Validator;
67
68/// The error type for I/O and Linux Syscalls (Errno)
69pub type Result<T> = result::Result<T, error::ReadlineError>;
70
71/// Completes the line/word
72fn complete_line<H: Helper>(
73    rdr: &mut <Terminal as Term>::Reader,
74    s: &mut State<'_, '_, H>,
75    input_state: &mut InputState,
76    config: &Config,
77) -> Result<Option<Cmd>> {
78    #[cfg(all(unix, feature = "with-fuzzy"))]
79    use skim::prelude::{
80        unbounded, Skim, SkimItem, SkimItemReceiver, SkimItemSender, SkimOptionsBuilder,
81    };
82
83    let completer = s.helper.unwrap();
84    // get a list of completions
85    let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?;
86    // if no completions, we are done
87    if candidates.is_empty() {
88        s.out.beep()?;
89        Ok(None)
90    } else if CompletionType::Circular == config.completion_type() {
91        let mark = s.changes.borrow_mut().begin();
92        // Save the current edited line before overwriting it
93        let backup = s.line.as_str().to_owned();
94        let backup_pos = s.line.pos();
95        let mut cmd;
96        let mut i = 0;
97        loop {
98            // Show completion or original buffer
99            if i < candidates.len() {
100                let candidate = candidates[i].replacement();
101                // TODO we can't highlight the line buffer directly
102                /*let candidate = if let Some(highlighter) = s.highlighter {
103                    highlighter.highlight_candidate(candidate, CompletionType::Circular)
104                } else {
105                    Borrowed(candidate)
106                };*/
107                completer.update(&mut s.line, start, candidate);
108            } else {
109                // Restore current edited line
110                s.line.update(&backup, backup_pos);
111            }
112            s.refresh_line()?;
113
114            cmd = s.next_cmd(input_state, rdr, true, true)?;
115            match cmd {
116                Cmd::Complete => {
117                    i = (i + 1) % (candidates.len() + 1); // Circular
118                    if i == candidates.len() {
119                        s.out.beep()?;
120                    }
121                }
122                Cmd::CompleteBackward => {
123                    if i == 0 {
124                        i = candidates.len(); // Circular
125                        s.out.beep()?;
126                    } else {
127                        i = (i - 1) % (candidates.len() + 1); // Circular
128                    }
129                }
130                Cmd::Abort => {
131                    // Re-show original buffer
132                    if i < candidates.len() {
133                        s.line.update(&backup, backup_pos);
134                        s.refresh_line()?;
135                    }
136                    s.changes.borrow_mut().truncate(mark);
137                    return Ok(None);
138                }
139                _ => {
140                    s.changes.borrow_mut().end();
141                    break;
142                }
143            }
144        }
145        Ok(Some(cmd))
146    } else if CompletionType::List == config.completion_type() {
147        if let Some(lcp) = longest_common_prefix(&candidates) {
148            // if we can extend the item, extend it
149            if lcp.len() > s.line.pos() - start {
150                completer.update(&mut s.line, start, lcp);
151                s.refresh_line()?;
152            }
153        }
154        // beep if ambiguous
155        if candidates.len() > 1 {
156            s.out.beep()?;
157        } else {
158            return Ok(None);
159        }
160        // we can't complete any further, wait for second tab
161        let mut cmd = s.next_cmd(input_state, rdr, true, true)?;
162        // if any character other than tab, pass it to the main loop
163        if cmd != Cmd::Complete {
164            return Ok(Some(cmd));
165        }
166        // move cursor to EOL to avoid overwriting the command line
167        let save_pos = s.line.pos();
168        s.edit_move_end()?;
169        s.line.set_pos(save_pos);
170        // we got a second tab, maybe show list of possible completions
171        let show_completions = if candidates.len() > config.completion_prompt_limit() {
172            let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
173            s.out.write_and_flush(msg.as_str())?;
174            s.layout.end.row += 1;
175            while cmd != Cmd::SelfInsert(1, 'y')
176                && cmd != Cmd::SelfInsert(1, 'Y')
177                && cmd != Cmd::SelfInsert(1, 'n')
178                && cmd != Cmd::SelfInsert(1, 'N')
179                && cmd != Cmd::Kill(Movement::BackwardChar(1))
180            {
181                cmd = s.next_cmd(input_state, rdr, false, true)?;
182            }
183            matches!(cmd, Cmd::SelfInsert(1, 'y' | 'Y'))
184        } else {
185            true
186        };
187        if show_completions {
188            page_completions(rdr, s, input_state, &candidates)
189        } else {
190            s.refresh_line()?;
191            Ok(None)
192        }
193    } else {
194        // if fuzzy feature is enabled and on unix based systems check for the
195        // corresponding completion_type
196        #[cfg(all(unix, feature = "with-fuzzy"))]
197        {
198            use std::borrow::Cow;
199            if CompletionType::Fuzzy == config.completion_type() {
200                struct Candidate {
201                    index: usize,
202                    text: String,
203                }
204                impl SkimItem for Candidate {
205                    fn text(&self) -> Cow<str> {
206                        Cow::Borrowed(&self.text)
207                    }
208                }
209
210                let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded();
211
212                candidates
213                    .iter()
214                    .enumerate()
215                    .map(|(i, c)| Candidate {
216                        index: i,
217                        text: c.display().to_owned(),
218                    })
219                    .for_each(|c| {
220                        let _ = tx_item.send(Arc::new(c));
221                    });
222                drop(tx_item); // so that skim could know when to stop waiting for more items.
223
224                // setup skim and run with input options
225                // will display UI for fuzzy search and return selected results
226                // by default skim multi select is off so only expect one selection
227
228                let options = SkimOptionsBuilder::default()
229                    .height(Some("20%"))
230                    .prompt(Some("? "))
231                    .reverse(true)
232                    .build()
233                    .unwrap();
234
235                let selected_items = Skim::run_with(&options, Some(rx_item))
236                    .map(|out| out.selected_items)
237                    .unwrap_or_else(Vec::new);
238
239                // match the first (and only) returned option with the candidate and update the
240                // line otherwise only refresh line to clear the skim UI changes
241                if let Some(item) = selected_items.first() {
242                    let item: &Candidate = (*item).as_any() // cast to Any
243                        .downcast_ref::<Candidate>() // downcast to concrete type
244                        .expect("something wrong with downcast");
245                    if let Some(candidate) = candidates.get(item.index) {
246                        completer.update(&mut s.line, start, candidate.replacement());
247                    }
248                }
249                s.refresh_line()?;
250            }
251        };
252        Ok(None)
253    }
254}
255
256/// Completes the current hint
257fn complete_hint_line<H: Helper>(s: &mut State<'_, '_, H>) -> Result<()> {
258    let hint = match s.hint.as_ref() {
259        Some(hint) => hint,
260        None => return Ok(()),
261    };
262    s.line.move_end();
263    if let Some(text) = hint.completion() {
264        if s.line.yank(text, 1).is_none() {
265            s.out.beep()?;
266        }
267    } else {
268        s.out.beep()?;
269    }
270    s.refresh_line()
271}
272
273fn page_completions<C: Candidate, H: Helper>(
274    rdr: &mut <Terminal as Term>::Reader,
275    s: &mut State<'_, '_, H>,
276    input_state: &mut InputState,
277    candidates: &[C],
278) -> Result<Option<Cmd>> {
279    use std::cmp;
280
281    let min_col_pad = 2;
282    let cols = s.out.get_columns();
283    let max_width = cmp::min(
284        cols,
285        candidates
286            .iter()
287            .map(|s| s.display().width())
288            .max()
289            .unwrap()
290            + min_col_pad,
291    );
292    let num_cols = cols / max_width;
293
294    let mut pause_row = s.out.get_rows() - 1;
295    let num_rows = (candidates.len() + num_cols - 1) / num_cols;
296    let mut ab = String::new();
297    for row in 0..num_rows {
298        if row == pause_row {
299            s.out.write_and_flush("\n--More--")?;
300            let mut cmd = Cmd::Noop;
301            while cmd != Cmd::SelfInsert(1, 'y')
302                && cmd != Cmd::SelfInsert(1, 'Y')
303                && cmd != Cmd::SelfInsert(1, 'n')
304                && cmd != Cmd::SelfInsert(1, 'N')
305                && cmd != Cmd::SelfInsert(1, 'q')
306                && cmd != Cmd::SelfInsert(1, 'Q')
307                && cmd != Cmd::SelfInsert(1, ' ')
308                && cmd != Cmd::Kill(Movement::BackwardChar(1))
309                && cmd != Cmd::AcceptLine
310                && cmd != Cmd::Newline
311                && !matches!(cmd, Cmd::AcceptOrInsertLine { .. })
312            {
313                cmd = s.next_cmd(input_state, rdr, false, true)?;
314            }
315            match cmd {
316                Cmd::SelfInsert(1, 'y' | 'Y' | ' ') => {
317                    pause_row += s.out.get_rows() - 1;
318                }
319                Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. } => {
320                    pause_row += 1;
321                }
322                _ => break,
323            }
324        }
325        s.out.write_and_flush("\n")?;
326        ab.clear();
327        for col in 0..num_cols {
328            let i = (col * num_rows) + row;
329            if i < candidates.len() {
330                let candidate = &candidates[i].display();
331                let width = candidate.width();
332                if let Some(highlighter) = s.highlighter() {
333                    ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List));
334                } else {
335                    ab.push_str(candidate);
336                }
337                if ((col + 1) * num_rows) + row < candidates.len() {
338                    for _ in width..max_width {
339                        ab.push(' ');
340                    }
341                }
342            }
343        }
344        s.out.write_and_flush(ab.as_str())?;
345    }
346    s.out.write_and_flush("\n")?;
347    s.layout.end.row = 0; // dirty way to make clear_old_rows do nothing
348    s.layout.cursor.row = 0;
349    s.refresh_line()?;
350    Ok(None)
351}
352
353/// Incremental search
354fn reverse_incremental_search<H: Helper>(
355    rdr: &mut <Terminal as Term>::Reader,
356    s: &mut State<'_, '_, H>,
357    input_state: &mut InputState,
358    history: &History,
359) -> Result<Option<Cmd>> {
360    if history.is_empty() {
361        return Ok(None);
362    }
363    let mark = s.changes.borrow_mut().begin();
364    // Save the current edited line (and cursor position) before overwriting it
365    let backup = s.line.as_str().to_owned();
366    let backup_pos = s.line.pos();
367
368    let mut search_buf = String::new();
369    let mut history_idx = history.len() - 1;
370    let mut direction = SearchDirection::Reverse;
371    let mut success = true;
372
373    let mut cmd;
374    // Display the reverse-i-search prompt and process chars
375    loop {
376        let prompt = if success {
377            format!("(reverse-i-search)`{}': ", search_buf)
378        } else {
379            format!("(failed reverse-i-search)`{}': ", search_buf)
380        };
381        s.refresh_prompt_and_line(&prompt)?;
382
383        cmd = s.next_cmd(input_state, rdr, true, true)?;
384        if let Cmd::SelfInsert(_, c) = cmd {
385            search_buf.push(c);
386        } else {
387            match cmd {
388                Cmd::Kill(Movement::BackwardChar(_)) => {
389                    search_buf.pop();
390                    continue;
391                }
392                Cmd::ReverseSearchHistory => {
393                    direction = SearchDirection::Reverse;
394                    if history_idx > 0 {
395                        history_idx -= 1;
396                    } else {
397                        success = false;
398                        continue;
399                    }
400                }
401                Cmd::ForwardSearchHistory => {
402                    direction = SearchDirection::Forward;
403                    if history_idx < history.len() - 1 {
404                        history_idx += 1;
405                    } else {
406                        success = false;
407                        continue;
408                    }
409                }
410                Cmd::Abort => {
411                    // Restore current edited line (before search)
412                    s.line.update(&backup, backup_pos);
413                    s.refresh_line()?;
414                    s.changes.borrow_mut().truncate(mark);
415                    return Ok(None);
416                }
417                Cmd::Move(_) => {
418                    s.refresh_line()?; // restore prompt
419                    break;
420                }
421                _ => break,
422            }
423        }
424        success = match history.search(&search_buf, history_idx, direction) {
425            Some(sr) => {
426                history_idx = sr.idx;
427                s.line.update(sr.entry, sr.pos);
428                true
429            }
430            _ => false,
431        };
432    }
433    s.changes.borrow_mut().end();
434    Ok(Some(cmd))
435}
436
437struct Guard<'m>(&'m tty::Mode);
438
439#[allow(unused_must_use)]
440impl Drop for Guard<'_> {
441    fn drop(&mut self) {
442        let Guard(mode) = *self;
443        mode.disable_raw_mode();
444    }
445}
446
447// Helper to handle backspace characters in a direct input
448fn apply_backspace_direct(input: &str) -> String {
449    // Setup the output buffer
450    // No '\b' in the input in the common case, so set the capacity to the input
451    // length
452    let mut out = String::with_capacity(input.len());
453
454    // Keep track of the size of each grapheme from the input
455    // As many graphemes as input bytes in the common case
456    let mut grapheme_sizes: Vec<u8> = Vec::with_capacity(input.len());
457
458    for g in unicode_segmentation::UnicodeSegmentation::graphemes(input, true) {
459        if g == "\u{0008}" {
460            // backspace char
461            if let Some(n) = grapheme_sizes.pop() {
462                // Remove the last grapheme
463                out.truncate(out.len() - n as usize);
464            }
465        } else {
466            out.push_str(g);
467            grapheme_sizes.push(g.len() as u8);
468        }
469    }
470
471    out
472}
473
474fn readline_direct(
475    mut reader: impl BufRead,
476    mut writer: impl Write,
477    validator: &Option<impl Validator>,
478) -> Result<String> {
479    let mut input = String::new();
480
481    loop {
482        if reader.read_line(&mut input)? == 0 {
483            return Err(error::ReadlineError::Eof);
484        }
485        // Remove trailing newline
486        let trailing_n = input.ends_with('\n');
487        let trailing_r;
488
489        if trailing_n {
490            input.pop();
491            trailing_r = input.ends_with('\r');
492            if trailing_r {
493                input.pop();
494            }
495        } else {
496            trailing_r = false;
497        }
498
499        input = apply_backspace_direct(&input);
500
501        match validator.as_ref() {
502            None => return Ok(input),
503            Some(v) => {
504                let mut ctx = input.as_str();
505                let mut ctx = validate::ValidationContext::new(&mut ctx);
506
507                match v.validate(&mut ctx)? {
508                    validate::ValidationResult::Valid(msg) => {
509                        if let Some(msg) = msg {
510                            writer.write_all(msg.as_bytes())?;
511                        }
512                        return Ok(input);
513                    }
514                    validate::ValidationResult::Invalid(Some(msg)) => {
515                        writer.write_all(msg.as_bytes())?;
516                    }
517                    validate::ValidationResult::Incomplete => {
518                        // Add newline and keep on taking input
519                        if trailing_r {
520                            input.push('\r');
521                        }
522                        if trailing_n {
523                            input.push('\n');
524                        }
525                    }
526                    _ => {}
527                }
528            }
529        }
530    }
531}
532
533/// Syntax specific helper.
534///
535/// TODO Tokenizer/parser used for both completion, suggestion, highlighting.
536/// (parse current line once)
537pub trait Helper
538where
539    Self: Completer + Hinter + Highlighter + Validator,
540{
541}
542
543impl Helper for () {}
544
545impl<'h, H: ?Sized + Helper> Helper for &'h H {}
546
547/// Completion/suggestion context
548pub struct Context<'h> {
549    history: &'h History,
550    history_index: usize,
551}
552
553impl<'h> Context<'h> {
554    /// Constructor. Visible for testing.
555    #[must_use]
556    pub fn new(history: &'h History) -> Self {
557        Context {
558            history,
559            history_index: history.len(),
560        }
561    }
562
563    /// Return an immutable reference to the history object.
564    #[must_use]
565    pub fn history(&self) -> &History {
566        self.history
567    }
568
569    /// The history index we are currently editing
570    #[must_use]
571    pub fn history_index(&self) -> usize {
572        self.history_index
573    }
574}
575
576/// Line editor
577#[must_use]
578pub struct Editor<H: Helper> {
579    term: Terminal,
580    history: History,
581    helper: Option<H>,
582    kill_ring: Arc<Mutex<KillRing>>,
583    config: Config,
584    custom_bindings: Bindings,
585}
586
587#[allow(clippy::new_without_default)]
588impl<H: Helper> Editor<H> {
589    /// Create an editor with the default configuration
590    pub fn new() -> Result<Self> {
591        Self::with_config(Config::default())
592    }
593
594    /// Create an editor with a specific configuration.
595    pub fn with_config(config: Config) -> Result<Self> {
596        let term = Terminal::new(
597            config.color_mode(),
598            config.behavior(),
599            config.tab_stop(),
600            config.bell_style(),
601            config.enable_bracketed_paste(),
602        )?;
603        Ok(Self {
604            term,
605            history: History::with_config(config),
606            helper: None,
607            kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
608            config,
609            custom_bindings: Bindings::new(),
610        })
611    }
612
613    /// This method will read a line from STDIN and will display a `prompt`.
614    ///
615    /// It uses terminal-style interaction if `stdin` is connected to a
616    /// terminal.
617    /// Otherwise (e.g., if `stdin` is a pipe or the terminal is not supported),
618    /// it uses file-style interaction.
619    pub fn readline(&mut self, prompt: &str) -> Result<String> {
620        self.readline_with(prompt, None)
621    }
622
623    /// This function behaves in the exact same manner as `readline`, except
624    /// that it pre-populates the input area.
625    ///
626    /// The text that resides in the input area is given as a 2-tuple.
627    /// The string on the left of the tuple is what will appear to the left of
628    /// the cursor and the string on the right is what will appear to the
629    /// right of the cursor.
630    pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
631        self.readline_with(prompt, Some(initial))
632    }
633
634    fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
635        if self.term.is_unsupported() {
636            debug!(target: "rustyline", "unsupported terminal");
637            // Write prompt and flush it to stdout
638            let mut stdout = io::stdout();
639            stdout.write_all(prompt.as_bytes())?;
640            stdout.flush()?;
641
642            readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
643        } else if self.term.is_input_tty() {
644            let (original_mode, term_key_map) = self.term.enable_raw_mode()?;
645            let guard = Guard(&original_mode);
646            let user_input = self.readline_edit(prompt, initial, &original_mode, term_key_map);
647            if self.config.auto_add_history() {
648                if let Ok(ref line) = user_input {
649                    self.add_history_entry(line.as_str());
650                }
651            }
652            drop(guard); // disable_raw_mode(original_mode)?;
653            self.term.writeln()?;
654            user_input
655        } else {
656            debug!(target: "rustyline", "stdin is not a tty");
657            // Not a tty: read from file / pipe.
658            readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
659        }
660    }
661
662    /// Handles reading and editing the readline buffer.
663    /// It will also handle special inputs in an appropriate fashion
664    /// (e.g., C-c will exit readline)
665    fn readline_edit(
666        &mut self,
667        prompt: &str,
668        initial: Option<(&str, &str)>,
669        original_mode: &tty::Mode,
670        term_key_map: tty::KeyMap,
671    ) -> Result<String> {
672        let mut stdout = self.term.create_writer();
673
674        self.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
675        let ctx = Context::new(&self.history);
676        let mut s = State::new(&mut stdout, prompt, self.helper.as_ref(), ctx);
677
678        let mut input_state = InputState::new(&self.config, &self.custom_bindings);
679
680        s.line.set_delete_listener(self.kill_ring.clone());
681        s.line.set_change_listener(s.changes.clone());
682
683        if let Some((left, right)) = initial {
684            s.line
685                .update((left.to_owned() + right).as_ref(), left.len());
686        }
687
688        let mut rdr = self.term.create_reader(&self.config, term_key_map);
689        if self.term.is_output_tty() && self.config.check_cursor_position() {
690            if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
691                if let ReadlineError::WindowResized = e {
692                    s.out.update_size();
693                } else {
694                    return Err(e);
695                }
696            }
697        }
698        s.refresh_line()?;
699
700        loop {
701            let mut cmd = s.next_cmd(&mut input_state, &mut rdr, false, false)?;
702
703            if cmd.should_reset_kill_ring() {
704                self.reset_kill_ring();
705            }
706
707            // First trigger commands that need extra input
708
709            if cmd == Cmd::Complete && s.helper.is_some() {
710                let next = complete_line(&mut rdr, &mut s, &mut input_state, &self.config)?;
711                if let Some(next) = next {
712                    cmd = next;
713                } else {
714                    continue;
715                }
716            }
717
718            if cmd == Cmd::ReverseSearchHistory {
719                // Search history backward
720                let next =
721                    reverse_incremental_search(&mut rdr, &mut s, &mut input_state, &self.history)?;
722                if let Some(next) = next {
723                    cmd = next;
724                } else {
725                    continue;
726                }
727            }
728
729            #[cfg(unix)]
730            if cmd == Cmd::Suspend {
731                original_mode.disable_raw_mode()?;
732                tty::suspend()?;
733                let _ = self.term.enable_raw_mode()?; // TODO original_mode may have changed
734                s.out.update_size(); // window may have been resized
735                s.refresh_line()?;
736                continue;
737            }
738
739            #[cfg(unix)]
740            if cmd == Cmd::QuotedInsert {
741                // Quoted insert
742                let c = rdr.next_char()?;
743                s.edit_insert(c, 1)?;
744                continue;
745            }
746
747            #[cfg(windows)]
748            if cmd == Cmd::PasteFromClipboard {
749                let clipboard = rdr.read_pasted_text()?;
750                s.edit_yank(&input_state, &clipboard[..], Anchor::Before, 1)?;
751            }
752
753            // Tiny test quirk
754            #[cfg(test)]
755            if matches!(
756                cmd,
757                Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. }
758            ) {
759                self.term.cursor = s.layout.cursor.col;
760            }
761
762            // Execute things can be done solely on a state object
763            match command::execute(cmd, &mut s, &input_state, &self.kill_ring, &self.config)? {
764                command::Status::Proceed => continue,
765                command::Status::Submit => break,
766            }
767        }
768
769        // Move to end, in case cursor was in the middle of the line, so that
770        // next thing application prints goes after the input
771        s.edit_move_buffer_end()?;
772
773        if cfg!(windows) {
774            let _ = original_mode; // silent warning
775        }
776        Ok(s.line.into_string())
777    }
778
779    /// Load the history from the specified file.
780    pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
781        self.history.load(path)
782    }
783
784    /// Save the history in the specified file.
785    pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
786        self.history.save(path)
787    }
788
789    /// Append new entries in the specified file.
790    pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
791        self.history.append(path)
792    }
793
794    /// Add a new entry in the history.
795    pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
796        self.history.add(line)
797    }
798
799    /// Clear history.
800    pub fn clear_history(&mut self) {
801        self.history.clear();
802    }
803
804    /// Return a mutable reference to the history object.
805    pub fn history_mut(&mut self) -> &mut History {
806        &mut self.history
807    }
808
809    /// Return an immutable reference to the history object.
810    pub fn history(&self) -> &History {
811        &self.history
812    }
813
814    /// Register a callback function to be called for tab-completion
815    /// or to show hints to the user at the right of the prompt.
816    pub fn set_helper(&mut self, helper: Option<H>) {
817        self.helper = helper;
818    }
819
820    /// Return a mutable reference to the helper.
821    pub fn helper_mut(&mut self) -> Option<&mut H> {
822        self.helper.as_mut()
823    }
824
825    /// Return an immutable reference to the helper.
826    pub fn helper(&self) -> Option<&H> {
827        self.helper.as_ref()
828    }
829
830    /// Bind a sequence to a command.
831    #[cfg(feature = "custom-bindings")]
832    #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
833    pub fn bind_sequence<E: Into<Event>, R: Into<EventHandler>>(
834        &mut self,
835        key_seq: E,
836        handler: R,
837    ) -> Option<EventHandler> {
838        self.custom_bindings
839            .insert(Event::normalize(key_seq.into()), handler.into())
840    }
841
842    /// Remove a binding for the given sequence.
843    #[cfg(feature = "custom-bindings")]
844    #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
845    pub fn unbind_sequence<E: Into<Event>>(&mut self, key_seq: E) -> Option<EventHandler> {
846        self.custom_bindings
847            .remove(&Event::normalize(key_seq.into()))
848    }
849
850    /// Returns an iterator over edited lines.
851    /// Iterator ends at [EOF](ReadlineError::Eof).
852    /// ```
853    /// let mut rl = rustyline::Editor::<()>::new()?;
854    /// for readline in rl.iter("> ") {
855    ///     match readline {
856    ///         Ok(line) => {
857    ///             println!("Line: {}", line);
858    ///         }
859    ///         Err(err) => {
860    ///             println!("Error: {:?}", err);
861    ///             break;
862    ///         }
863    ///     }
864    /// }
865    /// # Ok::<(), rustyline::error::ReadlineError>(())
866    /// ```
867    pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
868        Iter {
869            editor: self,
870            prompt,
871        }
872    }
873
874    fn reset_kill_ring(&self) {
875        let mut kill_ring = self.kill_ring.lock().unwrap();
876        kill_ring.reset();
877    }
878
879    /// If output stream is a tty, this function returns its width and height as
880    /// a number of characters.
881    pub fn dimensions(&mut self) -> Option<(usize, usize)> {
882        if self.term.is_output_tty() {
883            let out = self.term.create_writer();
884            Some((out.get_columns(), out.get_rows()))
885        } else {
886            None
887        }
888    }
889
890    /// Create an external printer
891    pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
892        self.term.create_external_printer()
893    }
894}
895
896impl<H: Helper> config::Configurer for Editor<H> {
897    fn config_mut(&mut self) -> &mut Config {
898        &mut self.config
899    }
900
901    fn set_max_history_size(&mut self, max_size: usize) {
902        self.config_mut().set_max_history_size(max_size);
903        self.history.set_max_len(max_size);
904    }
905
906    fn set_history_ignore_dups(&mut self, yes: bool) {
907        self.config_mut().set_history_ignore_dups(yes);
908        self.history.ignore_dups = yes;
909    }
910
911    fn set_history_ignore_space(&mut self, yes: bool) {
912        self.config_mut().set_history_ignore_space(yes);
913        self.history.ignore_space = yes;
914    }
915
916    fn set_color_mode(&mut self, color_mode: ColorMode) {
917        self.config_mut().set_color_mode(color_mode);
918        self.term.color_mode = color_mode;
919    }
920}
921
922impl<H: Helper> fmt::Debug for Editor<H> {
923    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924        f.debug_struct("Editor")
925            .field("term", &self.term)
926            .field("config", &self.config)
927            .finish()
928    }
929}
930
931struct Iter<'a, H: Helper> {
932    editor: &'a mut Editor<H>,
933    prompt: &'a str,
934}
935
936impl<'a, H: Helper> Iterator for Iter<'a, H> {
937    type Item = Result<String>;
938
939    fn next(&mut self) -> Option<Result<String>> {
940        let readline = self.editor.readline(self.prompt);
941        match readline {
942            Ok(l) => Some(Ok(l)),
943            Err(error::ReadlineError::Eof) => None,
944            e @ Err(_) => Some(e),
945        }
946    }
947}
948
949#[cfg(test)]
950#[macro_use]
951extern crate assert_matches;
952#[cfg(test)]
953mod test;
954
955#[cfg(doctest)]
956doc_comment::doctest!("../README.md");