1use std::io::{self, Read, Write};
2
3#[cfg(unix)]
8struct RawModeGuard {
9 original: libc::termios,
10}
11
12#[cfg(unix)]
13impl RawModeGuard {
14 fn enter() -> io::Result<Self> {
15 let mut original: libc::termios = unsafe { std::mem::zeroed() };
16 if unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut original) } != 0 {
17 return Err(io::Error::last_os_error());
18 }
19 let mut raw = original;
20 raw.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG);
21 raw.c_cc[libc::VMIN] = 1;
22 raw.c_cc[libc::VTIME] = 0;
23 if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &raw) } != 0 {
24 return Err(io::Error::last_os_error());
25 }
26 Ok(Self { original })
27 }
28}
29
30#[cfg(unix)]
31impl Drop for RawModeGuard {
32 fn drop(&mut self) {
33 unsafe {
34 libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.original);
35 }
36 }
37}
38
39#[cfg(windows)]
42mod win32 {
43 #[link(name = "kernel32")]
44 unsafe extern "system" {
45 pub fn GetStdHandle(nStdHandle: u32) -> isize;
46 pub fn GetConsoleMode(hConsoleHandle: isize, lpMode: *mut u32) -> i32;
47 pub fn SetConsoleMode(hConsoleHandle: isize, dwMode: u32) -> i32;
48 }
49
50 pub const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6; pub const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
52 pub const ENABLE_LINE_INPUT: u32 = 0x0002;
53 pub const ENABLE_ECHO_INPUT: u32 = 0x0004;
54 pub const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
55}
56
57#[cfg(windows)]
58struct RawModeGuard {
59 handle: isize,
60 original_mode: u32,
61}
62
63#[cfg(windows)]
64impl RawModeGuard {
65 fn enter() -> io::Result<Self> {
66 let handle = unsafe { win32::GetStdHandle(win32::STD_INPUT_HANDLE) };
67 if handle == -1 {
68 return Err(io::Error::last_os_error());
69 }
70 let mut original_mode: u32 = 0;
71 if unsafe { win32::GetConsoleMode(handle, &mut original_mode) } == 0 {
72 return Err(io::Error::last_os_error());
73 }
74 let new_mode = (original_mode
75 & !(win32::ENABLE_LINE_INPUT | win32::ENABLE_ECHO_INPUT | win32::ENABLE_PROCESSED_INPUT))
76 | win32::ENABLE_VIRTUAL_TERMINAL_INPUT;
77 if unsafe { win32::SetConsoleMode(handle, new_mode) } == 0 {
78 return Err(io::Error::last_os_error());
79 }
80 Ok(Self {
81 handle,
82 original_mode,
83 })
84 }
85}
86
87#[cfg(windows)]
88impl Drop for RawModeGuard {
89 fn drop(&mut self) {
90 unsafe {
91 win32::SetConsoleMode(self.handle, self.original_mode);
92 }
93 }
94}
95
96#[cfg(target_os = "wasi")]
99struct RawModeGuard;
100
101#[cfg(target_os = "wasi")]
102impl RawModeGuard {
103 fn enter() -> io::Result<Self> {
104 Ok(Self)
105 }
106}
107
108pub trait LineHandler {
122 fn handle_line(&self, line: &str) -> Result<LoopAction, String>;
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum LoopAction {
128 Continue,
130 Break,
132}
133
134impl<F> LineHandler for F
136where
137 F: Fn(&str) -> Result<LoopAction, String>,
138{
139 fn handle_line(&self, line: &str) -> Result<LoopAction, String> {
140 self(line)
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum KeyEvent {
150 Char(char),
151 Enter,
152 Backspace,
153 Delete,
154 Up,
155 Down,
156 Left,
157 Right,
158 Home,
159 End,
160 CtrlA,
161 CtrlE,
162 CtrlU,
163 CtrlK,
164 CtrlW,
165 CtrlD,
166 CtrlC,
167}
168
169pub trait KeyEventHandler {
170 fn on_key_event(&mut self, key: KeyEvent);
171}
172
173struct NoopKeyEventHandler;
174
175impl KeyEventHandler for NoopKeyEventHandler {
176 fn on_key_event(&mut self, _key: KeyEvent) {}
177}
178
179pub struct LineBuffer {
181 pub buffer: String,
182 pub cursor_pos: usize,
183}
184
185impl LineBuffer {
186 pub const fn new() -> Self {
188 Self {
189 buffer: String::new(),
190 cursor_pos: 0,
191 }
192 }
193
194 pub fn set_buffer(&mut self, text: String) {
195 self.buffer = text;
196 self.cursor_pos = self.buffer.len();
197 }
198
199 pub fn apply_key(&mut self, key: KeyEvent) {
200 match key {
201 KeyEvent::Char(ch) => {
202 self.buffer.insert(self.cursor_pos, ch);
203 self.cursor_pos += 1;
204 }
205 KeyEvent::Backspace => {
206 if self.cursor_pos > 0 {
207 self.cursor_pos -= 1;
208 self.buffer.remove(self.cursor_pos);
209 }
210 }
211 KeyEvent::Delete => {
212 if self.cursor_pos < self.buffer.len() {
213 self.buffer.remove(self.cursor_pos);
214 }
215 }
216 KeyEvent::Left => {
217 if self.cursor_pos > 0 {
218 self.cursor_pos -= 1;
219 }
220 }
221 KeyEvent::Right => {
222 if self.cursor_pos < self.buffer.len() {
223 self.cursor_pos += 1;
224 }
225 }
226 KeyEvent::Home | KeyEvent::CtrlA => {
227 self.cursor_pos = 0;
228 }
229 KeyEvent::End | KeyEvent::CtrlE => {
230 self.cursor_pos = self.buffer.len();
231 }
232 KeyEvent::CtrlU => {
233 self.buffer.clear();
234 self.cursor_pos = 0;
235 }
236 KeyEvent::CtrlK => {
237 self.buffer.truncate(self.cursor_pos);
238 }
239 KeyEvent::CtrlW => {
240 if self.cursor_pos > 0 {
241 let mut new_pos = self.cursor_pos;
242 while new_pos > 0 && self.buffer.as_bytes().get(new_pos - 1) == Some(&b' ') {
243 new_pos -= 1;
244 }
245 while new_pos > 0 && self.buffer.as_bytes().get(new_pos - 1) != Some(&b' ') {
246 new_pos -= 1;
247 }
248 self.buffer.drain(new_pos..self.cursor_pos);
249 self.cursor_pos = new_pos;
250 }
251 }
252 _ => {}
253 }
254 }
255}
256
257pub trait History {
262 fn push(&mut self, line: &str);
263 fn len(&self) -> usize;
264 fn get(&self, index: usize) -> Option<String>;
265 fn is_empty(&self) -> bool {
266 self.len() == 0
267 }
268}
269
270pub struct InMemoryHistory {
271 entries: Vec<String>,
272 max_len: usize,
273}
274
275impl InMemoryHistory {
276 pub fn new(max_len: usize) -> Self {
277 Self {
278 entries: Vec::new(),
279 max_len,
280 }
281 }
282}
283
284impl History for InMemoryHistory {
285 fn push(&mut self, line: &str) {
286 let trimmed = line.trim();
287 if trimmed.is_empty() {
288 return;
289 }
290 if self.entries.last().map(|s| s.as_str()) == Some(trimmed) {
291 return;
292 }
293 self.entries.push(trimmed.to_string());
294 if self.entries.len() > self.max_len {
295 self.entries.remove(0);
296 }
297 }
298
299 fn len(&self) -> usize {
300 self.entries.len()
301 }
302
303 fn get(&self, index: usize) -> Option<String> {
304 self.entries.get(index).cloned()
305 }
306}
307
308pub struct LineEditor<H: History = InMemoryHistory> {
324 line_buffer: LineBuffer,
325 history: H,
326 history_idx: usize,
327 saved_input: String,
328}
329
330impl LineEditor<InMemoryHistory> {
331 pub fn new(max_history: usize) -> Self {
333 Self::with_history(InMemoryHistory::new(max_history))
334 }
335}
336
337impl<H: History> LineEditor<H> {
338 pub fn with_history(history: H) -> Self {
339 let history_idx = history.len();
340 Self {
341 line_buffer: LineBuffer::new(),
342 history,
343 history_idx,
344 saved_input: String::new(),
345 }
346 }
347
348 pub fn buffer(&self) -> &str {
349 &self.line_buffer.buffer
350 }
351
352 pub fn cursor_pos(&self) -> usize {
353 self.line_buffer.cursor_pos
354 }
355
356 pub fn start_new_line(&mut self) {
358 self.line_buffer.buffer.clear();
359 self.line_buffer.cursor_pos = 0;
360 self.history_idx = self.history.len();
361 self.saved_input.clear();
362 }
363
364 pub fn input_char(&mut self, code: u32) -> Option<String> {
365 self.input_char_with_handler(code, &mut NoopKeyEventHandler)
366 }
367
368 pub fn input_char_with_handler<K: KeyEventHandler>(&mut self, code: u32, handler: &mut K) -> Option<String> {
369 let key = match code {
370 1 => KeyEvent::CtrlA,
372 3 => KeyEvent::CtrlC,
373 4 => KeyEvent::CtrlD,
374 5 => KeyEvent::CtrlE,
375 8 | 127 => KeyEvent::Backspace,
376 11 => KeyEvent::CtrlK,
377 13 | 10 => KeyEvent::Enter,
378 21 => KeyEvent::CtrlU,
379 23 => KeyEvent::CtrlW,
380 27 => return None, 1001 => KeyEvent::Up,
384 1002 => KeyEvent::Down,
385 1003 => KeyEvent::Right,
386 1004 => KeyEvent::Left,
387 1005 => KeyEvent::Home,
388 1006 => KeyEvent::End,
389 1007 => KeyEvent::Delete,
390
391 c if c >= 0x20 && c < 1000 => KeyEvent::Char(char::from_u32(c).unwrap_or(' ')),
393 _ => return None,
394 };
395
396 handler.on_key_event(key);
397
398 if key == KeyEvent::Enter {
399 let final_line = self.line_buffer.buffer.clone();
400 self.history.push(&final_line);
401 self.start_new_line();
402 return Some(final_line);
403 }
404
405 match key {
406 KeyEvent::Up => {
407 if !self.history.is_empty() && self.history_idx > 0 {
408 if self.history_idx == self.history.len() {
409 self.saved_input = self.line_buffer.buffer.clone();
410 }
411 self.history_idx -= 1;
412 if let Some(hist_line) = self.history.get(self.history_idx) {
413 self.line_buffer.set_buffer(hist_line);
414 }
415 }
416 }
417 KeyEvent::Down => {
418 if self.history_idx < self.history.len() {
419 self.history_idx += 1;
420 if self.history_idx == self.history.len() {
421 self.line_buffer.set_buffer(self.saved_input.clone());
422 } else if let Some(hist_line) = self.history.get(self.history_idx) {
423 self.line_buffer.set_buffer(hist_line);
424 }
425 }
426 }
427 _ => {
428 self.line_buffer.apply_key(key);
429 }
430 }
431
432 None
433 }
434
435 pub fn read_line(&mut self, prompt: &str, cancel_token: Option<wasibox_core::CancellationToken>) -> io::Result<Option<String>> {
439 let mut stdout = io::stdout();
440 write!(stdout, "{}", prompt)?;
441 stdout.flush()?;
442
443 let _guard = RawModeGuard::enter()?;
444
445 let mut reader = io::stdin();
446 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
447 }
448
449 pub fn read_line_with_stdin(&mut self, prompt: &str, cancel_token: Option<wasibox_core::CancellationToken>, mut reader: Box<dyn Read>) -> io::Result<Option<String>> {
451 let mut stdout = io::stdout();
452 write!(stdout, "{}", prompt)?;
453 stdout.flush()?;
454
455 let _guard = RawModeGuard::enter()?;
456
457 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
458 }
459
460 pub fn run_loop<P, L>(&mut self, prompt_fn: P, handler: &L, cancel_token: wasibox_core::CancellationToken) -> io::Result<()>
467 where
468 P: Fn() -> String,
469 L: LineHandler,
470 {
471 loop {
472 let prompt = prompt_fn();
473 match self.read_line(&prompt, Some(cancel_token.clone()))? {
474 None => break,
475 Some(line) => {
476 let trimmed = line.trim();
477 if trimmed.is_empty() {
478 continue;
479 }
480 match handler.handle_line(trimmed) {
481 Ok(LoopAction::Continue) => {}
482 Ok(LoopAction::Break) => break,
483 Err(e) => {
484 eprintln!("{}", e);
485 }
486 }
487 }
488 }
489 }
490 Ok(())
491 }
492
493 pub fn run_loop_with_stdin<P, L>(&mut self, prompt_fn: P, handler: &L, cancel_token: wasibox_core::CancellationToken, mut reader: Box<dyn Read>) -> io::Result<()>
495 where
496 P: Fn() -> String,
497 L: LineHandler,
498 {
499 loop {
500 let prompt = prompt_fn();
501 let _guard = RawModeGuard::enter()?;
502 match self.read_line_from(&mut reader, &mut io::stdout(), &prompt, Some(cancel_token.clone()))? {
503 None => break,
504 Some(line) => {
505 let trimmed = line.trim();
506 if trimmed.is_empty() {
507 continue;
508 }
509 match handler.handle_line(trimmed) {
510 Ok(LoopAction::Continue) => {}
511 Ok(LoopAction::Break) => break,
512 Err(e) => {
513 eprintln!("{}", e);
514 }
515 }
516 }
517 }
518 }
519 Ok(())
520 }
521
522 #[cfg(test)]
524 fn run_loop_from<R: Read, W: Write, L: LineHandler>(
525 &mut self,
526 reader: &mut R,
527 writer: &mut W,
528 prompt: &str,
529 handler: &L,
530 cancel_token: Option<wasibox_core::CancellationToken>,
531 ) -> io::Result<()> {
532 loop {
533 write!(writer, "{}", prompt)?;
534 writer.flush()?;
535 match self.read_line_from(reader, writer, prompt, cancel_token.clone())? {
536 None => break,
537 Some(line) => {
538 let trimmed = line.trim();
539 if trimmed.is_empty() {
540 continue;
541 }
542 match handler.handle_line(trimmed) {
543 Ok(LoopAction::Continue) => {}
544 Ok(LoopAction::Break) => break,
545 Err(e) => {
546 writeln!(writer, "Error: {}", e)?;
547 }
548 }
549 }
550 }
551 }
552 Ok(())
553 }
554
555 pub fn read_line_from<R: Read, W: Write>(
558 &mut self,
559 reader: &mut R,
560 writer: &mut W,
561 prompt: &str,
562 cancel_token: Option<wasibox_core::CancellationToken>,
563 ) -> io::Result<Option<String>> {
564 self.start_new_line();
565
566 loop {
567 let b = {
568 let mut buf = [0u8; 1];
569 reader.read_exact(&mut buf)?;
570 buf[0]
571 };
572
573 let code = match b {
574 4 => {
576 if self.buffer().is_empty() {
577 write!(writer, "\r\n")?;
578 writer.flush()?;
579 return Ok(None);
580 }
581 4
582 }
583 3 => {
585 if let Some(token) = &cancel_token {
586 token.cancel();
587 }
588 write!(writer, "^C\r\n")?;
589 writer.flush()?;
590 self.start_new_line(); return Ok(Some(String::new()));
592 }
593 27 => {
595 let seq1 = {
596 let mut buf = [0u8; 1];
597 reader.read_exact(&mut buf)?;
598 buf[0]
599 };
600 if seq1 == b'[' {
601 let seq2 = {
602 let mut buf = [0u8; 1];
603 reader.read_exact(&mut buf)?;
604 buf[0]
605 };
606 match seq2 {
607 b'A' => 1001, b'B' => 1002, b'C' => 1003, b'D' => 1004, b'H' => 1005, b'F' => 1006, b'3' => {
614 let seq3 = {
615 let mut buf = [0u8; 1];
616 reader.read_exact(&mut buf)?;
617 buf[0]
618 };
619 if seq3 == b'~' {
620 1007 } else {
622 continue;
623 }
624 }
625 _ => continue,
626 }
627 } else {
628 continue;
629 }
630 }
631 other => other as u32,
632 };
633
634 let old_pos = self.cursor_pos();
635 let old_len = self.buffer().len();
636
637 if let Some(completed_line) = self.input_char(code) {
638 write!(writer, "\r\n")?;
639 writer.flush()?;
640 return Ok(Some(completed_line));
641 }
642
643 if code >= 0x20 && code < 1000 && old_pos == old_len && self.cursor_pos() == self.buffer().len() {
645 write!(writer, "{}", char::from_u32(code).unwrap())?;
646 writer.flush()?;
647 } else if code == 1004 && old_pos > self.cursor_pos() && old_pos > 0 { write!(writer, "\x1b[D")?;
649 writer.flush()?;
650 } else if code == 1003 && old_pos < self.cursor_pos() && old_pos < old_len { write!(writer, "\x1b[C")?;
652 writer.flush()?;
653 } else {
654 Self::redraw_line(writer, prompt, self.buffer(), self.cursor_pos())?;
655 }
656 }
657 }
658
659 fn redraw_line<W: Write>(
661 writer: &mut W,
662 prompt: &str,
663 line: &str,
664 cursor_pos: usize,
665 ) -> io::Result<()> {
666 write!(writer, "\r\x1b[K{}{}", prompt, line)?;
667 let total_len = prompt.len() + line.len();
668 let target = prompt.len() + cursor_pos;
669 if target < total_len {
670 write!(writer, "\x1b[{}D", total_len - target)?;
671 }
672 writer.flush()
673 }
674}
675
676#[cfg(test)]
681mod tests {
682 use super::*;
683 use std::io::Cursor;
684
685 fn keys(parts: &[&[u8]]) -> Cursor<Vec<u8>> {
687 let mut buf = Vec::new();
688 for part in parts {
689 buf.extend_from_slice(part);
690 }
691 Cursor::new(buf)
692 }
693
694 const UP: &[u8] = b"\x1b[A";
695 const DOWN: &[u8] = b"\x1b[B";
696 const ENTER: &[u8] = b"\r";
697
698 #[test]
699 fn test_line_editor_basic() {
700 let mut editor = LineEditor::new(0);
701
702 assert!(editor.input_char('a' as u32).is_none());
703 assert!(editor.input_char('b' as u32).is_none());
704 assert_eq!(editor.buffer(), "ab");
705 assert_eq!(editor.cursor_pos(), 2);
706
707 assert!(editor.input_char(1004).is_none()); assert_eq!(editor.cursor_pos(), 1);
709
710 assert!(editor.input_char('c' as u32).is_none());
711 assert_eq!(editor.buffer(), "acb");
712 assert_eq!(editor.cursor_pos(), 2);
713
714 assert!(editor.input_char(127).is_none()); assert_eq!(editor.buffer(), "ab");
716 assert_eq!(editor.cursor_pos(), 1);
717
718 let result = editor.input_char(13); assert_eq!(result, Some("ab".to_string()));
720 }
721
722 #[test]
723 fn test_line_editor_history() {
724 let mut editor = LineEditor::new(10);
725 editor.input_char('f' as u32);
726 editor.input_char('i' as u32);
727 editor.input_char('r' as u32);
728 editor.input_char('s' as u32);
729 editor.input_char('t' as u32);
730 editor.input_char(13); editor.input_char('s' as u32);
733 editor.input_char('e' as u32);
734 editor.input_char('c' as u32);
735 editor.input_char('o' as u32);
736 editor.input_char('n' as u32);
737 editor.input_char('d' as u32);
738 editor.input_char(13); editor.input_char(1001); assert_eq!(editor.buffer(), "second");
742
743 editor.input_char(1001); assert_eq!(editor.buffer(), "first");
745
746 editor.input_char(1002); assert_eq!(editor.buffer(), "second");
748
749 editor.input_char(1002); assert_eq!(editor.buffer(), ""); }
752
753 #[test]
754 fn test_simple_input() {
755 let mut reader = LineEditor::new(100);
756 let mut input = keys(&[b"hello", ENTER]);
757 let mut out = Vec::new();
758 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
759 assert_eq!(result, Some("hello".to_string()));
760 }
761
762 #[test]
763 fn test_eof_on_empty() {
764 let mut reader = LineEditor::new(100);
765 let mut input = Cursor::new(vec![4u8]); let mut out = Vec::new();
767 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
768 assert_eq!(result, None);
769 }
770
771 #[test]
772 fn test_history_up_arrow() {
773 let mut reader = LineEditor::new(100);
774 let mut out = Vec::new();
775
776 let mut input = keys(&[b"echo hello", ENTER]);
778 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
779
780 let mut input = keys(&[UP, ENTER]);
782 out.clear();
783 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
784 assert_eq!(result, Some("echo hello".to_string()));
785 }
786
787 #[test]
788 fn test_history_up_down_arrow() {
789 let mut reader = LineEditor::new(100);
790 let mut out = Vec::new();
791
792 let mut input = keys(&[b"first", ENTER]);
794 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
795 let mut input = keys(&[b"second", ENTER]);
796 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
797
798 let mut input = keys(&[UP, UP, DOWN, ENTER]);
800 out.clear();
801 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
802 assert_eq!(result, Some("second".to_string()));
803 }
804
805 #[test]
806 fn test_history_down_restores_current_input() {
807 let mut reader = LineEditor::new(100);
808 let mut out = Vec::new();
809
810 let mut input = keys(&[b"old", ENTER]);
812 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
813
814 let mut input = keys(&[b"new", UP, DOWN, ENTER]);
816 out.clear();
817 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
818 assert_eq!(result, Some("new".to_string()));
819 }
820
821 #[test]
822 fn test_history_dedup() {
823 let mut reader = LineEditor::new(100);
824 let mut out = Vec::new();
825
826 let mut input = keys(&[b"dup", ENTER]);
828 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
829 let mut input = keys(&[b"dup", ENTER]);
830 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
831
832 let mut input = keys(&[UP, UP, ENTER]);
835 out.clear();
836 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
837 assert_eq!(result, Some("dup".to_string()));
838 }
839
840 #[test]
841 fn test_history_max_size() {
842 let mut reader = LineEditor::new(3);
843 let mut out = Vec::new();
844
845 for cmd in &["aaa", "bbb", "ccc", "ddd"] {
846 let mut input = keys(&[cmd.as_bytes(), ENTER]);
847 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
848 }
849
850 let mut input = keys(&[UP, UP, UP, ENTER]);
852 out.clear();
853 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
854 assert_eq!(result, Some("bbb".to_string()));
855 }
856
857 #[test]
858 fn test_backspace() {
859 let mut reader = LineEditor::new(100);
860 let mut input = keys(&[b"helloo", &[127], ENTER]);
861 let mut out = Vec::new();
862 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
863 assert_eq!(result, Some("hello".to_string()));
864 }
865
866 #[test]
867 fn test_ctrl_u_clears_line() {
868 let mut reader = LineEditor::new(100);
869 let mut input = keys(&[b"garbage", &[21], b"clean", ENTER]); let mut out = Vec::new();
871 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
872 assert_eq!(result, Some("clean".to_string()));
873 }
874
875 #[test]
876 fn test_empty_line_not_in_history() {
877 let mut reader = LineEditor::new(100);
878 let mut out = Vec::new();
879
880 let mut input = keys(&[b"real", ENTER]);
882 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
883
884 let mut input = keys(&[ENTER]);
886 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
887
888 let mut input = keys(&[UP, ENTER]);
890 out.clear();
891 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
892 assert_eq!(result, Some("real".to_string()));
893 }
894
895 #[test]
898 fn test_run_loop_with_handler() {
899 use std::sync::{Arc, Mutex};
900
901 let executed = Arc::new(Mutex::new(Vec::new()));
902 let exec_clone = Arc::clone(&executed);
903
904 let handler = move |line: &str| -> Result<LoopAction, String> {
905 exec_clone.lock().unwrap().push(line.to_string());
906 Ok(LoopAction::Continue)
907 };
908
909 let mut reader = LineEditor::new(100);
910 let mut input = keys(&[b"echo hello", ENTER, b"ls", ENTER, &[4]]);
912 let mut out = Vec::new();
913 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
914
915 let cmds = executed.lock().unwrap();
916 assert_eq!(cmds.len(), 2);
917 assert_eq!(cmds[0], "echo hello");
918 assert_eq!(cmds[1], "ls");
919 }
920
921 #[test]
922 fn test_run_loop_break_on_exit() {
923 let handler = |line: &str| -> Result<LoopAction, String> {
924 if line == "exit" {
925 Ok(LoopAction::Break)
926 } else {
927 Ok(LoopAction::Continue)
928 }
929 };
930
931 let mut reader = LineEditor::new(100);
932 let mut input = keys(&[b"cmd1", ENTER, b"exit", ENTER, b"cmd2", ENTER]);
933 let mut out = Vec::new();
934 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
935 }
937
938 #[test]
939 fn test_run_loop_error_continues() {
940 use std::sync::{Arc, Mutex};
941
942 let count = Arc::new(Mutex::new(0u32));
943 let count_clone = Arc::clone(&count);
944
945 let handler = move |line: &str| -> Result<LoopAction, String> {
946 *count_clone.lock().unwrap() += 1;
947 if line == "fail" {
948 Err("simulated error".to_string())
949 } else {
950 Ok(LoopAction::Continue)
951 }
952 };
953
954 let mut reader = LineEditor::new(100);
955 let mut input = keys(&[b"ok", ENTER, b"fail", ENTER, b"ok2", ENTER, &[4]]);
956 let mut out = Vec::new();
957 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
958
959 assert_eq!(*count.lock().unwrap(), 3);
961 }
962
963 #[test]
964 fn test_run_loop_with_history_navigation() {
965 use std::sync::{Arc, Mutex};
966
967 let executed = Arc::new(Mutex::new(Vec::new()));
968 let exec_clone = Arc::clone(&executed);
969
970 let handler = move |line: &str| -> Result<LoopAction, String> {
971 exec_clone.lock().unwrap().push(line.to_string());
972 Ok(LoopAction::Continue)
973 };
974
975 let mut reader = LineEditor::new(100);
976 let mut input = keys(&[
978 b"echo hello", ENTER,
979 UP, ENTER, &[4], ]);
982 let mut out = Vec::new();
983 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
984
985 let cmds = executed.lock().unwrap();
986 assert_eq!(cmds.len(), 2);
987 assert_eq!(cmds[0], "echo hello");
988 assert_eq!(cmds[1], "echo hello"); }
990
991 #[test]
992 fn test_run_loop_with_handle_parallel() {
993 use std::sync::{Arc, Mutex};
994 use crate::{CommandRegistry, handle_parallel, ArcVecWriter};
995
996 let registry = Arc::new(CommandRegistry::with_builtins());
997 let output = Arc::new(Mutex::new(Vec::<u8>::new()));
998
999 let reg = Arc::clone(®istry);
1000 let out_ref = Arc::clone(&output);
1001
1002 let handler = move |line: &str| -> Result<LoopAction, String> {
1003 if line == "exit" {
1004 return Ok(LoopAction::Break);
1005 }
1006 let results = handle_parallel(
1007 vec![line.to_string()],
1008 Box::new(std::io::empty()),
1009 Box::new(ArcVecWriter { inner: Arc::clone(&out_ref) }),
1010 Arc::clone(®),
1011 wasibox_core::CancellationToken::new(),
1012 );
1013 for res in results {
1014 res?;
1015 }
1016 Ok(LoopAction::Continue)
1017 };
1018
1019 let mut reader = LineEditor::new(100);
1020 let mut input = keys(&[
1022 b"echo hello", ENTER,
1023 UP, ENTER, b"exit", ENTER,
1025 ]);
1026 let mut term_out = Vec::new();
1027 reader.run_loop_from(&mut input, &mut term_out, "$ ", &handler, None).unwrap();
1028
1029 let buf = output.lock().unwrap();
1030 let result = String::from_utf8_lossy(&buf);
1031 let lines: Vec<&str> = result.trim().lines().collect();
1032 assert_eq!(lines.len(), 2);
1033 assert_eq!(lines[0], "hello");
1034 assert_eq!(lines[1], "hello"); }
1036
1037 #[test]
1038 fn test_key_event_handler() {
1039 struct MockHandler {
1040 events: Vec<KeyEvent>,
1041 }
1042 impl KeyEventHandler for MockHandler {
1043 fn on_key_event(&mut self, key: KeyEvent) {
1044 self.events.push(key);
1045 }
1046 }
1047
1048 let mut editor = LineEditor::new(10);
1049 let mut handler = MockHandler { events: Vec::new() };
1050
1051 editor.input_char_with_handler('a' as u32, &mut handler);
1052 editor.input_char_with_handler('b' as u32, &mut handler);
1053 editor.input_char_with_handler(13, &mut handler);
1054
1055 assert_eq!(handler.events, vec![
1056 KeyEvent::Char('a'),
1057 KeyEvent::Char('b'),
1058 KeyEvent::Enter,
1059 ]);
1060 }
1061}