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