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 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 = s.next_cmd(input_state, rdr, true, true)?;
167 if cmd != Cmd::Complete {
169 return Ok(Some(cmd));
170 }
171 let save_pos = s.line.pos();
173 s.edit_move_end()?;
174 s.line.set_pos(save_pos);
175 let show_completions = if candidates.len() > config.completion_prompt_limit() {
177 let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
178 s.out.write_and_flush(msg.as_str())?;
179 s.layout.end.row += 1;
180 while cmd != Cmd::SelfInsert(1, 'y')
181 && cmd != Cmd::SelfInsert(1, 'Y')
182 && cmd != Cmd::SelfInsert(1, 'n')
183 && cmd != Cmd::SelfInsert(1, 'N')
184 && cmd != Cmd::Kill(Movement::BackwardChar(1))
185 {
186 cmd = s.next_cmd(input_state, rdr, false, true)?;
187 }
188 matches!(cmd, Cmd::SelfInsert(1, 'y' | 'Y'))
189 } else {
190 true
191 };
192 if show_completions {
193 page_completions(rdr, s, input_state, &candidates)
194 } else {
195 s.refresh_line()?;
196 Ok(None)
197 }
198 } else {
199 #[cfg(all(unix, feature = "with-fuzzy"))]
202 {
203 use std::borrow::Cow;
204 if CompletionType::Fuzzy == config.completion_type() {
205 struct Candidate {
206 index: usize,
207 text: String,
208 }
209 impl SkimItem for Candidate {
210 fn text(&self) -> Cow<str> {
211 Cow::Borrowed(&self.text)
212 }
213 }
214
215 let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded();
216
217 candidates
218 .iter()
219 .enumerate()
220 .map(|(i, c)| Candidate {
221 index: i,
222 text: c.display().to_owned(),
223 })
224 .for_each(|c| {
225 let _ = tx_item.send(std::sync::Arc::new(c));
226 });
227 drop(tx_item); let options = SkimOptionsBuilder::default()
234 .prompt(Some("? "))
235 .reverse(true)
236 .build()
237 .unwrap();
238
239 let selected_items = Skim::run_with(&options, Some(rx_item))
240 .map(|out| out.selected_items)
241 .unwrap_or_default();
242
243 if let Some(item) = selected_items.first() {
246 let item: &Candidate = (*item).as_any() .downcast_ref::<Candidate>() .expect("something wrong with downcast");
249 if let Some(candidate) = candidates.get(item.index) {
250 completer.update(
251 &mut s.line,
252 start,
253 candidate.replacement(),
254 &mut s.changes,
255 );
256 }
257 }
258 s.refresh_line()?;
259 }
260 };
261 Ok(None)
262 }
263}
264
265fn complete_hint_line<H: Helper>(s: &mut State<'_, '_, H>) -> Result<()> {
267 let hint = match s.hint.as_ref() {
268 Some(hint) => hint,
269 None => return Ok(()),
270 };
271 s.line.move_end();
272 if let Some(text) = hint.completion() {
273 if s.line.yank(text, 1, &mut s.changes).is_none() {
274 s.out.beep()?;
275 }
276 } else {
277 s.out.beep()?;
278 }
279 s.refresh_line()
280}
281
282fn page_completions<C: Candidate, H: Helper>(
283 rdr: &mut <Terminal as Term>::Reader,
284 s: &mut State<'_, '_, H>,
285 input_state: &mut InputState,
286 candidates: &[C],
287) -> Result<Option<Cmd>> {
288 use std::cmp;
289
290 let min_col_pad = 2;
291 let cols = s.out.get_columns();
292 let max_width = cmp::min(
293 cols,
294 candidates
295 .iter()
296 .map(|s| s.display().width())
297 .max()
298 .unwrap()
299 + min_col_pad,
300 );
301 let num_cols = cols / max_width;
302
303 let mut pause_row = s.out.get_rows() - 1;
304 let num_rows = (candidates.len() + num_cols - 1) / num_cols;
305 let mut ab = String::new();
306 for row in 0..num_rows {
307 if row == pause_row {
308 s.out.write_and_flush("\n--More--")?;
309 let mut cmd = Cmd::Noop;
310 while cmd != Cmd::SelfInsert(1, 'y')
311 && cmd != Cmd::SelfInsert(1, 'Y')
312 && cmd != Cmd::SelfInsert(1, 'n')
313 && cmd != Cmd::SelfInsert(1, 'N')
314 && cmd != Cmd::SelfInsert(1, 'q')
315 && cmd != Cmd::SelfInsert(1, 'Q')
316 && cmd != Cmd::SelfInsert(1, ' ')
317 && cmd != Cmd::Kill(Movement::BackwardChar(1))
318 && cmd != Cmd::AcceptLine
319 && cmd != Cmd::Newline
320 && !matches!(cmd, Cmd::AcceptOrInsertLine { .. })
321 {
322 cmd = s.next_cmd(input_state, rdr, false, true)?;
323 }
324 match cmd {
325 Cmd::SelfInsert(1, 'y' | 'Y' | ' ') => {
326 pause_row += s.out.get_rows() - 1;
327 }
328 Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. } => {
329 pause_row += 1;
330 }
331 _ => break,
332 }
333 }
334 s.out.write_and_flush("\n")?;
335 ab.clear();
336 for col in 0..num_cols {
337 let i = (col * num_rows) + row;
338 if i < candidates.len() {
339 let candidate = &candidates[i].display();
340 let width = candidate.width();
341 if let Some(highlighter) = s.highlighter() {
342 ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List));
343 } else {
344 ab.push_str(candidate);
345 }
346 if ((col + 1) * num_rows) + row < candidates.len() {
347 for _ in width..max_width {
348 ab.push(' ');
349 }
350 }
351 }
352 }
353 s.out.write_and_flush(ab.as_str())?;
354 }
355 s.out.write_and_flush("\n")?;
356 s.layout.end.row = 0; s.layout.cursor.row = 0;
358 s.refresh_line()?;
359 Ok(None)
360}
361
362fn reverse_incremental_search<H: Helper, I: History>(
364 rdr: &mut <Terminal as Term>::Reader,
365 s: &mut State<'_, '_, H>,
366 input_state: &mut InputState,
367 history: &I,
368) -> Result<Option<Cmd>> {
369 if history.is_empty() {
370 return Ok(None);
371 }
372 let mark = s.changes.begin();
373 let backup = s.line.as_str().to_owned();
375 let backup_pos = s.line.pos();
376
377 let mut search_buf = String::new();
378 let mut history_idx = history.len() - 1;
379 let mut direction = SearchDirection::Reverse;
380 let mut success = true;
381
382 let mut cmd;
383 loop {
385 let prompt = if success {
386 format!("(reverse-i-search)`{search_buf}': ")
387 } else {
388 format!("(failed reverse-i-search)`{search_buf}': ")
389 };
390 s.refresh_prompt_and_line(&prompt)?;
391
392 cmd = s.next_cmd(input_state, rdr, true, true)?;
393 if let Cmd::SelfInsert(_, c) = cmd {
394 search_buf.push(c);
395 } else {
396 match cmd {
397 Cmd::Kill(Movement::BackwardChar(_)) => {
398 search_buf.pop();
399 continue;
400 }
401 Cmd::ReverseSearchHistory => {
402 direction = SearchDirection::Reverse;
403 if history_idx > 0 {
404 history_idx -= 1;
405 } else {
406 success = false;
407 continue;
408 }
409 }
410 Cmd::ForwardSearchHistory => {
411 direction = SearchDirection::Forward;
412 if history_idx < history.len() - 1 {
413 history_idx += 1;
414 } else {
415 success = false;
416 continue;
417 }
418 }
419 Cmd::Abort => {
420 s.line.update(&backup, backup_pos, &mut s.changes);
422 s.refresh_line()?;
423 s.changes.truncate(mark);
424 return Ok(None);
425 }
426 Cmd::Move(_) => {
427 s.refresh_line()?; break;
429 }
430 _ => break,
431 }
432 }
433 success = match history.search(&search_buf, history_idx, direction)? {
434 Some(sr) => {
435 history_idx = sr.idx;
436 s.line.update(&sr.entry, sr.pos, &mut s.changes);
437 true
438 }
439 _ => false,
440 };
441 }
442 s.changes.end();
443 Ok(Some(cmd))
444}
445
446struct Guard<'m>(&'m tty::Mode);
447
448#[allow(unused_must_use)]
449impl Drop for Guard<'_> {
450 fn drop(&mut self) {
451 let Guard(mode) = *self;
452 mode.disable_raw_mode();
453 }
454}
455
456fn apply_backspace_direct(input: &str) -> String {
458 let mut out = String::with_capacity(input.len());
462
463 let mut grapheme_sizes: Vec<u8> = Vec::with_capacity(input.len());
466
467 for g in unicode_segmentation::UnicodeSegmentation::graphemes(input, true) {
468 if g == "\u{0008}" {
469 if let Some(n) = grapheme_sizes.pop() {
471 out.truncate(out.len() - n as usize);
473 }
474 } else {
475 out.push_str(g);
476 grapheme_sizes.push(g.len() as u8);
477 }
478 }
479
480 out
481}
482
483fn readline_direct(
484 mut reader: impl BufRead,
485 mut writer: impl Write,
486 validator: &Option<impl Validator>,
487) -> Result<String> {
488 let mut input = String::new();
489
490 loop {
491 if reader.read_line(&mut input)? == 0 {
492 return Err(ReadlineError::Eof);
493 }
494 let trailing_n = input.ends_with('\n');
496 let trailing_r;
497
498 if trailing_n {
499 input.pop();
500 trailing_r = input.ends_with('\r');
501 if trailing_r {
502 input.pop();
503 }
504 } else {
505 trailing_r = false;
506 }
507
508 input = apply_backspace_direct(&input);
509
510 match validator.as_ref() {
511 None => return Ok(input),
512 Some(v) => {
513 let mut ctx = input.as_str();
514 let mut ctx = validate::ValidationContext::new(&mut ctx);
515
516 match v.validate(&mut ctx)? {
517 validate::ValidationResult::Valid(msg) => {
518 if let Some(msg) = msg {
519 writer.write_all(msg.as_bytes())?;
520 }
521 return Ok(input);
522 }
523 validate::ValidationResult::Invalid(Some(msg)) => {
524 writer.write_all(msg.as_bytes())?;
525 }
526 validate::ValidationResult::Incomplete => {
527 if trailing_r {
529 input.push('\r');
530 }
531 if trailing_n {
532 input.push('\n');
533 }
534 }
535 _ => {}
536 }
537 }
538 }
539 }
540}
541
542pub trait Helper
547where
548 Self: Completer + Hinter + Highlighter + Validator,
549{
550}
551
552impl Helper for () {}
553
554impl<'h, H: ?Sized + Helper> Helper for &'h H {}
555
556pub struct Context<'h> {
558 history: &'h dyn History,
559 history_index: usize,
560}
561
562impl<'h> Context<'h> {
563 #[must_use]
565 pub fn new(history: &'h dyn History) -> Self {
566 Context {
567 history,
568 history_index: history.len(),
569 }
570 }
571
572 #[must_use]
574 pub fn history(&self) -> &dyn History {
575 self.history
576 }
577
578 #[must_use]
580 pub fn history_index(&self) -> usize {
581 self.history_index
582 }
583}
584
585#[must_use]
587pub struct Editor<H: Helper, I: History> {
588 term: Terminal,
589 buffer: Option<Buffer>,
590 history: I,
591 helper: Option<H>,
592 kill_ring: KillRing,
593 config: Config,
594 custom_bindings: Bindings,
595}
596
597pub type DefaultEditor = Editor<(), DefaultHistory>;
599
600#[allow(clippy::new_without_default)]
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(
617 config.color_mode(),
618 config.behavior(),
619 config.tab_stop(),
620 config.bell_style(),
621 config.enable_bracketed_paste(),
622 config.enable_signals(),
623 )?;
624 Ok(Self {
625 term,
626 buffer: None,
627 history,
628 helper: None,
629 kill_ring: KillRing::new(60),
630 config,
631 custom_bindings: Bindings::new(),
632 })
633 }
634
635 pub fn readline(&mut self, prompt: &str) -> Result<String> {
642 self.readline_with(prompt, None)
643 }
644
645 pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
653 self.readline_with(prompt, Some(initial))
654 }
655
656 fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
657 if self.term.is_unsupported() {
658 debug!(target: "rustyline", "unsupported terminal");
659 let mut stdout = io::stdout();
661 stdout.write_all(prompt.as_bytes())?;
662 stdout.flush()?;
663
664 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
665 } else if self.term.is_input_tty() {
666 let (original_mode, term_key_map) = self.term.enable_raw_mode()?;
667 let guard = Guard(&original_mode);
668 let user_input = self.readline_edit(prompt, initial, &original_mode, term_key_map);
669 if self.config.auto_add_history() {
670 if let Ok(ref line) = user_input {
671 self.add_history_entry(line.as_str())?;
672 }
673 }
674 drop(guard); self.term.writeln()?;
676 user_input
677 } else {
678 debug!(target: "rustyline", "stdin is not a tty");
679 readline_direct(io::stdin().lock(), io::stderr(), &self.helper)
681 }
682 }
683
684 fn readline_edit(
688 &mut self,
689 prompt: &str,
690 initial: Option<(&str, &str)>,
691 original_mode: &tty::Mode,
692 term_key_map: tty::KeyMap,
693 ) -> Result<String> {
694 let mut stdout = self.term.create_writer();
695
696 self.kill_ring.reset(); let ctx = Context::new(&self.history);
698 let mut s = State::new(&mut stdout, prompt, self.helper.as_ref(), ctx);
699
700 let mut input_state = InputState::new(&self.config, &self.custom_bindings);
701
702 if let Some((left, right)) = initial {
703 s.line.update(
704 (left.to_owned() + right).as_ref(),
705 left.len(),
706 &mut s.changes,
707 );
708 }
709
710 let mut rdr = self
711 .term
712 .create_reader(self.buffer.take(), &self.config, term_key_map);
713 if self.term.is_output_tty() && self.config.check_cursor_position() {
714 if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
715 if let ReadlineError::WindowResized = e {
716 s.out.update_size();
717 } else {
718 return Err(e);
719 }
720 }
721 }
722 s.refresh_line()?;
723
724 loop {
725 let mut cmd = s.next_cmd(&mut input_state, &mut rdr, false, false)?;
726
727 if cmd.should_reset_kill_ring() {
728 self.kill_ring.reset();
729 }
730
731 if cmd == Cmd::Complete && s.helper.is_some() {
734 let next = complete_line(&mut rdr, &mut s, &mut input_state, &self.config)?;
735 if let Some(next) = next {
736 cmd = next;
737 } else {
738 continue;
739 }
740 }
741
742 if cmd == Cmd::ReverseSearchHistory {
743 let next =
745 reverse_incremental_search(&mut rdr, &mut s, &mut input_state, &self.history)?;
746 if let Some(next) = next {
747 cmd = next;
748 } else {
749 continue;
750 }
751 }
752
753 #[cfg(unix)]
754 if cmd == Cmd::Suspend {
755 original_mode.disable_raw_mode()?;
756 tty::suspend()?;
757 let _ = self.term.enable_raw_mode()?; s.out.update_size(); s.refresh_line()?;
760 continue;
761 }
762
763 #[cfg(unix)]
764 if cmd == Cmd::QuotedInsert {
765 let c = rdr.next_char()?;
767 s.edit_insert(c, 1)?;
768 continue;
769 }
770
771 #[cfg(windows)]
772 if cmd == Cmd::PasteFromClipboard {
773 let clipboard = rdr.read_pasted_text()?;
774 s.edit_yank(&input_state, &clipboard[..], Anchor::Before, 1)?;
775 }
776
777 #[cfg(test)]
779 if matches!(
780 cmd,
781 Cmd::AcceptLine | Cmd::Newline | Cmd::AcceptOrInsertLine { .. }
782 ) {
783 self.term.cursor = s.layout.cursor.col;
784 }
785
786 match command::execute(cmd, &mut s, &input_state, &mut self.kill_ring, &self.config)? {
788 command::Status::Proceed => continue,
789 command::Status::Submit => break,
790 }
791 }
792
793 s.forced_refresh = true;
796 s.edit_move_buffer_end()?;
797 s.forced_refresh = false;
798
799 if cfg!(windows) {
800 let _ = original_mode; }
802 self.buffer = rdr.unbuffer();
803 Ok(s.line.into_string())
804 }
805
806 pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
808 self.history.load(path.as_ref())
809 }
810
811 pub fn save_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
813 self.history.save(path.as_ref())
814 }
815
816 pub fn append_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
818 self.history.append(path.as_ref())
819 }
820
821 pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> Result<bool> {
823 self.history.add(line.as_ref())
824 }
825
826 pub fn clear_history(&mut self) -> Result<()> {
828 self.history.clear()
829 }
830
831 pub fn history_mut(&mut self) -> &mut I {
833 &mut self.history
834 }
835
836 pub fn history(&self) -> &I {
838 &self.history
839 }
840
841 pub fn set_helper(&mut self, helper: Option<H>) {
844 self.helper = helper;
845 }
846
847 pub fn helper_mut(&mut self) -> Option<&mut H> {
849 self.helper.as_mut()
850 }
851
852 pub fn helper(&self) -> Option<&H> {
854 self.helper.as_ref()
855 }
856
857 #[cfg(feature = "custom-bindings")]
859 #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
860 pub fn bind_sequence<E: Into<Event>, R: Into<EventHandler>>(
861 &mut self,
862 key_seq: E,
863 handler: R,
864 ) -> Option<EventHandler> {
865 self.custom_bindings
866 .insert(Event::normalize(key_seq.into()), handler.into())
867 }
868
869 #[cfg(feature = "custom-bindings")]
871 #[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
872 pub fn unbind_sequence<E: Into<Event>>(&mut self, key_seq: E) -> Option<EventHandler> {
873 self.custom_bindings
874 .remove(&Event::normalize(key_seq.into()))
875 }
876
877 pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
895 Iter {
896 editor: self,
897 prompt,
898 }
899 }
900
901 pub fn dimensions(&mut self) -> Option<(usize, usize)> {
904 if self.term.is_output_tty() {
905 let out = self.term.create_writer();
906 Some((out.get_columns(), out.get_rows()))
907 } else {
908 None
909 }
910 }
911
912 pub fn clear_screen(&mut self) -> Result<()> {
914 if self.term.is_output_tty() {
915 let mut out = self.term.create_writer();
916 out.clear_screen()
917 } else {
918 Ok(())
919 }
920 }
921
922 pub fn create_external_printer(&mut self) -> Result<<Terminal as Term>::ExternalPrinter> {
924 self.term.create_external_printer()
925 }
926
927 pub fn set_cursor_visibility(
929 &mut self,
930 visible: bool,
931 ) -> Result<Option<<Terminal as Term>::CursorGuard>> {
932 self.term.set_cursor_visibility(visible)
933 }
934}
935
936impl<H: Helper, I: History> config::Configurer for Editor<H, I> {
937 fn config_mut(&mut self) -> &mut Config {
938 &mut self.config
939 }
940
941 fn set_max_history_size(&mut self, max_size: usize) -> Result<()> {
942 self.config_mut().set_max_history_size(max_size);
943 self.history.set_max_len(max_size)
944 }
945
946 fn set_history_ignore_dups(&mut self, yes: bool) -> Result<()> {
947 self.config_mut().set_history_ignore_dups(yes);
948 self.history.ignore_dups(yes)
949 }
950
951 fn set_history_ignore_space(&mut self, yes: bool) {
952 self.config_mut().set_history_ignore_space(yes);
953 self.history.ignore_space(yes);
954 }
955
956 fn set_color_mode(&mut self, color_mode: ColorMode) {
957 self.config_mut().set_color_mode(color_mode);
958 self.term.color_mode = color_mode;
959 }
960}
961
962impl<H: Helper, I: History> fmt::Debug for Editor<H, I> {
963 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
964 f.debug_struct("Editor")
965 .field("term", &self.term)
966 .field("config", &self.config)
967 .finish()
968 }
969}
970
971struct Iter<'a, H: Helper, I: History> {
972 editor: &'a mut Editor<H, I>,
973 prompt: &'a str,
974}
975
976impl<'a, H: Helper, I: History> Iterator for Iter<'a, H, I> {
977 type Item = Result<String>;
978
979 fn next(&mut self) -> Option<Result<String>> {
980 let readline = self.editor.readline(self.prompt);
981 match readline {
982 Ok(l) => Some(Ok(l)),
983 Err(ReadlineError::Eof) => None,
984 e @ Err(_) => Some(e),
985 }
986 }
987}
988
989#[cfg(test)]
990#[macro_use]
991extern crate assert_matches;
992#[cfg(test)]
993mod test;
994
995#[cfg(doctest)]
996doc_comment::doctest!("../README.md");