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;
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
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 (start, candidates) = s.helper.as_deref_mut().unwrap().complete(&s.line, s.line.pos(), &s.ctx)?;
90
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 let completer = s.helper.as_deref().unwrap();
113 completer.update(&mut s.line, start, candidate, &mut s.changes);
114 } else {
115 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); if i == candidates.len() {
125 s.out.beep()?;
126 }
127 }
128 Cmd::CompleteBackward => {
129 if i == 0 {
130 i = candidates.len(); s.out.beep()?;
132 } else {
133 i = (i - 1) % (candidates.len() + 1); }
135 }
136 Cmd::Abort => {
137 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 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 if candidates.len() > 1 {
163 s.out.beep()?;
164 } else {
165 return Ok(None);
166 }
167 let mut cmd = s.next_cmd(input_state, rdr, true, true)?;
169 if cmd != Cmd::Complete {
171 return Ok(Some(cmd));
172 }
173 let save_pos = s.line.pos();
175 s.edit_move_end()?;
176 s.line.set_pos(save_pos);
177 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 #[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); 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 if let Some(item) = selected_items.first() {
248 let item: &Candidate = (*item).as_any() .downcast_ref::<Candidate>() .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
267fn 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; s.layout.cursor.row = 0;
360 s.refresh_line()?;
361 Ok(None)
362}
363
364fn 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 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 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 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()?; 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
458fn apply_backspace_direct(input: &str) -> String {
460 let mut out = String::with_capacity(input.len());
464
465 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 if let Some(n) = grapheme_sizes.pop() {
473 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 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 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
544pub 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
558pub struct Context<'h> {
560 history: &'h dyn History,
561 history_index: usize,
562}
563
564impl<'h> Context<'h> {
565 #[must_use]
567 pub fn new(history: &'h dyn History) -> Self {
568 Context {
569 history,
570 history_index: history.len(),
571 }
572 }
573
574 #[must_use]
576 pub fn history(&self) -> &dyn History {
577 self.history
578 }
579
580 #[must_use]
582 pub fn history_index(&self) -> usize {
583 self.history_index
584 }
585}
586
587#[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
599pub type DefaultEditor = Editor<(), DefaultHistory>;
601
602#[allow(clippy::new_without_default)]
603impl<H: Helper> Editor<H, DefaultHistory> {
604 pub fn new() -> Result<Self> {
606 Self::with_config(Config::default())
607 }
608
609 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 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 pub fn readline(&mut self, prompt: &str) -> Result<String> {
644 self.readline_with(prompt, None)
645 }
646
647 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 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); self.term.writeln()?;
678 user_input
679 } else {
680 debug!(target: "rustyline", "stdin is not a tty");
681 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
683 }
684 }
685
686 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(); 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 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 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()?; s.out.update_size(); s.refresh_line()?;
762 continue;
763 }
764
765 #[cfg(unix)]
766 if cmd == Cmd::QuotedInsert {
767 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 #[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 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 s.forced_refresh = true;
798 s.edit_move_buffer_end()?;
799 s.forced_refresh = false;
800
801 if cfg!(windows) {
802 let _ = original_mode; }
804 self.buffer = rdr.unbuffer();
805 Ok(s.line.into_string())
806 }
807
808 pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
810 self.history.load(path.as_ref())
811 }
812
813 pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
815 self.history.save(path.as_ref())
816 }
817
818 pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
820 self.history.append(path.as_ref())
821 }
822
823 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 pub fn clear_history(&mut self) -> Result<()> {
830 self.history.clear()
831 }
832
833 pub fn history_mut(&mut self) -> &mut I {
835 &mut self.history
836 }
837
838 pub fn history(&self) -> &I {
840 &self.history
841 }
842
843 pub fn set_helper(&mut self, helper: Option<H>) {
846 self.helper = helper;
847 }
848
849 pub fn helper_mut(&mut self) -> Option<&mut H> {
851 self.helper.as_mut()
852 }
853
854 pub fn helper(&self) -> Option<&H> {
856 self.helper.as_ref()
857 }
858
859 #[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 #[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 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 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 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 pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
926 self.term.create_external_printer()
927 }
928
929 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");