1use crate::console::{Console, Key, LineBuffer};
20use std::borrow::Cow;
21use std::io;
22
23const SECURE_CHAR: &str = "*";
25
26enum DispatchResult {
28 Continue,
29 Submit,
30}
31
32struct Readline<'a, 'h> {
34 console: &'a mut dyn Console,
35 history: Option<&'h mut Vec<String>>,
36 echo: bool,
37 input_width: usize,
38 line: LineBuffer,
39 pos: usize,
40 view_start: usize,
41 rendered: String,
42 rendered_len: usize,
43 history_pos: usize,
44}
45
46impl<'a, 'h> Readline<'a, 'h> {
47 fn adjust_view(&self, pos: usize, view_start: usize) -> usize {
49 if self.input_width == 0 {
50 0
51 } else if pos < view_start {
52 pos
53 } else if pos > view_start + self.input_width {
54 pos - self.input_width
55 } else {
56 view_start
57 }
58 }
59
60 fn current_pos(&self) -> usize {
62 self.pos - self.view_start
63 }
64
65 fn render_line(&self, view_start: usize) -> String {
67 debug_assert!(view_start <= self.line.len());
68
69 if !self.echo {
70 SECURE_CHAR.repeat((self.line.len() - view_start).min(self.input_width))
71 } else {
72 self.line.range(view_start, view_start + self.input_width)
73 }
74 }
75
76 fn sync_rendered(&mut self) {
78 self.rendered = self.render_line(self.view_start);
79 self.rendered_len = self.rendered.chars().count();
80 }
81
82 fn reset_view_to_end(&mut self) {
84 self.view_start =
85 if self.input_width == 0 { 0 } else { self.pos.saturating_sub(self.input_width) };
86 self.sync_rendered();
87 }
88
89 fn redraw_line(&mut self, old_pos: usize, clear_len: usize, new_pos: usize) -> io::Result<()> {
91 self.console.hide_cursor()?;
92 if old_pos > 0 {
93 self.console.move_within_line(-(old_pos as i16))?;
94 }
95 if !self.rendered.is_empty() {
96 self.console.write(&self.rendered)?;
97 }
98 let written_len = self.rendered_len.max(clear_len);
99 if self.rendered_len < clear_len {
100 let diff = clear_len - self.rendered_len;
101 self.console.write(&" ".repeat(diff))?;
102 }
103 debug_assert!(new_pos <= written_len);
104 let reset = written_len - new_pos;
105 if reset > 0 {
106 self.console.move_within_line(-(reset as i16))?;
107 }
108 self.console.show_cursor()
109 }
110
111 fn erase_at(&mut self, cursor_pos: usize, remove_pos: usize) -> io::Result<()> {
113 debug_assert!(remove_pos < self.line.len());
114 debug_assert!(remove_pos <= cursor_pos);
115
116 self.console.hide_cursor()?;
117 let delta = cursor_pos - remove_pos;
118 if delta > 0 {
119 self.console.move_within_line(-(delta as i16))?;
120 }
121 let tail_len = self.line.len() - remove_pos - 1;
122 if self.echo {
123 self.console.write(&self.line.end(remove_pos + 1))?;
124 } else {
125 self.console.write(&SECURE_CHAR.repeat(tail_len))?;
126 }
127 self.console.write(" ")?;
128 self.console.move_within_line(-((tail_len + 1) as i16))?;
129 self.console.show_cursor()?;
130 self.line.remove(remove_pos);
131 Ok(())
132 }
133
134 fn do_backspace(&mut self) -> io::Result<DispatchResult> {
136 if self.pos == 0 {
137 return Ok(DispatchResult::Continue);
138 }
139
140 if self.view_start == 0 && self.line.len() <= self.input_width {
141 self.erase_at(self.pos, self.pos - 1)?;
142 self.pos -= 1;
143 self.sync_rendered();
144 } else {
145 let old_pos = self.current_pos();
146 let clear_len = self.rendered_len;
147 self.line.remove(self.pos - 1);
148 self.pos -= 1;
149 self.view_start = self.adjust_view(self.pos, self.view_start);
150 self.sync_rendered();
151 self.redraw_line(old_pos, clear_len, self.current_pos())?;
152 }
153 Ok(DispatchResult::Continue)
154 }
155
156 fn do_carriage_return(&mut self) -> io::Result<DispatchResult> {
158 if cfg!(not(target_os = "windows")) {
163 self.console.print("")?;
164 Ok(DispatchResult::Submit)
165 } else {
166 Ok(DispatchResult::Continue)
167 }
168 }
169
170 fn do_char(&mut self, ch: char) -> io::Result<DispatchResult> {
172 if self.input_width == 0 {
173 return Ok(DispatchResult::Continue);
174 }
175
176 let line_len = self.line.len();
177 let new_view_start = self.adjust_view(self.pos + 1, self.view_start);
178 if new_view_start == self.view_start && line_len < self.input_width {
179 if self.pos < line_len {
180 self.console.hide_cursor()?;
181 if self.echo {
182 let mut buf = [0u8; 4];
183 self.console.write(ch.encode_utf8(&mut buf))?;
184 self.console.write(&self.line.end(self.pos))?;
185 } else {
186 self.console.write(&SECURE_CHAR.repeat(line_len - self.pos + 1))?;
187 }
188 self.console.move_within_line(-((line_len - self.pos) as i16))?;
189 self.console.show_cursor()?;
190 self.line.insert(self.pos, ch);
191 } else {
192 if self.echo {
193 let mut buf = [0u8; 4];
194 self.console.write(ch.encode_utf8(&mut buf))?;
195 } else {
196 self.console.write(SECURE_CHAR)?;
197 }
198 self.line.insert(line_len, ch);
199 }
200 self.pos += 1;
201 self.sync_rendered();
202 } else {
203 let old_pos = self.current_pos();
204 let clear_len = self.rendered_len;
205 self.line.insert(self.pos, ch);
206 self.pos += 1;
207 self.view_start = new_view_start;
208 self.sync_rendered();
209 self.redraw_line(old_pos, clear_len, self.current_pos())?;
210 }
211 Ok(DispatchResult::Continue)
212 }
213
214 fn do_delete(&mut self) -> io::Result<DispatchResult> {
216 if self.pos >= self.line.len() {
217 return Ok(DispatchResult::Continue);
218 }
219
220 if self.view_start == 0 && self.line.len() <= self.input_width {
221 self.erase_at(self.pos, self.pos)?;
222 self.sync_rendered();
223 } else {
224 let old_pos = self.current_pos();
225 let clear_len = self.rendered_len;
226 self.line.remove(self.pos);
227 self.view_start = self.adjust_view(self.pos, self.view_start);
228 self.sync_rendered();
229 self.redraw_line(old_pos, clear_len, self.current_pos())?;
230 }
231 Ok(DispatchResult::Continue)
232 }
233
234 fn do_end(&mut self) -> io::Result<DispatchResult> {
236 self.do_move_to(self.line.len())
237 }
238
239 fn do_eof_or_delete(&mut self) -> io::Result<DispatchResult> {
241 if self.line.is_empty() {
242 Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF"))
243 } else {
244 self.do_delete()
245 }
246 }
247
248 fn do_home(&mut self) -> io::Result<DispatchResult> {
250 self.do_move_to(0)
251 }
252
253 fn do_ignore(&mut self) -> io::Result<DispatchResult> {
255 Ok(DispatchResult::Continue)
256 }
257
258 fn do_interrupt(&mut self) -> io::Result<DispatchResult> {
260 Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl+C"))
261 }
262
263 fn do_move_to(&mut self, new_pos: usize) -> io::Result<DispatchResult> {
265 if new_pos == self.pos {
266 return Ok(DispatchResult::Continue);
267 }
268
269 let old_pos = self.current_pos();
270 let new_view_start = self.adjust_view(new_pos, self.view_start);
271 if new_view_start == self.view_start {
272 let delta = new_pos as i16 - self.pos as i16;
273 self.console.move_within_line(delta)?;
274 self.pos = new_pos;
275 } else {
276 let clear_len = self.rendered_len;
277 self.pos = new_pos;
278 self.view_start = new_view_start;
279 self.sync_rendered();
280 self.redraw_line(old_pos, clear_len, self.current_pos())?;
281 }
282 Ok(DispatchResult::Continue)
283 }
284
285 fn do_newline(&mut self) -> io::Result<DispatchResult> {
287 self.console.print("")?;
288 Ok(DispatchResult::Submit)
289 }
290
291 fn do_up_down(&mut self, delta: isize) -> io::Result<DispatchResult> {
293 let (new_history_pos, new_line) = {
294 let history = match self.history.as_deref_mut() {
295 Some(history) => history,
296 None => return Ok(DispatchResult::Continue),
297 };
298
299 let new_history_pos = if delta < 0 {
300 if self.history_pos == 0 {
301 return Ok(DispatchResult::Continue);
302 }
303 self.history_pos - 1
304 } else {
305 if self.history_pos == history.len() - 1 {
306 return Ok(DispatchResult::Continue);
307 }
308 self.history_pos + 1
309 };
310
311 history[self.history_pos] = self.line.to_string();
312 (new_history_pos, history[new_history_pos].clone())
313 };
314
315 let old_pos = self.current_pos();
316 let clear_len = self.rendered_len;
317 self.history_pos = new_history_pos;
318 self.line = LineBuffer::from(&new_line);
319 self.pos = self.line.len();
320 self.reset_view_to_end();
321 self.redraw_line(old_pos, clear_len, self.current_pos())?;
322 Ok(DispatchResult::Continue)
323 }
324
325 fn finish(mut self) -> String {
327 if let Some(history) = self.history.as_mut() {
328 if self.line.is_empty() {
329 history.pop();
330 } else {
331 let last = history.len() - 1;
332 history[last] = self.line.to_string();
333 }
334 }
335 self.line.into_inner()
336 }
337
338 fn new(
340 console: &'a mut dyn Console,
341 prompt: &str,
342 previous: &str,
343 mut history: Option<&'h mut Vec<String>>,
344 echo: bool,
345 ) -> io::Result<Self> {
346 let console_width = {
347 let console_size = console.size_chars()?;
348 usize::from(console_size.x)
349 };
350
351 let mut prompt = Cow::from(prompt);
352 let mut prompt_len = prompt.len();
353 if prompt_len >= console_width {
354 if console_width >= 5 {
356 prompt = Cow::from(format!("{}...", &prompt[0..console_width - 5]));
357 } else {
358 prompt = Cow::from("");
359 }
360 prompt_len = prompt.len();
361 }
362
363 let input_width = {
364 let width = console_width - prompt_len;
367 width.saturating_sub(1)
368 };
369
370 let line = LineBuffer::from(previous);
371 let pos = line.len();
372 let view_start = if input_width == 0 { 0 } else { pos.saturating_sub(input_width) };
373 let history_pos = match history.as_mut() {
374 Some(history) => {
375 history.push(line.to_string());
376 history.len() - 1
377 }
378 None => 0,
379 };
380
381 let mut readline = Self {
382 console,
383 history,
384 echo,
385 input_width,
386 line,
387 pos,
388 view_start,
389 rendered: String::new(),
390 rendered_len: 0,
391 history_pos,
392 };
393 readline.view_start = readline.adjust_view(readline.pos, readline.view_start);
394 readline.sync_rendered();
395
396 if !prompt.is_empty() || !readline.rendered.is_empty() {
397 readline.console.write(&format!("{}{}", prompt, readline.rendered))?;
398 readline.console.sync_now()?;
399 }
400
401 Ok(readline)
402 }
403
404 async fn run(mut self) -> io::Result<String> {
406 loop {
407 let result = match self.console.read_key().await? {
408 Key::ArrowDown => self.do_up_down(1),
409 Key::ArrowLeft => self.do_move_to(self.pos.saturating_sub(1)),
410 Key::ArrowRight => self.do_move_to((self.pos + 1).min(self.line.len())),
411 Key::ArrowUp => self.do_up_down(-1),
412 Key::Backspace => self.do_backspace(),
413 Key::CarriageReturn => self.do_carriage_return(),
414 Key::Char(ch) => self.do_char(ch),
415 Key::Delete => self.do_delete(),
416 Key::End => self.do_end(),
417 Key::EofOrDelete => self.do_eof_or_delete(),
418 Key::Escape => self.do_ignore(),
419 Key::Home => self.do_home(),
420 Key::Interrupt => self.do_interrupt(),
421 Key::NewLine => self.do_newline(),
422 Key::PageDown | Key::PageUp | Key::Tab | Key::Unknown => self.do_ignore(),
423 }?;
424
425 if let DispatchResult::Submit = result {
426 break;
427 }
428 }
429
430 Ok(self.finish())
431 }
432}
433
434async fn read_line_interactive(
438 console: &mut dyn Console,
439 prompt: &str,
440 previous: &str,
441 mut history: Option<&mut Vec<String>>,
442 echo: bool,
443) -> io::Result<String> {
444 Readline::new(console, prompt, previous, history.take(), echo)?.run().await
445}
446
447async fn read_line_raw(console: &mut dyn Console) -> io::Result<String> {
449 let mut line = String::new();
450 loop {
451 match console.read_key().await? {
452 Key::ArrowUp | Key::ArrowDown | Key::ArrowLeft | Key::ArrowRight => (),
453 Key::Backspace => {
454 if !line.is_empty() {
455 line.pop();
456 }
457 }
458 Key::CarriageReturn => {
459 if cfg!(not(target_os = "windows")) {
464 break;
465 }
466 }
467 Key::Char(ch) => line.push(ch),
468 Key::Delete => (),
469 Key::End | Key::Home => (),
470 Key::Escape => (),
471 Key::EofOrDelete => return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF")),
472 Key::Interrupt => return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl+C")),
473 Key::NewLine => break,
474 Key::PageDown | Key::PageUp => (),
475 Key::Tab => (),
476 Key::Unknown => line.push('?'),
477 }
478 }
479 Ok(line)
480}
481
482pub async fn read_line(
485 console: &mut dyn Console,
486 prompt: &str,
487 previous: &str,
488 history: Option<&mut Vec<String>>,
489) -> io::Result<String> {
490 if console.is_interactive() {
491 read_line_interactive(console, prompt, previous, history, true).await
492 } else {
493 read_line_raw(console).await
494 }
495}
496
497pub async fn read_line_secure(console: &mut dyn Console, prompt: &str) -> io::Result<String> {
502 if !console.is_interactive() {
503 return Err(io::Error::other("Cannot read secure strings from a raw console".to_owned()));
504 }
505 read_line_interactive(console, prompt, "", None, false).await
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511 use crate::console::CharsXY;
512 use crate::testutils::*;
513 use futures_lite::future::block_on;
514
515 #[must_use]
517 struct ReadLineInteractiveTest {
518 size_chars: CharsXY,
519 keys: Vec<Key>,
520 prompt: &'static str,
521 previous: &'static str,
522 history: Option<Vec<String>>,
523 echo: bool,
524 exp_line: &'static str,
525 exp_output: Vec<CapturedOut>,
526 exp_history: Option<Vec<String>>,
527 }
528
529 impl Default for ReadLineInteractiveTest {
530 fn default() -> Self {
533 Self {
534 size_chars: CharsXY::new(15, 5),
535 keys: vec![],
536 prompt: "",
537 previous: "",
538 history: None,
539 echo: true,
540 exp_line: "",
541 exp_output: vec![],
542 exp_history: None,
543 }
544 }
545 }
546
547 impl ReadLineInteractiveTest {
548 fn add_key(mut self, key: Key) -> Self {
550 self.keys.push(key);
551 self
552 }
553
554 fn add_key_chars(mut self, chars: &'static str) -> Self {
556 for ch in chars.chars() {
557 self.keys.push(Key::Char(ch));
558 }
559 self
560 }
561
562 fn add_output(mut self, output: CapturedOut) -> Self {
564 self.exp_output.push(output);
565 self
566 }
567
568 fn add_output_bytes(mut self, bytes: &'static str) -> Self {
570 if bytes.is_empty() {
571 self.exp_output.push(CapturedOut::Write("".to_string()))
572 } else {
573 for b in bytes.chars() {
574 let mut buf = [0u8; 4];
575 self.exp_output.push(CapturedOut::Write(b.encode_utf8(&mut buf).to_string()));
576 }
577 }
578 self
579 }
580
581 fn set_size_chars(mut self, size: CharsXY) -> Self {
583 self.size_chars = size;
584 self
585 }
586
587 fn set_line(mut self, line: &'static str) -> Self {
589 self.exp_line = line;
590 self
591 }
592
593 fn set_prompt(mut self, prompt: &'static str) -> Self {
595 self.prompt = prompt;
596 self
597 }
598
599 fn set_previous(mut self, previous: &'static str) -> Self {
601 self.previous = previous;
602 self
603 }
604
605 fn set_history(mut self, history: Vec<String>, exp_history: Vec<String>) -> Self {
608 self.history = Some(history);
609 self.exp_history = Some(exp_history);
610 self
611 }
612
613 fn set_echo(mut self, echo: bool) -> Self {
615 self.echo = echo;
616 self
617 }
618
619 fn accept(mut self) {
622 self.keys.push(Key::NewLine);
623 self.exp_output.push(CapturedOut::Print("".to_owned()));
624
625 let mut console = MockConsole::default();
626 console.add_input_keys(&self.keys);
627 console.set_size_chars(self.size_chars);
628 let line = block_on(read_line_interactive(
629 &mut console,
630 self.prompt,
631 self.previous,
632 self.history.as_mut(),
633 self.echo,
634 ))
635 .unwrap();
636 assert_eq!(self.exp_line, &line);
637 assert_eq!(self.exp_output.as_slice(), console.captured_out());
638 assert_eq!(self.exp_history, self.history);
639 }
640
641 fn expect_err(mut self, exp_kind: io::ErrorKind) {
643 let mut console = MockConsole::default();
644 console.add_input_keys(&self.keys);
645 console.set_size_chars(self.size_chars);
646 let err = block_on(read_line_interactive(
647 &mut console,
648 self.prompt,
649 self.previous,
650 self.history.as_mut(),
651 self.echo,
652 ))
653 .expect_err("read_line_interactive should fail");
654 assert_eq!(exp_kind, err.kind());
655 assert_eq!(self.exp_output.as_slice(), console.captured_out());
656 assert_eq!(self.exp_history, self.history);
657 }
658 }
659
660 #[test]
661 fn test_read_line_interactive_empty() {
662 ReadLineInteractiveTest::default().accept();
663 ReadLineInteractiveTest::default().add_key(Key::Backspace).accept();
664 ReadLineInteractiveTest::default().add_key(Key::Delete).accept();
665 ReadLineInteractiveTest::default()
666 .add_key(Key::EofOrDelete)
667 .expect_err(io::ErrorKind::UnexpectedEof);
668 ReadLineInteractiveTest::default().add_key(Key::ArrowLeft).accept();
669 ReadLineInteractiveTest::default().add_key(Key::ArrowRight).accept();
670 }
671
672 #[test]
673 fn test_read_line_with_prompt() {
674 ReadLineInteractiveTest::default()
675 .set_prompt("Ready> ")
676 .add_output(CapturedOut::Write("Ready> ".to_string()))
677 .add_output(CapturedOut::SyncNow)
678 .add_key_chars("hello")
680 .add_output_bytes("hello")
681 .set_line("hello")
683 .accept();
684
685 ReadLineInteractiveTest::default()
686 .set_prompt("Cannot delete")
687 .add_output(CapturedOut::Write("Cannot delete".to_string()))
688 .add_output(CapturedOut::SyncNow)
689 .add_key(Key::Backspace)
691 .accept();
692 }
693
694 #[test]
695 fn test_read_line_with_prompt_larger_than_screen() {
696 ReadLineInteractiveTest::default()
697 .set_size_chars(CharsXY::new(15, 5))
698 .set_prompt("This is larger than the screen> ")
699 .add_output(CapturedOut::Write("This is la...".to_string()))
700 .add_output(CapturedOut::SyncNow)
701 .add_key_chars("hello")
703 .add_output_bytes("h")
704 .add_output(CapturedOut::HideCursor)
705 .add_output(CapturedOut::MoveWithinLine(-1))
706 .add_output(CapturedOut::Write("e".to_string()))
707 .add_output(CapturedOut::ShowCursor)
708 .add_output(CapturedOut::HideCursor)
709 .add_output(CapturedOut::MoveWithinLine(-1))
710 .add_output(CapturedOut::Write("l".to_string()))
711 .add_output(CapturedOut::ShowCursor)
712 .add_output(CapturedOut::HideCursor)
713 .add_output(CapturedOut::MoveWithinLine(-1))
714 .add_output(CapturedOut::Write("l".to_string()))
715 .add_output(CapturedOut::ShowCursor)
716 .add_output(CapturedOut::HideCursor)
717 .add_output(CapturedOut::MoveWithinLine(-1))
718 .add_output(CapturedOut::Write("o".to_string()))
719 .add_output(CapturedOut::ShowCursor)
720 .set_line("hello")
722 .accept();
723 }
724
725 #[test]
726 fn test_read_line_with_prompt_equal_to_screen() {
727 ReadLineInteractiveTest::default()
728 .set_size_chars(CharsXY::new(10, 5))
729 .set_prompt("0123456789")
730 .add_output(CapturedOut::Write("01234...".to_string()))
731 .add_output(CapturedOut::SyncNow)
732 .add_key_chars("hello")
734 .add_output_bytes("h")
735 .add_output(CapturedOut::HideCursor)
736 .add_output(CapturedOut::MoveWithinLine(-1))
737 .add_output(CapturedOut::Write("e".to_string()))
738 .add_output(CapturedOut::ShowCursor)
739 .add_output(CapturedOut::HideCursor)
740 .add_output(CapturedOut::MoveWithinLine(-1))
741 .add_output(CapturedOut::Write("l".to_string()))
742 .add_output(CapturedOut::ShowCursor)
743 .add_output(CapturedOut::HideCursor)
744 .add_output(CapturedOut::MoveWithinLine(-1))
745 .add_output(CapturedOut::Write("l".to_string()))
746 .add_output(CapturedOut::ShowCursor)
747 .add_output(CapturedOut::HideCursor)
748 .add_output(CapturedOut::MoveWithinLine(-1))
749 .add_output(CapturedOut::Write("o".to_string()))
750 .add_output(CapturedOut::ShowCursor)
751 .set_line("hello")
753 .accept();
754 }
755
756 #[test]
757 fn test_read_line_with_prompt_larger_than_tiny_screen() {
758 ReadLineInteractiveTest::default()
759 .set_size_chars(CharsXY::new(3, 5))
760 .set_prompt("This is larger than the screen> ")
761 .add_key_chars("hello")
763 .add_output_bytes("he")
764 .add_output(CapturedOut::HideCursor)
765 .add_output(CapturedOut::MoveWithinLine(-2))
766 .add_output(CapturedOut::Write("el".to_string()))
767 .add_output(CapturedOut::ShowCursor)
768 .add_output(CapturedOut::HideCursor)
769 .add_output(CapturedOut::MoveWithinLine(-2))
770 .add_output(CapturedOut::Write("ll".to_string()))
771 .add_output(CapturedOut::ShowCursor)
772 .add_output(CapturedOut::HideCursor)
773 .add_output(CapturedOut::MoveWithinLine(-2))
774 .add_output(CapturedOut::Write("lo".to_string()))
775 .add_output(CapturedOut::ShowCursor)
776 .set_line("hello")
778 .accept();
779 }
780
781 #[test]
782 fn test_read_line_with_prompt_shorter_than_tiny_screen() {
783 ReadLineInteractiveTest::default()
784 .set_size_chars(CharsXY::new(3, 5))
785 .set_prompt("?")
786 .add_output(CapturedOut::Write("?".to_string()))
787 .add_output(CapturedOut::SyncNow)
788 .add_key_chars("hello")
790 .add_output_bytes("h")
791 .add_output(CapturedOut::HideCursor)
792 .add_output(CapturedOut::MoveWithinLine(-1))
793 .add_output(CapturedOut::Write("e".to_string()))
794 .add_output(CapturedOut::ShowCursor)
795 .add_output(CapturedOut::HideCursor)
796 .add_output(CapturedOut::MoveWithinLine(-1))
797 .add_output(CapturedOut::Write("l".to_string()))
798 .add_output(CapturedOut::ShowCursor)
799 .add_output(CapturedOut::HideCursor)
800 .add_output(CapturedOut::MoveWithinLine(-1))
801 .add_output(CapturedOut::Write("l".to_string()))
802 .add_output(CapturedOut::ShowCursor)
803 .add_output(CapturedOut::HideCursor)
804 .add_output(CapturedOut::MoveWithinLine(-1))
805 .add_output(CapturedOut::Write("o".to_string()))
806 .add_output(CapturedOut::ShowCursor)
807 .set_line("hello")
809 .accept();
810 }
811
812 #[test]
813 fn test_read_line_interactive_trailing_input() {
814 ReadLineInteractiveTest::default()
815 .add_key_chars("hello")
816 .add_output_bytes("hello")
817 .set_line("hello")
819 .accept();
820
821 ReadLineInteractiveTest::default()
822 .set_previous("123")
823 .add_output(CapturedOut::Write("123".to_string()))
824 .add_output(CapturedOut::SyncNow)
825 .add_key_chars("hello")
827 .add_output_bytes("hello")
828 .set_line("123hello")
830 .accept();
831 }
832
833 #[test]
834 fn test_read_line_interactive_middle_input() {
835 ReadLineInteractiveTest::default()
836 .add_key_chars("some text")
837 .add_output_bytes("some text")
838 .add_key(Key::ArrowLeft)
840 .add_output(CapturedOut::MoveWithinLine(-1))
841 .add_key(Key::ArrowLeft)
843 .add_output(CapturedOut::MoveWithinLine(-1))
844 .add_key(Key::ArrowLeft)
846 .add_output(CapturedOut::MoveWithinLine(-1))
847 .add_key(Key::ArrowRight)
849 .add_output(CapturedOut::MoveWithinLine(1))
850 .add_key_chars(" ")
852 .add_output(CapturedOut::HideCursor)
853 .add_output_bytes(" ")
854 .add_output(CapturedOut::Write("xt".to_string()))
855 .add_output(CapturedOut::MoveWithinLine(-2))
856 .add_output(CapturedOut::ShowCursor)
857 .add_key_chars(".")
859 .add_output(CapturedOut::HideCursor)
860 .add_output_bytes(".")
861 .add_output(CapturedOut::Write("xt".to_string()))
862 .add_output(CapturedOut::MoveWithinLine(-2))
863 .add_output(CapturedOut::ShowCursor)
864 .set_line("some te .xt")
866 .accept();
867 }
868
869 #[test]
870 fn test_read_line_interactive_utf8_basic() {
871 ReadLineInteractiveTest::default()
872 .add_key_chars("é")
873 .add_output(CapturedOut::Write("é".to_string()))
874 .set_line("é")
876 .accept();
877 }
878
879 #[test]
880 fn test_read_line_interactive_utf8_remove_2byte_char() {
881 ReadLineInteractiveTest::default()
882 .add_key_chars("é")
883 .add_output(CapturedOut::Write("é".to_string()))
884 .add_key(Key::Backspace)
886 .add_output(CapturedOut::HideCursor)
887 .add_output(CapturedOut::MoveWithinLine(-1))
888 .add_output_bytes("")
889 .add_output_bytes(" ")
890 .add_output(CapturedOut::MoveWithinLine(-1))
891 .add_output(CapturedOut::ShowCursor)
892 .set_line("")
894 .accept();
895 }
896
897 #[test]
898 fn test_read_line_interactive_utf8_add_and_remove_last() {
899 ReadLineInteractiveTest::default()
900 .add_key_chars("àé")
901 .add_output(CapturedOut::Write("à".to_string()))
902 .add_output(CapturedOut::Write("é".to_string()))
903 .add_key(Key::Backspace)
905 .add_output(CapturedOut::HideCursor)
906 .add_output(CapturedOut::MoveWithinLine(-1))
907 .add_output_bytes("")
908 .add_output_bytes(" ")
909 .add_output(CapturedOut::MoveWithinLine(-1))
910 .add_output(CapturedOut::ShowCursor)
911 .set_line("à")
913 .accept();
914 }
915
916 #[test]
917 fn test_read_line_interactive_utf8_navigate_2byte_chars() {
918 ReadLineInteractiveTest::default()
919 .add_key_chars("àé")
920 .add_output(CapturedOut::Write("à".to_string()))
921 .add_output(CapturedOut::Write("é".to_string()))
922 .add_key(Key::ArrowLeft)
924 .add_output(CapturedOut::MoveWithinLine(-1))
925 .add_key(Key::ArrowLeft)
927 .add_output(CapturedOut::MoveWithinLine(-1))
928 .add_key(Key::ArrowLeft)
930 .add_key(Key::ArrowRight)
932 .add_output(CapturedOut::MoveWithinLine(1))
933 .add_key(Key::Backspace)
935 .add_output(CapturedOut::HideCursor)
936 .add_output(CapturedOut::MoveWithinLine(-1))
937 .add_output(CapturedOut::Write("é".to_string()))
938 .add_output_bytes(" ")
939 .add_output(CapturedOut::MoveWithinLine(-2))
940 .add_output(CapturedOut::ShowCursor)
941 .set_line("é")
943 .accept();
944 }
945
946 #[test]
947 fn test_read_line_interactive_trailing_backspace() {
948 ReadLineInteractiveTest::default()
949 .add_key_chars("bar")
950 .add_output_bytes("bar")
951 .add_key(Key::Backspace)
953 .add_output(CapturedOut::HideCursor)
954 .add_output(CapturedOut::MoveWithinLine(-1))
955 .add_output_bytes("")
956 .add_output_bytes(" ")
957 .add_output(CapturedOut::MoveWithinLine(-1))
958 .add_output(CapturedOut::ShowCursor)
959 .add_key_chars("zar")
961 .add_output_bytes("zar")
962 .set_line("bazar")
964 .accept();
965 }
966
967 #[test]
968 fn test_read_line_interactive_middle_backspace() {
969 ReadLineInteractiveTest::default()
970 .add_key_chars("has a tYpo")
971 .add_output_bytes("has a tYpo")
972 .add_key(Key::ArrowLeft)
974 .add_output(CapturedOut::MoveWithinLine(-1))
975 .add_key(Key::ArrowLeft)
977 .add_output(CapturedOut::MoveWithinLine(-1))
978 .add_key(Key::Backspace)
980 .add_output(CapturedOut::HideCursor)
981 .add_output(CapturedOut::MoveWithinLine(-1))
982 .add_output(CapturedOut::Write("po".to_string()))
983 .add_output_bytes(" ")
984 .add_output(CapturedOut::MoveWithinLine(-3))
985 .add_output(CapturedOut::ShowCursor)
986 .add_key_chars("y")
988 .add_output(CapturedOut::HideCursor)
989 .add_output_bytes("y")
990 .add_output(CapturedOut::Write("po".to_string()))
991 .add_output(CapturedOut::MoveWithinLine(-2))
992 .add_output(CapturedOut::ShowCursor)
993 .set_line("has a typo")
995 .accept();
996 }
997
998 #[test]
999 fn test_read_line_interactive_middle_delete() {
1000 ReadLineInteractiveTest::default()
1001 .add_key_chars("has a typo")
1002 .add_output_bytes("has a typo")
1003 .add_key(Key::ArrowLeft)
1005 .add_output(CapturedOut::MoveWithinLine(-1))
1006 .add_key(Key::ArrowLeft)
1008 .add_output(CapturedOut::MoveWithinLine(-1))
1009 .add_key(Key::Delete)
1011 .add_output(CapturedOut::HideCursor)
1012 .add_output(CapturedOut::Write("o".to_string()))
1013 .add_output_bytes(" ")
1014 .add_output(CapturedOut::MoveWithinLine(-2))
1015 .add_output(CapturedOut::ShowCursor)
1016 .add_key_chars("e")
1018 .add_output(CapturedOut::HideCursor)
1019 .add_output_bytes("e")
1020 .add_output(CapturedOut::Write("o".to_string()))
1021 .add_output(CapturedOut::MoveWithinLine(-1))
1022 .add_output(CapturedOut::ShowCursor)
1023 .set_line("has a tyeo")
1025 .accept();
1026 }
1027
1028 #[test]
1029 fn test_read_line_interactive_middle_ctrl_d_deletes() {
1030 ReadLineInteractiveTest::default()
1031 .add_key_chars("has a typo")
1032 .add_output_bytes("has a typo")
1033 .add_key(Key::ArrowLeft)
1035 .add_output(CapturedOut::MoveWithinLine(-1))
1036 .add_key(Key::ArrowLeft)
1038 .add_output(CapturedOut::MoveWithinLine(-1))
1039 .add_key(Key::EofOrDelete)
1041 .add_output(CapturedOut::HideCursor)
1042 .add_output(CapturedOut::Write("o".to_string()))
1043 .add_output_bytes(" ")
1044 .add_output(CapturedOut::MoveWithinLine(-2))
1045 .add_output(CapturedOut::ShowCursor)
1046 .add_key_chars("e")
1048 .add_output(CapturedOut::HideCursor)
1049 .add_output_bytes("e")
1050 .add_output(CapturedOut::Write("o".to_string()))
1051 .add_output(CapturedOut::MoveWithinLine(-1))
1052 .add_output(CapturedOut::ShowCursor)
1053 .set_line("has a tyeo")
1055 .accept();
1056 }
1057
1058 #[test]
1059 fn test_read_line_interactive_utf8_delete_2byte_char() {
1060 ReadLineInteractiveTest::default()
1061 .add_key_chars("àé")
1062 .add_output(CapturedOut::Write("à".to_string()))
1063 .add_output(CapturedOut::Write("é".to_string()))
1064 .add_key(Key::ArrowLeft)
1066 .add_output(CapturedOut::MoveWithinLine(-1))
1067 .add_key(Key::Delete)
1069 .add_output(CapturedOut::HideCursor)
1070 .add_output_bytes("")
1071 .add_output_bytes(" ")
1072 .add_output(CapturedOut::MoveWithinLine(-1))
1073 .add_output(CapturedOut::ShowCursor)
1074 .set_line("à")
1076 .accept();
1077 }
1078
1079 #[test]
1080 fn test_read_line_interactive_delete_at_end_is_ignored() {
1081 ReadLineInteractiveTest::default()
1082 .set_previous("sample")
1083 .add_output(CapturedOut::Write("sample".to_string()))
1084 .add_output(CapturedOut::SyncNow)
1085 .add_key(Key::Delete)
1087 .set_line("sample")
1089 .accept();
1090 }
1091
1092 #[test]
1093 fn test_read_line_interactive_ctrl_d_at_end_is_ignored() {
1094 ReadLineInteractiveTest::default()
1095 .set_previous("sample")
1096 .add_output(CapturedOut::Write("sample".to_string()))
1097 .add_output(CapturedOut::SyncNow)
1098 .add_key(Key::EofOrDelete)
1100 .set_line("sample")
1102 .accept();
1103 }
1104
1105 #[test]
1106 fn test_read_line_interactive_test_move_bounds() {
1107 ReadLineInteractiveTest::default()
1108 .set_previous("12")
1109 .add_output(CapturedOut::Write("12".to_string()))
1110 .add_output(CapturedOut::SyncNow)
1111 .add_key(Key::ArrowLeft)
1113 .add_output(CapturedOut::MoveWithinLine(-1))
1114 .add_key(Key::ArrowLeft)
1116 .add_output(CapturedOut::MoveWithinLine(-1))
1117 .add_key(Key::ArrowLeft)
1119 .add_key(Key::ArrowLeft)
1120 .add_key(Key::ArrowLeft)
1121 .add_key(Key::ArrowLeft)
1122 .add_key(Key::ArrowRight)
1124 .add_output(CapturedOut::MoveWithinLine(1))
1125 .add_key(Key::ArrowRight)
1127 .add_output(CapturedOut::MoveWithinLine(1))
1128 .add_key(Key::ArrowRight)
1130 .add_key(Key::ArrowRight)
1131 .add_key_chars("3")
1133 .add_output_bytes("3")
1134 .set_line("123")
1136 .accept();
1137 }
1138
1139 #[test]
1140 fn test_read_line_interactive_test_home_end() {
1141 ReadLineInteractiveTest::default()
1142 .set_previous("sample text")
1143 .add_output(CapturedOut::Write("sample text".to_string()))
1144 .add_output(CapturedOut::SyncNow)
1145 .add_key(Key::End)
1147 .add_key(Key::Home)
1149 .add_output(CapturedOut::MoveWithinLine(-11))
1150 .add_key(Key::Home)
1152 .add_key(Key::Char('>'))
1154 .add_output(CapturedOut::HideCursor)
1155 .add_output_bytes(">")
1156 .add_output(CapturedOut::Write("sample text".to_string()))
1157 .add_output(CapturedOut::MoveWithinLine(-11))
1158 .add_output(CapturedOut::ShowCursor)
1159 .add_key(Key::End)
1161 .add_output(CapturedOut::MoveWithinLine(11))
1162 .add_key(Key::Char('<'))
1164 .add_output_bytes("<")
1165 .add_key(Key::ArrowLeft)
1167 .add_output(CapturedOut::MoveWithinLine(-1))
1168 .add_key(Key::ArrowLeft)
1170 .add_output(CapturedOut::MoveWithinLine(-1))
1171 .add_key(Key::ArrowLeft)
1173 .add_output(CapturedOut::MoveWithinLine(-1))
1174 .add_key(Key::ArrowLeft)
1176 .add_output(CapturedOut::MoveWithinLine(-1))
1177 .add_key(Key::ArrowLeft)
1179 .add_output(CapturedOut::MoveWithinLine(-1))
1180 .add_key(Key::Backspace)
1182 .add_output(CapturedOut::HideCursor)
1183 .add_output(CapturedOut::MoveWithinLine(-1))
1184 .add_output(CapturedOut::Write("text<".to_string()))
1185 .add_output_bytes(" ")
1186 .add_output(CapturedOut::MoveWithinLine(-6))
1187 .add_output(CapturedOut::ShowCursor)
1188 .set_line(">sampletext<")
1190 .accept();
1191 }
1192
1193 #[test]
1194 fn test_read_line_interactive_horizontal_scrolling_while_appending() {
1195 ReadLineInteractiveTest::default()
1196 .add_key_chars("12345678901234")
1197 .add_output_bytes("12345678901234")
1198 .add_key_chars("5")
1200 .add_output(CapturedOut::HideCursor)
1201 .add_output(CapturedOut::MoveWithinLine(-14))
1202 .add_output(CapturedOut::Write("23456789012345".to_string()))
1203 .add_output(CapturedOut::ShowCursor)
1204 .set_line("123456789012345")
1206 .accept();
1207 }
1208
1209 #[test]
1210 fn test_read_line_interactive_horizontal_scrolling_while_navigating() {
1211 ReadLineInteractiveTest::default()
1212 .set_size_chars(CharsXY::new(6, 5))
1213 .set_previous("abcdef")
1214 .add_output(CapturedOut::Write("bcdef".to_string()))
1215 .add_output(CapturedOut::SyncNow)
1216 .add_key(Key::ArrowLeft)
1218 .add_output(CapturedOut::MoveWithinLine(-1))
1219 .add_key(Key::ArrowLeft)
1220 .add_output(CapturedOut::MoveWithinLine(-1))
1221 .add_key(Key::ArrowLeft)
1222 .add_output(CapturedOut::MoveWithinLine(-1))
1223 .add_key(Key::ArrowLeft)
1224 .add_output(CapturedOut::MoveWithinLine(-1))
1225 .add_key(Key::ArrowLeft)
1226 .add_output(CapturedOut::MoveWithinLine(-1))
1227 .add_key(Key::ArrowLeft)
1228 .add_output(CapturedOut::HideCursor)
1229 .add_output(CapturedOut::Write("abcde".to_string()))
1230 .add_output(CapturedOut::MoveWithinLine(-5))
1231 .add_output(CapturedOut::ShowCursor)
1232 .add_key(Key::ArrowRight)
1234 .add_output(CapturedOut::MoveWithinLine(1))
1235 .add_key(Key::ArrowRight)
1236 .add_output(CapturedOut::MoveWithinLine(1))
1237 .add_key(Key::ArrowRight)
1238 .add_output(CapturedOut::MoveWithinLine(1))
1239 .add_key(Key::ArrowRight)
1240 .add_output(CapturedOut::MoveWithinLine(1))
1241 .add_key(Key::ArrowRight)
1242 .add_output(CapturedOut::MoveWithinLine(1))
1243 .add_key(Key::ArrowRight)
1244 .add_output(CapturedOut::HideCursor)
1245 .add_output(CapturedOut::MoveWithinLine(-5))
1246 .add_output(CapturedOut::Write("bcdef".to_string()))
1247 .add_output(CapturedOut::ShowCursor)
1248 .set_line("abcdef")
1250 .accept();
1251 }
1252
1253 #[test]
1254 fn test_read_line_interactive_horizontal_scrolling_with_prompt_and_previous() {
1255 ReadLineInteractiveTest::default()
1256 .set_prompt("12345")
1257 .set_previous("67890")
1258 .add_output(CapturedOut::Write("1234567890".to_string()))
1259 .add_output(CapturedOut::SyncNow)
1260 .add_key_chars("1234567890")
1262 .add_output_bytes("1234")
1263 .add_output(CapturedOut::HideCursor)
1264 .add_output(CapturedOut::MoveWithinLine(-9))
1265 .add_output(CapturedOut::Write("789012345".to_string()))
1266 .add_output(CapturedOut::ShowCursor)
1267 .add_output(CapturedOut::HideCursor)
1268 .add_output(CapturedOut::MoveWithinLine(-9))
1269 .add_output(CapturedOut::Write("890123456".to_string()))
1270 .add_output(CapturedOut::ShowCursor)
1271 .add_output(CapturedOut::HideCursor)
1272 .add_output(CapturedOut::MoveWithinLine(-9))
1273 .add_output(CapturedOut::Write("901234567".to_string()))
1274 .add_output(CapturedOut::ShowCursor)
1275 .add_output(CapturedOut::HideCursor)
1276 .add_output(CapturedOut::MoveWithinLine(-9))
1277 .add_output(CapturedOut::Write("012345678".to_string()))
1278 .add_output(CapturedOut::ShowCursor)
1279 .add_output(CapturedOut::HideCursor)
1280 .add_output(CapturedOut::MoveWithinLine(-9))
1281 .add_output(CapturedOut::Write("123456789".to_string()))
1282 .add_output(CapturedOut::ShowCursor)
1283 .add_output(CapturedOut::HideCursor)
1284 .add_output(CapturedOut::MoveWithinLine(-9))
1285 .add_output(CapturedOut::Write("234567890".to_string()))
1286 .add_output(CapturedOut::ShowCursor)
1287 .set_line("678901234567890")
1289 .accept();
1290 }
1291
1292 #[test]
1293 fn test_read_line_interactive_horizontal_scrolling_secure() {
1294 ReadLineInteractiveTest::default()
1295 .set_size_chars(CharsXY::new(6, 5))
1296 .set_echo(false)
1297 .add_key_chars("abcdef")
1298 .add_output_bytes("*****")
1299 .add_output(CapturedOut::HideCursor)
1300 .add_output(CapturedOut::MoveWithinLine(-5))
1301 .add_output(CapturedOut::Write("*****".to_string()))
1302 .add_output(CapturedOut::ShowCursor)
1303 .set_line("abcdef")
1305 .accept();
1306 }
1307
1308 #[test]
1309 fn test_read_line_interactive_horizontal_scrolling_delete() {
1310 ReadLineInteractiveTest::default()
1311 .set_size_chars(CharsXY::new(6, 5))
1312 .set_previous("abcdef")
1313 .add_output(CapturedOut::Write("bcdef".to_string()))
1314 .add_output(CapturedOut::SyncNow)
1315 .add_key(Key::ArrowLeft)
1317 .add_output(CapturedOut::MoveWithinLine(-1))
1318 .add_key(Key::Delete)
1319 .add_output(CapturedOut::HideCursor)
1320 .add_output(CapturedOut::MoveWithinLine(-4))
1321 .add_output(CapturedOut::Write("bcde".to_string()))
1322 .add_output(CapturedOut::Write(" ".to_string()))
1323 .add_output(CapturedOut::MoveWithinLine(-1))
1324 .add_output(CapturedOut::ShowCursor)
1325 .set_line("abcde")
1327 .accept();
1328 }
1329
1330 #[test]
1331 fn test_read_line_interactive_history_not_enabled_by_default() {
1332 ReadLineInteractiveTest::default().add_key(Key::ArrowUp).accept();
1333 ReadLineInteractiveTest::default().add_key(Key::ArrowDown).accept();
1334 }
1335
1336 #[test]
1337 fn test_read_line_interactive_history_empty() {
1338 ReadLineInteractiveTest::default()
1339 .set_history(vec![], vec!["foobarbaz".to_owned()])
1340 .add_key_chars("foo")
1342 .add_output_bytes("foo")
1343 .add_key(Key::ArrowUp)
1345 .add_key_chars("bar")
1347 .add_output_bytes("bar")
1348 .add_key(Key::ArrowDown)
1350 .add_key_chars("baz")
1352 .add_output_bytes("baz")
1353 .set_line("foobarbaz")
1355 .accept();
1356 }
1357
1358 #[test]
1359 fn test_read_line_interactive_skips_empty_lines() {
1360 ReadLineInteractiveTest::default()
1361 .set_history(vec!["first".to_owned()], vec!["first".to_owned()])
1362 .add_key_chars("x")
1364 .add_output(CapturedOut::Write("x".to_string()))
1365 .add_key(Key::Backspace)
1367 .add_output(CapturedOut::HideCursor)
1368 .add_output(CapturedOut::MoveWithinLine(-1))
1369 .add_output_bytes("")
1370 .add_output_bytes(" ")
1371 .add_output(CapturedOut::MoveWithinLine(-1))
1372 .add_output(CapturedOut::ShowCursor)
1373 .accept();
1375 }
1376
1377 #[test]
1378 fn test_read_line_interactive_history_navigate_up_down_end_of_line() {
1379 ReadLineInteractiveTest::default()
1380 .set_prompt("? ")
1381 .add_output(CapturedOut::Write("? ".to_string()))
1382 .add_output(CapturedOut::SyncNow)
1383 .set_history(
1385 vec!["first".to_owned(), "long second line".to_owned(), "last".to_owned()],
1386 vec!["first".to_owned(), "long second line".to_owned(), "last".to_owned()],
1387 )
1388 .add_key(Key::ArrowUp)
1390 .add_output(CapturedOut::HideCursor)
1391 .add_output(CapturedOut::Write("last".to_string()))
1392 .add_output(CapturedOut::ShowCursor)
1393 .add_key(Key::ArrowUp)
1395 .add_output(CapturedOut::HideCursor)
1396 .add_output(CapturedOut::MoveWithinLine(-("last".len() as i16)))
1397 .add_output(CapturedOut::Write(" second line".to_string()))
1398 .add_output(CapturedOut::ShowCursor)
1399 .add_key(Key::ArrowUp)
1401 .add_output(CapturedOut::HideCursor)
1402 .add_output(CapturedOut::MoveWithinLine(-(" second line".len() as i16)))
1403 .add_output(CapturedOut::Write("first".to_string()))
1404 .add_output(CapturedOut::Write(" ".to_string()))
1405 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1406 .add_output(CapturedOut::ShowCursor)
1407 .add_key(Key::ArrowUp)
1409 .add_key(Key::ArrowDown)
1411 .add_output(CapturedOut::HideCursor)
1412 .add_output(CapturedOut::MoveWithinLine(-("first".len() as i16)))
1413 .add_output(CapturedOut::Write(" second line".to_string()))
1414 .add_output(CapturedOut::ShowCursor)
1415 .add_key(Key::ArrowDown)
1417 .add_output(CapturedOut::HideCursor)
1418 .add_output(CapturedOut::MoveWithinLine(-(" second line".len() as i16)))
1419 .add_output(CapturedOut::Write("last".to_string()))
1420 .add_output(CapturedOut::Write(" ".to_string()))
1421 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1422 .add_output(CapturedOut::ShowCursor)
1423 .add_key(Key::ArrowDown)
1425 .add_output(CapturedOut::HideCursor)
1426 .add_output(CapturedOut::MoveWithinLine(-("last".len() as i16)))
1427 .add_output(CapturedOut::Write(" ".to_string()))
1428 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1429 .add_output(CapturedOut::ShowCursor)
1430 .add_key(Key::ArrowDown)
1432 .accept();
1434 }
1435
1436 #[test]
1437 fn test_read_line_interactive_history_navigate_up_down_middle_of_line() {
1438 ReadLineInteractiveTest::default()
1439 .set_prompt("? ")
1440 .add_output(CapturedOut::Write("? ".to_string()))
1441 .add_output(CapturedOut::SyncNow)
1442 .set_history(
1444 vec!["a".to_owned(), "long-line".to_owned(), "zzzz".to_owned()],
1445 vec!["a".to_owned(), "long-line".to_owned(), "zzzz".to_owned()],
1446 )
1447 .add_key(Key::ArrowUp)
1449 .add_output(CapturedOut::HideCursor)
1450 .add_output(CapturedOut::Write("zzzz".to_string()))
1451 .add_output(CapturedOut::ShowCursor)
1452 .add_key(Key::ArrowUp)
1454 .add_output(CapturedOut::HideCursor)
1455 .add_output(CapturedOut::MoveWithinLine(-("zzzz".len() as i16)))
1456 .add_output(CapturedOut::Write("long-line".to_string()))
1457 .add_output(CapturedOut::ShowCursor)
1458 .add_key(Key::ArrowLeft)
1460 .add_output(CapturedOut::MoveWithinLine(-1))
1461 .add_key(Key::ArrowLeft)
1462 .add_output(CapturedOut::MoveWithinLine(-1))
1463 .add_key(Key::ArrowLeft)
1464 .add_output(CapturedOut::MoveWithinLine(-1))
1465 .add_key(Key::ArrowLeft)
1466 .add_output(CapturedOut::MoveWithinLine(-1))
1467 .add_key(Key::ArrowUp)
1469 .add_output(CapturedOut::HideCursor)
1470 .add_output(CapturedOut::MoveWithinLine(-("long-line".len() as i16) + 4))
1471 .add_output(CapturedOut::Write("a".to_string()))
1472 .add_output(CapturedOut::Write(" ".to_string()))
1473 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1474 .add_output(CapturedOut::ShowCursor)
1475 .add_key(Key::ArrowUp)
1477 .add_key(Key::ArrowDown)
1479 .add_output(CapturedOut::HideCursor)
1480 .add_output(CapturedOut::MoveWithinLine(-("a".len() as i16)))
1481 .add_output(CapturedOut::Write("long-line".to_string()))
1482 .add_output(CapturedOut::ShowCursor)
1483 .add_key(Key::ArrowLeft)
1485 .add_output(CapturedOut::MoveWithinLine(-1))
1486 .add_key(Key::ArrowLeft)
1487 .add_output(CapturedOut::MoveWithinLine(-1))
1488 .add_key(Key::ArrowLeft)
1489 .add_output(CapturedOut::MoveWithinLine(-1))
1490 .add_key(Key::ArrowLeft)
1491 .add_output(CapturedOut::MoveWithinLine(-1))
1492 .add_key(Key::ArrowLeft)
1493 .add_output(CapturedOut::MoveWithinLine(-1))
1494 .add_key(Key::ArrowLeft)
1495 .add_output(CapturedOut::MoveWithinLine(-1))
1496 .add_key(Key::ArrowDown)
1498 .add_output(CapturedOut::HideCursor)
1499 .add_output(CapturedOut::MoveWithinLine(-("long-line".len() as i16) + 6))
1500 .add_output(CapturedOut::Write("zzzz".to_string()))
1501 .add_output(CapturedOut::Write(" ".to_string()))
1502 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1503 .add_output(CapturedOut::ShowCursor)
1504 .add_key(Key::ArrowDown)
1506 .add_output(CapturedOut::HideCursor)
1507 .add_output(CapturedOut::MoveWithinLine(-("zzzz".len() as i16)))
1508 .add_output(CapturedOut::Write(" ".to_string()))
1509 .add_output(CapturedOut::MoveWithinLine(-(" ".len() as i16)))
1510 .add_output(CapturedOut::ShowCursor)
1511 .add_key(Key::ArrowDown)
1513 .accept();
1515 }
1516
1517 #[test]
1518 fn test_read_line_interactive_history_navigate_and_edit() {
1519 ReadLineInteractiveTest::default()
1520 .set_prompt("? ")
1521 .add_output(CapturedOut::Write("? ".to_string()))
1522 .add_output(CapturedOut::SyncNow)
1523 .set_history(
1525 vec!["first".to_owned(), "second".to_owned(), "third".to_owned()],
1526 vec![
1527 "first".to_owned(),
1528 "second".to_owned(),
1529 "third".to_owned(),
1530 "sec ond".to_owned(),
1531 ],
1532 )
1533 .add_key(Key::ArrowUp)
1535 .add_output(CapturedOut::HideCursor)
1536 .add_output(CapturedOut::Write("third".to_string()))
1537 .add_output(CapturedOut::ShowCursor)
1538 .add_key(Key::ArrowUp)
1540 .add_output(CapturedOut::HideCursor)
1541 .add_output(CapturedOut::MoveWithinLine(-5))
1542 .add_output(CapturedOut::Write("second".to_string()))
1543 .add_output(CapturedOut::ShowCursor)
1544 .add_key(Key::ArrowLeft)
1546 .add_output(CapturedOut::MoveWithinLine(-1))
1547 .add_key(Key::ArrowLeft)
1549 .add_output(CapturedOut::MoveWithinLine(-1))
1550 .add_key(Key::ArrowLeft)
1552 .add_output(CapturedOut::MoveWithinLine(-1))
1553 .add_key_chars(" ")
1555 .add_output(CapturedOut::HideCursor)
1556 .add_output_bytes(" ")
1557 .add_output(CapturedOut::Write("ond".to_string()))
1558 .add_output(CapturedOut::MoveWithinLine(-3))
1559 .add_output(CapturedOut::ShowCursor)
1560 .set_line("sec ond")
1562 .accept();
1563 }
1564
1565 #[test]
1566 fn test_read_line_ignored_keys() {
1567 ReadLineInteractiveTest::default()
1568 .add_key_chars("not ")
1569 .add_output_bytes("not ")
1570 .add_key(Key::Escape)
1572 .add_key(Key::PageDown)
1573 .add_key(Key::PageUp)
1574 .add_key(Key::Tab)
1575 .add_key_chars("affected")
1577 .add_output_bytes("affected")
1578 .set_line("not affected")
1580 .accept();
1581 }
1582
1583 #[test]
1584 fn test_read_line_without_echo() {
1585 ReadLineInteractiveTest::default()
1586 .set_echo(false)
1587 .set_prompt("> ")
1588 .set_previous("pass1234")
1589 .add_output(CapturedOut::Write("> ********".to_string()))
1590 .add_output(CapturedOut::SyncNow)
1591 .add_key_chars("56")
1593 .add_output_bytes("**")
1594 .add_key(Key::ArrowLeft)
1596 .add_output(CapturedOut::MoveWithinLine(-1))
1597 .add_key(Key::ArrowLeft)
1599 .add_output(CapturedOut::MoveWithinLine(-1))
1600 .add_key(Key::Backspace)
1602 .add_output(CapturedOut::HideCursor)
1603 .add_output(CapturedOut::MoveWithinLine(-1))
1604 .add_output(CapturedOut::Write("**".to_string()))
1605 .add_output_bytes(" ")
1606 .add_output(CapturedOut::MoveWithinLine(-3))
1607 .add_output(CapturedOut::ShowCursor)
1608 .add_output(CapturedOut::HideCursor)
1610 .add_key_chars("7")
1611 .add_output(CapturedOut::Write("***".to_string()))
1612 .add_output(CapturedOut::MoveWithinLine(-2))
1613 .add_output(CapturedOut::ShowCursor)
1614 .set_line("pass123756")
1616 .accept();
1617
1618 ReadLineInteractiveTest::default()
1619 .set_echo(false)
1620 .set_prompt("> ")
1621 .set_previous("pass1234")
1622 .add_output(CapturedOut::Write("> ********".to_string()))
1623 .add_output(CapturedOut::SyncNow)
1624 .add_key(Key::ArrowLeft)
1626 .add_output(CapturedOut::MoveWithinLine(-1))
1627 .add_key(Key::ArrowLeft)
1629 .add_output(CapturedOut::MoveWithinLine(-1))
1630 .add_key(Key::Delete)
1632 .add_output(CapturedOut::HideCursor)
1633 .add_output(CapturedOut::Write("*".to_string()))
1634 .add_output_bytes(" ")
1635 .add_output(CapturedOut::MoveWithinLine(-2))
1636 .add_output(CapturedOut::ShowCursor)
1637 .set_line("pass124")
1639 .accept();
1640 }
1641
1642 #[test]
1643 fn test_read_line_secure_trivial_test() {
1644 let mut console = MockConsole::default();
1645 console.set_interactive(true);
1646 console.add_input_keys(&[Key::Char('1'), Key::Char('5'), Key::NewLine]);
1647 console.set_size_chars(CharsXY::new(15, 5));
1648 let line = block_on(read_line_secure(&mut console, "> ")).unwrap();
1649 assert_eq!("15", &line);
1650 assert_eq!(
1651 &[
1652 CapturedOut::Write("> ".to_string()),
1653 CapturedOut::SyncNow,
1654 CapturedOut::Write("*".to_string()),
1655 CapturedOut::Write("*".to_string()),
1656 CapturedOut::Print("".to_owned()),
1657 ],
1658 console.captured_out()
1659 );
1660 }
1661
1662 #[test]
1663 fn test_read_line_secure_unsupported_in_noninteractive_console() {
1664 let mut console = MockConsole::default();
1665 let err = block_on(read_line_secure(&mut console, "> ")).unwrap_err();
1666 assert!(format!("{}", err).contains("Cannot read secure"));
1667 }
1668}