1#![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
73pub type Result<T> = result::Result<T, ReadlineError>;
75
76fn 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 let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?;
91 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 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 if i < candidates.len() {
105 let candidate = candidates[i].replacement();
106 completer.update(&mut s.line, start, candidate, &mut s.changes);
113 } else {
114 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); if i == candidates.len() {
124 s.out.beep()?;
125 }
126 }
127 Cmd::CompleteBackward => {
128 if i == 0 {
129 i = candidates.len(); s.out.beep()?;
131 } else {
132 i = (i - 1) % (candidates.len() + 1); }
134 }
135 Cmd::Abort => {
136 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 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 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 cmd = s.next_cmd(input_state, rdr, true, true)?;
169 if cmd != Cmd::Complete {
171 return Ok(Some(cmd));
172 }
173 }
174 let save_pos = s.line.pos();
176 s.edit_move_end()?;
177 s.line.set_pos(save_pos);
178 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 #[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); 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 if let Some(item) = selected_items.first() {
249 let item: &Candidate = (*item).as_any() .downcast_ref::<Candidate>() .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
268fn 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; s.layout.cursor.row = 0;
361 s.refresh_line()?;
362 Ok(None)
363}
364
365fn 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 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 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 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()?; 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
459fn apply_backspace_direct(input: &str) -> String {
461 let mut out = String::with_capacity(input.len());
465
466 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 if let Some(n) = grapheme_sizes.pop() {
474 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 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 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
545pub trait Helper
550where
551 Self: Completer + Hinter + Highlighter + Validator,
552{
553}
554
555impl Helper for () {}
556
557pub struct Context<'h> {
559 history: &'h dyn History,
560 history_index: usize,
561}
562
563impl<'h> Context<'h> {
564 #[must_use]
566 pub fn new(history: &'h dyn History) -> Self {
567 Self {
568 history,
569 history_index: history.len(),
570 }
571 }
572
573 #[must_use]
575 pub fn history(&self) -> &dyn History {
576 self.history
577 }
578
579 #[must_use]
581 pub fn history_index(&self) -> usize {
582 self.history_index
583 }
584}
585
586#[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
598pub type DefaultEditor = Editor<(), DefaultHistory>;
600
601impl<H: Helper> Editor<H, DefaultHistory> {
602 pub fn new() -> Result<Self> {
604 Self::with_config(Config::default())
605 }
606
607 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 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 pub fn readline(&mut self, prompt: &str) -> Result<String> {
638 self.readline_with(prompt, None)
639 }
640
641 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 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); self.term.writeln()?;
672 user_input
673 } else {
674 debug!(target: "rustyline", "stdin is not a tty");
675 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
677 }
678 }
679
680 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(); 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 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 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()?; s.out.update_size(); s.refresh_line()?;
757 continue;
758 }
759
760 #[cfg(unix)]
761 if cmd == Cmd::QuotedInsert {
762 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 #[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 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 s.edit_move_buffer_end(CmdKind::ForcedRefresh)?;
793
794 if cfg!(windows) {
795 let _ = original_mode; }
797 self.buffer = rdr.unbuffer();
798 Ok(s.line.into_string())
799 }
800
801 pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
803 self.history.load(path.as_ref())
804 }
805
806 pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
808 self.history.save(path.as_ref())
809 }
810
811 pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
813 self.history.append(path.as_ref())
814 }
815
816 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 pub fn clear_history(&mut self) -> Result<()> {
823 self.history.clear()
824 }
825
826 pub fn history_mut(&mut self) -> &mut I {
828 &mut self.history
829 }
830
831 pub fn history(&self) -> &I {
833 &self.history
834 }
835
836 pub fn set_helper(&mut self, helper: Option<H>) {
839 self.helper = helper;
840 }
841
842 pub fn helper_mut(&mut self) -> Option<&mut H> {
844 self.helper.as_mut()
845 }
846
847 pub fn helper(&self) -> Option<&H> {
849 self.helper.as_ref()
850 }
851
852 #[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 #[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 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 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 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 pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
917 self.term.create_external_printer()
918 }
919
920 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");