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 tty;
38mod undo;
39pub mod validate;
40
41use std::fmt;
42use std::io::{self, BufRead, Write};
43use std::path::Path;
44use std::result;
45use std::sync::{Arc, Mutex};
46
47use log::debug;
48use unicode_width::UnicodeWidthStr;
49
50use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal};
51
52#[cfg(feature = "custom-bindings")]
53pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
54use crate::completion::{longest_common_prefix, Candidate, Completer};
55pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
56use crate::edit::State;
57use crate::error::ReadlineError;
58use crate::highlight::Highlighter;
59use crate::hint::Hinter;
60use crate::history::{History, SearchDirection};
61pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
62use crate::keymap::{Bindings, InputState, Refresher};
63pub use crate::keys::{KeyCode, KeyEvent, Modifiers};
64use crate::kill_ring::KillRing;
65pub use crate::tty::ExternalPrinter;
66use crate::validate::Validator;
67
68pub type Result<T> = result::Result<T, error::ReadlineError>;
70
71fn complete_line<H: Helper>(
73 rdr: &mut <Terminal as Term>::Reader,
74 s: &mut State<'_, '_, H>,
75 input_state: &mut InputState,
76 config: &Config,
77) -> Result<Option<Cmd>> {
78 #[cfg(all(unix, feature = "with-fuzzy"))]
79 use skim::prelude::{
80 unbounded, Skim, SkimItem, SkimItemReceiver, SkimItemSender, SkimOptionsBuilder,
81 };
82
83 let completer = s.helper.unwrap();
84 let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?;
86 if candidates.is_empty() {
88 s.out.beep()?;
89 Ok(None)
90 } else if CompletionType::Circular == config.completion_type() {
91 let mark = s.changes.borrow_mut().begin();
92 let backup = s.line.as_str().to_owned();
94 let backup_pos = s.line.pos();
95 let mut cmd;
96 let mut i = 0;
97 loop {
98 if i < candidates.len() {
100 let candidate = candidates[i].replacement();
101 completer.update(&mut s.line, start, candidate);
108 } else {
109 s.line.update(&backup, backup_pos);
111 }
112 s.refresh_line()?;
113
114 cmd = s.next_cmd(input_state, rdr, true, true)?;
115 match cmd {
116 Cmd::Complete => {
117 i = (i + 1) % (candidates.len() + 1); if i == candidates.len() {
119 s.out.beep()?;
120 }
121 }
122 Cmd::CompleteBackward => {
123 if i == 0 {
124 i = candidates.len(); s.out.beep()?;
126 } else {
127 i = (i - 1) % (candidates.len() + 1); }
129 }
130 Cmd::Abort => {
131 if i < candidates.len() {
133 s.line.update(&backup, backup_pos);
134 s.refresh_line()?;
135 }
136 s.changes.borrow_mut().truncate(mark);
137 return Ok(None);
138 }
139 _ => {
140 s.changes.borrow_mut().end();
141 break;
142 }
143 }
144 }
145 Ok(Some(cmd))
146 } else if CompletionType::List == config.completion_type() {
147 if let Some(lcp) = longest_common_prefix(&candidates) {
148 if lcp.len() > s.line.pos() - start {
150 completer.update(&mut s.line, start, lcp);
151 s.refresh_line()?;
152 }
153 }
154 if candidates.len() > 1 {
156 s.out.beep()?;
157 } else {
158 return Ok(None);
159 }
160 let mut cmd = s.next_cmd(input_state, rdr, true, true)?;
162 if cmd != Cmd::Complete {
164 return Ok(Some(cmd));
165 }
166 let save_pos = s.line.pos();
168 s.edit_move_end()?;
169 s.line.set_pos(save_pos);
170 let show_completions = if candidates.len() > config.completion_prompt_limit() {
172 let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
173 s.out.write_and_flush(msg.as_str())?;
174 s.layout.end.row += 1;
175 while cmd != Cmd::SelfInsert(1, 'y')
176 && cmd != Cmd::SelfInsert(1, 'Y')
177 && cmd != Cmd::SelfInsert(1, 'n')
178 && cmd != Cmd::SelfInsert(1, 'N')
179 && cmd != Cmd::Kill(Movement::BackwardChar(1))
180 {
181 cmd = s.next_cmd(input_state, rdr, false, true)?;
182 }
183 matches!(cmd, Cmd::SelfInsert(1, 'y' | 'Y'))
184 } else {
185 true
186 };
187 if show_completions {
188 page_completions(rdr, s, input_state, &candidates)
189 } else {
190 s.refresh_line()?;
191 Ok(None)
192 }
193 } else {
194 #[cfg(all(unix, feature = "with-fuzzy"))]
197 {
198 use std::borrow::Cow;
199 if CompletionType::Fuzzy == config.completion_type() {
200 struct Candidate {
201 index: usize,
202 text: String,
203 }
204 impl SkimItem for Candidate {
205 fn text(&self) -> Cow<str> {
206 Cow::Borrowed(&self.text)
207 }
208 }
209
210 let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded();
211
212 candidates
213 .iter()
214 .enumerate()
215 .map(|(i, c)| Candidate {
216 index: i,
217 text: c.display().to_owned(),
218 })
219 .for_each(|c| {
220 let _ = tx_item.send(Arc::new(c));
221 });
222 drop(tx_item); let options = SkimOptionsBuilder::default()
229 .height(Some("20%"))
230 .prompt(Some("? "))
231 .reverse(true)
232 .build()
233 .unwrap();
234
235 let selected_items = Skim::run_with(&options, Some(rx_item))
236 .map(|out| out.selected_items)
237 .unwrap_or_else(Vec::new);
238
239 if let Some(item) = selected_items.first() {
242 let item: &Candidate = (*item).as_any() .downcast_ref::<Candidate>() .expect("something wrong with downcast");
245 if let Some(candidate) = candidates.get(item.index) {
246 completer.update(&mut s.line, start, candidate.replacement());
247 }
248 }
249 s.refresh_line()?;
250 }
251 };
252 Ok(None)
253 }
254}
255
256fn complete_hint_line<H: Helper>(s: &mut State<'_, '_, H>) -> Result<()> {
258 let hint = match s.hint.as_ref() {
259 Some(hint) => hint,
260 None => return Ok(()),
261 };
262 s.line.move_end();
263 if let Some(text) = hint.completion() {
264 if s.line.yank(text, 1).is_none() {
265 s.out.beep()?;
266 }
267 } else {
268 s.out.beep()?;
269 }
270 s.refresh_line()
271}
272
273fn page_completions<C: Candidate, H: Helper>(
274 rdr: &mut <Terminal as Term>::Reader,
275 s: &mut State<'_, '_, H>,
276 input_state: &mut InputState,
277 candidates: &[C],
278) -> Result<Option<Cmd>> {
279 use std::cmp;
280
281 let min_col_pad = 2;
282 let cols = s.out.get_columns();
283 let max_width = cmp::min(
284 cols,
285 candidates
286 .iter()
287 .map(|s| s.display().width())
288 .max()
289 .unwrap()
290 + min_col_pad,
291 );
292 let num_cols = cols / max_width;
293
294 let mut pause_row = s.out.get_rows() - 1;
295 let num_rows = (candidates.len() + num_cols - 1) / num_cols;
296 let mut ab = String::new();
297 for row in 0..num_rows {
298 if row == pause_row {
299 s.out.write_and_flush("\n--More--")?;
300 let mut cmd = Cmd::Noop;
301 while cmd != Cmd::SelfInsert(1, 'y')
302 && cmd != Cmd::SelfInsert(1, 'Y')
303 && cmd != Cmd::SelfInsert(1, 'n')
304 && cmd != Cmd::SelfInsert(1, 'N')
305 && cmd != Cmd::SelfInsert(1, 'q')
306 && cmd != Cmd::SelfInsert(1, 'Q')
307 && cmd != Cmd::SelfInsert(1, ' ')
308 && cmd != Cmd::Kill(Movement::BackwardChar(1))
309 && cmd != Cmd::AcceptLine
310 && cmd != Cmd::Newline
311 && !matches!(cmd, Cmd::AcceptOrInsertLine { .. })
312 {
313 cmd = s.next_cmd(input_state, rdr, false, true)?;
314 }
315 match cmd {
316 Cmd::SelfInsert(1, 'y' | 'Y' | ' ') => {
317 pause_row += s.out.get_rows() - 1;
318 }
319 Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. } => {
320 pause_row += 1;
321 }
322 _ => break,
323 }
324 }
325 s.out.write_and_flush("\n")?;
326 ab.clear();
327 for col in 0..num_cols {
328 let i = (col * num_rows) + row;
329 if i < candidates.len() {
330 let candidate = &candidates[i].display();
331 let width = candidate.width();
332 if let Some(highlighter) = s.highlighter() {
333 ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List));
334 } else {
335 ab.push_str(candidate);
336 }
337 if ((col + 1) * num_rows) + row < candidates.len() {
338 for _ in width..max_width {
339 ab.push(' ');
340 }
341 }
342 }
343 }
344 s.out.write_and_flush(ab.as_str())?;
345 }
346 s.out.write_and_flush("\n")?;
347 s.layout.end.row = 0; s.layout.cursor.row = 0;
349 s.refresh_line()?;
350 Ok(None)
351}
352
353fn reverse_incremental_search<H: Helper>(
355 rdr: &mut <Terminal as Term>::Reader,
356 s: &mut State<'_, '_, H>,
357 input_state: &mut InputState,
358 history: &History,
359) -> Result<Option<Cmd>> {
360 if history.is_empty() {
361 return Ok(None);
362 }
363 let mark = s.changes.borrow_mut().begin();
364 let backup = s.line.as_str().to_owned();
366 let backup_pos = s.line.pos();
367
368 let mut search_buf = String::new();
369 let mut history_idx = history.len() - 1;
370 let mut direction = SearchDirection::Reverse;
371 let mut success = true;
372
373 let mut cmd;
374 loop {
376 let prompt = if success {
377 format!("(reverse-i-search)`{}': ", search_buf)
378 } else {
379 format!("(failed reverse-i-search)`{}': ", search_buf)
380 };
381 s.refresh_prompt_and_line(&prompt)?;
382
383 cmd = s.next_cmd(input_state, rdr, true, true)?;
384 if let Cmd::SelfInsert(_, c) = cmd {
385 search_buf.push(c);
386 } else {
387 match cmd {
388 Cmd::Kill(Movement::BackwardChar(_)) => {
389 search_buf.pop();
390 continue;
391 }
392 Cmd::ReverseSearchHistory => {
393 direction = SearchDirection::Reverse;
394 if history_idx > 0 {
395 history_idx -= 1;
396 } else {
397 success = false;
398 continue;
399 }
400 }
401 Cmd::ForwardSearchHistory => {
402 direction = SearchDirection::Forward;
403 if history_idx < history.len() - 1 {
404 history_idx += 1;
405 } else {
406 success = false;
407 continue;
408 }
409 }
410 Cmd::Abort => {
411 s.line.update(&backup, backup_pos);
413 s.refresh_line()?;
414 s.changes.borrow_mut().truncate(mark);
415 return Ok(None);
416 }
417 Cmd::Move(_) => {
418 s.refresh_line()?; break;
420 }
421 _ => break,
422 }
423 }
424 success = match history.search(&search_buf, history_idx, direction) {
425 Some(sr) => {
426 history_idx = sr.idx;
427 s.line.update(sr.entry, sr.pos);
428 true
429 }
430 _ => false,
431 };
432 }
433 s.changes.borrow_mut().end();
434 Ok(Some(cmd))
435}
436
437struct Guard<'m>(&'m tty::Mode);
438
439#[allow(unused_must_use)]
440impl Drop for Guard<'_> {
441 fn drop(&mut self) {
442 let Guard(mode) = *self;
443 mode.disable_raw_mode();
444 }
445}
446
447fn apply_backspace_direct(input: &str) -> String {
449 let mut out = String::with_capacity(input.len());
453
454 let mut grapheme_sizes: Vec<u8> = Vec::with_capacity(input.len());
457
458 for g in unicode_segmentation::UnicodeSegmentation::graphemes(input, true) {
459 if g == "\u{0008}" {
460 if let Some(n) = grapheme_sizes.pop() {
462 out.truncate(out.len() - n as usize);
464 }
465 } else {
466 out.push_str(g);
467 grapheme_sizes.push(g.len() as u8);
468 }
469 }
470
471 out
472}
473
474fn readline_direct(
475 mut reader: impl BufRead,
476 mut writer: impl Write,
477 validator: &Option<impl Validator>,
478) -> Result<String> {
479 let mut input = String::new();
480
481 loop {
482 if reader.read_line(&mut input)? == 0 {
483 return Err(error::ReadlineError::Eof);
484 }
485 let trailing_n = input.ends_with('\n');
487 let trailing_r;
488
489 if trailing_n {
490 input.pop();
491 trailing_r = input.ends_with('\r');
492 if trailing_r {
493 input.pop();
494 }
495 } else {
496 trailing_r = false;
497 }
498
499 input = apply_backspace_direct(&input);
500
501 match validator.as_ref() {
502 None => return Ok(input),
503 Some(v) => {
504 let mut ctx = input.as_str();
505 let mut ctx = validate::ValidationContext::new(&mut ctx);
506
507 match v.validate(&mut ctx)? {
508 validate::ValidationResult::Valid(msg) => {
509 if let Some(msg) = msg {
510 writer.write_all(msg.as_bytes())?;
511 }
512 return Ok(input);
513 }
514 validate::ValidationResult::Invalid(Some(msg)) => {
515 writer.write_all(msg.as_bytes())?;
516 }
517 validate::ValidationResult::Incomplete => {
518 if trailing_r {
520 input.push('\r');
521 }
522 if trailing_n {
523 input.push('\n');
524 }
525 }
526 _ => {}
527 }
528 }
529 }
530 }
531}
532
533pub trait Helper
538where
539 Self: Completer + Hinter + Highlighter + Validator,
540{
541}
542
543impl Helper for () {}
544
545impl<'h, H: ?Sized + Helper> Helper for &'h H {}
546
547pub struct Context<'h> {
549 history: &'h History,
550 history_index: usize,
551}
552
553impl<'h> Context<'h> {
554 #[must_use]
556 pub fn new(history: &'h History) -> Self {
557 Context {
558 history,
559 history_index: history.len(),
560 }
561 }
562
563 #[must_use]
565 pub fn history(&self) -> &History {
566 self.history
567 }
568
569 #[must_use]
571 pub fn history_index(&self) -> usize {
572 self.history_index
573 }
574}
575
576#[must_use]
578pub struct Editor<H: Helper> {
579 term: Terminal,
580 history: History,
581 helper: Option<H>,
582 kill_ring: Arc<Mutex<KillRing>>,
583 config: Config,
584 custom_bindings: Bindings,
585}
586
587#[allow(clippy::new_without_default)]
588impl<H: Helper> Editor<H> {
589 pub fn new() -> Result<Self> {
591 Self::with_config(Config::default())
592 }
593
594 pub fn with_config(config: Config) -> Result<Self> {
596 let term = Terminal::new(
597 config.color_mode(),
598 config.behavior(),
599 config.tab_stop(),
600 config.bell_style(),
601 config.enable_bracketed_paste(),
602 )?;
603 Ok(Self {
604 term,
605 history: History::with_config(config),
606 helper: None,
607 kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
608 config,
609 custom_bindings: Bindings::new(),
610 })
611 }
612
613 pub fn readline(&mut self, prompt: &str) -> Result<String> {
620 self.readline_with(prompt, None)
621 }
622
623 pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
631 self.readline_with(prompt, Some(initial))
632 }
633
634 fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
635 if self.term.is_unsupported() {
636 debug!(target: "rustyline", "unsupported terminal");
637 let mut stdout = io::stdout();
639 stdout.write_all(prompt.as_bytes())?;
640 stdout.flush()?;
641
642 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
643 } else if self.term.is_input_tty() {
644 let (original_mode, term_key_map) = self.term.enable_raw_mode()?;
645 let guard = Guard(&original_mode);
646 let user_input = self.readline_edit(prompt, initial, &original_mode, term_key_map);
647 if self.config.auto_add_history() {
648 if let Ok(ref line) = user_input {
649 self.add_history_entry(line.as_str());
650 }
651 }
652 drop(guard); self.term.writeln()?;
654 user_input
655 } else {
656 debug!(target: "rustyline", "stdin is not a tty");
657 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
659 }
660 }
661
662 fn readline_edit(
666 &mut self,
667 prompt: &str,
668 initial: Option<(&str, &str)>,
669 original_mode: &tty::Mode,
670 term_key_map: tty::KeyMap,
671 ) -> Result<String> {
672 let mut stdout = self.term.create_writer();
673
674 self.reset_kill_ring(); let ctx = Context::new(&self.history);
676 let mut s = State::new(&mut stdout, prompt, self.helper.as_ref(), ctx);
677
678 let mut input_state = InputState::new(&self.config, &self.custom_bindings);
679
680 s.line.set_delete_listener(self.kill_ring.clone());
681 s.line.set_change_listener(s.changes.clone());
682
683 if let Some((left, right)) = initial {
684 s.line
685 .update((left.to_owned() + right).as_ref(), left.len());
686 }
687
688 let mut rdr = self.term.create_reader(&self.config, term_key_map);
689 if self.term.is_output_tty() && self.config.check_cursor_position() {
690 if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
691 if let ReadlineError::WindowResized = e {
692 s.out.update_size();
693 } else {
694 return Err(e);
695 }
696 }
697 }
698 s.refresh_line()?;
699
700 loop {
701 let mut cmd = s.next_cmd(&mut input_state, &mut rdr, false, false)?;
702
703 if cmd.should_reset_kill_ring() {
704 self.reset_kill_ring();
705 }
706
707 if cmd == Cmd::Complete && s.helper.is_some() {
710 let next = complete_line(&mut rdr, &mut s, &mut input_state, &self.config)?;
711 if let Some(next) = next {
712 cmd = next;
713 } else {
714 continue;
715 }
716 }
717
718 if cmd == Cmd::ReverseSearchHistory {
719 let next =
721 reverse_incremental_search(&mut rdr, &mut s, &mut input_state, &self.history)?;
722 if let Some(next) = next {
723 cmd = next;
724 } else {
725 continue;
726 }
727 }
728
729 #[cfg(unix)]
730 if cmd == Cmd::Suspend {
731 original_mode.disable_raw_mode()?;
732 tty::suspend()?;
733 let _ = self.term.enable_raw_mode()?; s.out.update_size(); s.refresh_line()?;
736 continue;
737 }
738
739 #[cfg(unix)]
740 if cmd == Cmd::QuotedInsert {
741 let c = rdr.next_char()?;
743 s.edit_insert(c, 1)?;
744 continue;
745 }
746
747 #[cfg(windows)]
748 if cmd == Cmd::PasteFromClipboard {
749 let clipboard = rdr.read_pasted_text()?;
750 s.edit_yank(&input_state, &clipboard[..], Anchor::Before, 1)?;
751 }
752
753 #[cfg(test)]
755 if matches!(
756 cmd,
757 Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. }
758 ) {
759 self.term.cursor = s.layout.cursor.col;
760 }
761
762 match command::execute(cmd, &mut s, &input_state, &self.kill_ring, &self.config)? {
764 command::Status::Proceed => continue,
765 command::Status::Submit => break,
766 }
767 }
768
769 s.edit_move_buffer_end()?;
772
773 if cfg!(windows) {
774 let _ = original_mode; }
776 Ok(s.line.into_string())
777 }
778
779 pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
781 self.history.load(path)
782 }
783
784 pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
786 self.history.save(path)
787 }
788
789 pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
791 self.history.append(path)
792 }
793
794 pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
796 self.history.add(line)
797 }
798
799 pub fn clear_history(&mut self) {
801 self.history.clear();
802 }
803
804 pub fn history_mut(&mut self) -> &mut History {
806 &mut self.history
807 }
808
809 pub fn history(&self) -> &History {
811 &self.history
812 }
813
814 pub fn set_helper(&mut self, helper: Option<H>) {
817 self.helper = helper;
818 }
819
820 pub fn helper_mut(&mut self) -> Option<&mut H> {
822 self.helper.as_mut()
823 }
824
825 pub fn helper(&self) -> Option<&H> {
827 self.helper.as_ref()
828 }
829
830 #[cfg(feature = "custom-bindings")]
832 #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
833 pub fn bind_sequence<E: Into<Event>, R: Into<EventHandler>>(
834 &mut self,
835 key_seq: E,
836 handler: R,
837 ) -> Option<EventHandler> {
838 self.custom_bindings
839 .insert(Event::normalize(key_seq.into()), handler.into())
840 }
841
842 #[cfg(feature = "custom-bindings")]
844 #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
845 pub fn unbind_sequence<E: Into<Event>>(&mut self, key_seq: E) -> Option<EventHandler> {
846 self.custom_bindings
847 .remove(&Event::normalize(key_seq.into()))
848 }
849
850 pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
868 Iter {
869 editor: self,
870 prompt,
871 }
872 }
873
874 fn reset_kill_ring(&self) {
875 let mut kill_ring = self.kill_ring.lock().unwrap();
876 kill_ring.reset();
877 }
878
879 pub fn dimensions(&mut self) -> Option<(usize, usize)> {
882 if self.term.is_output_tty() {
883 let out = self.term.create_writer();
884 Some((out.get_columns(), out.get_rows()))
885 } else {
886 None
887 }
888 }
889
890 pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
892 self.term.create_external_printer()
893 }
894}
895
896impl<H: Helper> config::Configurer for Editor<H> {
897 fn config_mut(&mut self) -> &mut Config {
898 &mut self.config
899 }
900
901 fn set_max_history_size(&mut self, max_size: usize) {
902 self.config_mut().set_max_history_size(max_size);
903 self.history.set_max_len(max_size);
904 }
905
906 fn set_history_ignore_dups(&mut self, yes: bool) {
907 self.config_mut().set_history_ignore_dups(yes);
908 self.history.ignore_dups = yes;
909 }
910
911 fn set_history_ignore_space(&mut self, yes: bool) {
912 self.config_mut().set_history_ignore_space(yes);
913 self.history.ignore_space = yes;
914 }
915
916 fn set_color_mode(&mut self, color_mode: ColorMode) {
917 self.config_mut().set_color_mode(color_mode);
918 self.term.color_mode = color_mode;
919 }
920}
921
922impl<H: Helper> fmt::Debug for Editor<H> {
923 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924 f.debug_struct("Editor")
925 .field("term", &self.term)
926 .field("config", &self.config)
927 .finish()
928 }
929}
930
931struct Iter<'a, H: Helper> {
932 editor: &'a mut Editor<H>,
933 prompt: &'a str,
934}
935
936impl<'a, H: Helper> Iterator for Iter<'a, H> {
937 type Item = Result<String>;
938
939 fn next(&mut self) -> Option<Result<String>> {
940 let readline = self.editor.readline(self.prompt);
941 match readline {
942 Ok(l) => Some(Ok(l)),
943 Err(error::ReadlineError::Eof) => None,
944 e @ Err(_) => Some(e),
945 }
946 }
947}
948
949#[cfg(test)]
950#[macro_use]
951extern crate assert_matches;
952#[cfg(test)]
953mod test;
954
955#[cfg(doctest)]
956doc_comment::doctest!("../README.md");