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