Skip to main content

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