1#![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
75pub type Result<T> = result::Result<T, ReadlineError>;
77
78fn 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 let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?;
93 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 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 if i < candidates.len() {
107 let candidate = candidates[i].replacement();
108 completer.update(&mut s.line, start, candidate, &mut s.changes);
115 } else {
116 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); if i == candidates.len() {
126 s.out.beep()?;
127 }
128 }
129 Cmd::CompleteBackward => {
130 if i == 0 {
131 i = candidates.len(); s.out.beep()?;
133 } else {
134 i = (i - 1) % (candidates.len() + 1); }
136 }
137 Cmd::Abort => {
138 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 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 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 cmd = s.next_cmd(input_state, rdr, true, true)?;
171 if cmd != Cmd::Complete {
173 return Ok(Some(cmd));
174 }
175 }
176 let save_pos = s.line.pos();
178 s.edit_move_end()?;
179 s.line.set_pos(save_pos);
180 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; }
203 s.refresh_line()?;
204 Ok(None)
205 }
206 } else {
207 #[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); 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 if let Some(item) = selected_items.first() {
256 let item: &Candidate = (*item).as_any() .downcast_ref::<Candidate>() .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
275fn 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
370fn 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 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 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 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()?; 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
464fn apply_backspace_direct(input: &str) -> String {
466 let mut out = String::with_capacity(input.len());
470
471 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 if let Some(n) = grapheme_sizes.pop() {
479 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 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 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
550pub trait Helper
555where
556 Self: Completer + Hinter + Highlighter + Validator,
557{
558}
559
560impl Helper for () {}
561
562pub struct Context<'h> {
564 history: &'h dyn History,
565 history_index: usize,
566}
567
568impl<'h> Context<'h> {
569 #[must_use]
571 pub fn new(history: &'h dyn History) -> Self {
572 Self {
573 history,
574 history_index: history.len(),
575 }
576 }
577
578 #[must_use]
580 pub fn history(&self) -> &dyn History {
581 self.history
582 }
583
584 #[must_use]
586 pub fn history_index(&self) -> usize {
587 self.history_index
588 }
589}
590
591#[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
603pub type DefaultEditor = Editor<(), DefaultHistory>;
605
606impl<H: Helper> Editor<H, DefaultHistory> {
607 pub fn new() -> Result<Self> {
609 Self::with_config(Config::default())
610 }
611
612 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 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 pub fn readline<P: Prompt + ?Sized>(&mut self, prompt: &P) -> Result<String> {
644 self.readline_with(prompt, None)
645 }
646
647 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 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); self.term.writeln()?;
686 user_input
687 } else {
688 debug!(target: "rustyline", "stdin is not a tty");
689 readline_direct(io::stdin().lock(), io::stderr(), self.helper.as_ref())
691 }
692 }
693
694 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(); 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 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 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)?; s.out.update_size(); s.refresh_line()?;
771 continue;
772 }
773
774 #[cfg(unix)]
775 if cmd == Cmd::QuotedInsert {
776 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 #[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 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 s.edit_move_buffer_end(CmdKind::ForcedRefresh)?;
807
808 if cfg!(windows) {
809 let _ = original_mode; }
811 self.buffer = rdr.unbuffer();
812 Ok(s.line.into_string())
813 }
814
815 pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
817 self.history.load(path.as_ref())
818 }
819
820 pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
822 self.history.save(path.as_ref())
823 }
824
825 pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
827 self.history.append(path.as_ref())
828 }
829
830 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 pub fn clear_history(&mut self) -> Result<()> {
837 self.history.clear()
838 }
839
840 pub fn history_mut(&mut self) -> &mut I {
842 &mut self.history
843 }
844
845 pub fn history(&self) -> &I {
847 &self.history
848 }
849
850 pub fn set_helper(&mut self, helper: Option<H>) {
853 self.helper = helper;
854 }
855
856 pub fn helper_mut(&mut self) -> Option<&mut H> {
858 self.helper.as_mut()
859 }
860
861 pub fn helper(&self) -> Option<&H> {
863 self.helper.as_ref()
864 }
865
866 #[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 #[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 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 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 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 pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
931 self.term.create_external_printer()
932 }
933
934 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");