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