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 Esc,
168}
169
170pub trait KeyEventHandler {
171 fn on_key_event(&mut self, key: KeyEvent);
172}
173
174struct NoopKeyEventHandler;
175
176impl KeyEventHandler for NoopKeyEventHandler {
177 fn on_key_event(&mut self, _key: KeyEvent) {}
178}
179
180pub struct LineBuffer {
182 pub buffer: String,
183 pub cursor_pos: usize,
184}
185
186impl LineBuffer {
187 pub const fn new() -> Self {
189 Self {
190 buffer: String::new(),
191 cursor_pos: 0,
192 }
193 }
194
195 pub fn set_buffer(&mut self, text: String) {
196 self.buffer = text;
197 self.cursor_pos = self.buffer.len();
198 }
199
200 pub fn apply_key(&mut self, key: KeyEvent) {
201 match key {
202 KeyEvent::Char(ch) => {
203 self.buffer.insert(self.cursor_pos, ch);
204 self.cursor_pos += 1;
205 }
206 KeyEvent::Backspace => {
207 if self.cursor_pos > 0 {
208 self.cursor_pos -= 1;
209 self.buffer.remove(self.cursor_pos);
210 }
211 }
212 KeyEvent::Delete => {
213 if self.cursor_pos < self.buffer.len() {
214 self.buffer.remove(self.cursor_pos);
215 }
216 }
217 KeyEvent::Left => {
218 if self.cursor_pos > 0 {
219 self.cursor_pos -= 1;
220 }
221 }
222 KeyEvent::Right => {
223 if self.cursor_pos < self.buffer.len() {
224 self.cursor_pos += 1;
225 }
226 }
227 KeyEvent::Home | KeyEvent::CtrlA => {
228 self.cursor_pos = 0;
229 }
230 KeyEvent::End | KeyEvent::CtrlE => {
231 self.cursor_pos = self.buffer.len();
232 }
233 KeyEvent::CtrlU => {
234 self.buffer.clear();
235 self.cursor_pos = 0;
236 }
237 KeyEvent::CtrlK => {
238 self.buffer.truncate(self.cursor_pos);
239 }
240 KeyEvent::CtrlW => {
241 if self.cursor_pos > 0 {
242 let mut new_pos = self.cursor_pos;
243 while new_pos > 0 && self.buffer.as_bytes().get(new_pos - 1) == Some(&b' ') {
244 new_pos -= 1;
245 }
246 while new_pos > 0 && self.buffer.as_bytes().get(new_pos - 1) != Some(&b' ') {
247 new_pos -= 1;
248 }
249 self.buffer.drain(new_pos..self.cursor_pos);
250 self.cursor_pos = new_pos;
251 }
252 }
253 KeyEvent::Esc => {}
254 _ => {}
255 }
256 }
257}
258
259pub trait History {
264 fn push(&mut self, line: &str);
265 fn len(&self) -> usize;
266 fn get(&self, index: usize) -> Option<String>;
267 fn is_empty(&self) -> bool {
268 self.len() == 0
269 }
270}
271
272pub struct InMemoryHistory {
273 entries: Vec<String>,
274 max_len: usize,
275}
276
277impl InMemoryHistory {
278 pub const fn new(max_len: usize) -> Self {
279 Self {
280 entries: Vec::new(),
281 max_len,
282 }
283 }
284}
285
286impl History for InMemoryHistory {
287 fn push(&mut self, line: &str) {
288 let trimmed = line.trim();
289 if trimmed.is_empty() {
290 return;
291 }
292 if self.entries.last().map(|s| s.as_str()) == Some(trimmed) {
293 return;
294 }
295 self.entries.push(trimmed.to_string());
296 if self.entries.len() > self.max_len {
297 self.entries.remove(0);
298 }
299 }
300
301 fn len(&self) -> usize {
302 self.entries.len()
303 }
304
305 fn get(&self, index: usize) -> Option<String> {
306 self.entries.get(index).cloned()
307 }
308}
309
310pub struct LineEditor<H: History = InMemoryHistory> {
326 line_buffer: LineBuffer,
327 history: H,
328 history_idx: usize,
329 saved_input: String,
330 esc_buf: smallvec::SmallVec<[u8; 4]>,
331}
332
333impl LineEditor<InMemoryHistory> {
334 pub const fn new(max_history: usize) -> Self {
336 Self::with_history_and_len(InMemoryHistory::new(max_history), 0)
337 }
338}
339
340impl<H: History> LineEditor<H> {
341 pub fn with_history(history: H) -> Self {
342 let history_idx = history.len();
343 Self {
344 line_buffer: LineBuffer::new(),
345 history,
346 history_idx,
347 saved_input: String::new(),
348 esc_buf: smallvec::SmallVec::new_const(),
349 }
350 }
351
352 pub const fn with_history_and_len(history: H, len: usize) -> Self {
353 Self {
354 line_buffer: LineBuffer::new(),
355 history,
356 history_idx: len,
357 saved_input: String::new(),
358 esc_buf: smallvec::SmallVec::new_const(),
359 }
360 }
361
362 pub fn buffer(&self) -> &str {
363 &self.line_buffer.buffer
364 }
365
366 pub fn cursor_pos(&self) -> usize {
367 self.line_buffer.cursor_pos
368 }
369
370 pub fn start_new_line(&mut self) {
372 self.line_buffer.buffer.clear();
373 self.line_buffer.cursor_pos = 0;
374 self.history_idx = self.history.len();
375 self.saved_input.clear();
376 self.esc_buf.clear();
377 }
378
379 pub fn input_char(&mut self, code: u32) -> Option<String> {
380 self.input_char_with_handler(code, &mut NoopKeyEventHandler)
381 }
382
383 pub fn input_char_with_handler<K: KeyEventHandler>(&mut self, code: u32, handler: &mut K) -> Option<String> {
384 if code == 27 {
386 self.esc_buf.clear();
387 self.esc_buf.push(27);
388 return None;
389 }
390
391 if !self.esc_buf.is_empty() {
392 self.esc_buf.push(code as u8);
393 let seq = self.esc_buf.as_slice();
394
395 let key = match seq {
396 [27, b'[', b'A'] => Some(KeyEvent::Up),
397 [27, b'[', b'B'] => Some(KeyEvent::Down),
398 [27, b'[', b'C'] => Some(KeyEvent::Right),
399 [27, b'[', b'D'] => Some(KeyEvent::Left),
400 [27, b'[', b'H'] => Some(KeyEvent::Home),
401 [27, b'[', b'F'] => Some(KeyEvent::End),
402 [27, b'[', b'3', b'~'] => Some(KeyEvent::Delete),
403 _ => {
404 if seq.len() >= 4 || (seq.len() == 2 && seq[1] != b'[') {
406 self.esc_buf.clear();
408 None
409 } else {
410 return None;
412 }
413 }
414 };
415
416 if let Some(k) = key {
417 self.esc_buf.clear();
418 return self.handle_key_event(k, handler);
419 }
420 }
422
423 let key = match code {
425 1 => KeyEvent::CtrlA,
427 3 => KeyEvent::CtrlC,
428 4 => KeyEvent::CtrlD,
429 5 => KeyEvent::CtrlE,
430 8 | 127 => KeyEvent::Backspace,
431 11 => KeyEvent::CtrlK,
432 13 | 10 => KeyEvent::Enter,
433 21 => KeyEvent::CtrlU,
434 23 => KeyEvent::CtrlW,
435 27 => KeyEvent::Esc,
436
437 1001 => KeyEvent::Up,
439 1002 => KeyEvent::Down,
440 1003 => KeyEvent::Right,
441 1004 => KeyEvent::Left,
442 1005 => KeyEvent::Home,
443 1006 => KeyEvent::End,
444 1007 => KeyEvent::Delete,
445
446 c if c >= 0x20 && c < 1000 => KeyEvent::Char(char::from_u32(c).unwrap_or(' ')),
448 _ => return None,
449 };
450
451 self.handle_key_event(key, handler)
452 }
453
454 fn handle_key_event<K: KeyEventHandler>(&mut self, key: KeyEvent, handler: &mut K) -> Option<String> {
455 handler.on_key_event(key);
456
457 if key == KeyEvent::Enter {
458 let final_line = self.line_buffer.buffer.clone();
459 self.history.push(&final_line);
460 self.start_new_line();
461 return Some(final_line);
462 }
463
464 match key {
465 KeyEvent::Up => {
466 if !self.history.is_empty() && self.history_idx > 0 {
467 if self.history_idx == self.history.len() {
468 self.saved_input = self.line_buffer.buffer.clone();
469 }
470 self.history_idx -= 1;
471 if let Some(hist_line) = self.history.get(self.history_idx) {
472 self.line_buffer.set_buffer(hist_line);
473 }
474 }
475 }
476 KeyEvent::Down => {
477 if self.history_idx < self.history.len() {
478 self.history_idx += 1;
479 if self.history_idx == self.history.len() {
480 self.line_buffer.set_buffer(self.saved_input.clone());
481 } else if let Some(hist_line) = self.history.get(self.history_idx) {
482 self.line_buffer.set_buffer(hist_line);
483 }
484 }
485 }
486 _ => {
487 self.line_buffer.apply_key(key);
488 }
489 }
490
491 None
492 }
493
494 pub fn read_line(&mut self, prompt: &str, cancel_token: Option<wasibox_core::CancellationToken>) -> io::Result<Option<String>> {
498 let mut stdout = io::stdout();
499 write!(stdout, "{}", prompt)?;
500 stdout.flush()?;
501
502 let _guard = RawModeGuard::enter()?;
503
504 let mut reader = io::stdin();
505 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
506 }
507
508 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>> {
510 let mut stdout = io::stdout();
511 write!(stdout, "{}", prompt)?;
512 stdout.flush()?;
513
514 let _guard = RawModeGuard::enter()?;
515
516 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
517 }
518
519 pub fn run_loop<P, L>(&mut self, prompt_fn: P, handler: &L, cancel_token: wasibox_core::CancellationToken) -> io::Result<()>
526 where
527 P: Fn() -> String,
528 L: LineHandler,
529 {
530 loop {
531 let prompt = prompt_fn();
532 match self.read_line(&prompt, Some(cancel_token.clone()))? {
533 None => break,
534 Some(line) => {
535 let trimmed = line.trim();
536 if trimmed.is_empty() {
537 continue;
538 }
539 match handler.handle_line(trimmed) {
540 Ok(LoopAction::Continue) => {}
541 Ok(LoopAction::Break) => break,
542 Err(e) => {
543 eprintln!("{}", e);
544 }
545 }
546 }
547 }
548 }
549 Ok(())
550 }
551
552 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<()>
554 where
555 P: Fn() -> String,
556 L: LineHandler,
557 {
558 loop {
559 let prompt = prompt_fn();
560 let _guard = RawModeGuard::enter()?;
561 match self.read_line_from(&mut reader, &mut io::stdout(), &prompt, Some(cancel_token.clone()))? {
562 None => break,
563 Some(line) => {
564 let trimmed = line.trim();
565 if trimmed.is_empty() {
566 continue;
567 }
568 match handler.handle_line(trimmed) {
569 Ok(LoopAction::Continue) => {}
570 Ok(LoopAction::Break) => break,
571 Err(e) => {
572 eprintln!("{}", e);
573 }
574 }
575 }
576 }
577 }
578 Ok(())
579 }
580
581 #[cfg(test)]
583 fn run_loop_from<R: Read, W: Write, L: LineHandler>(
584 &mut self,
585 reader: &mut R,
586 writer: &mut W,
587 prompt: &str,
588 handler: &L,
589 cancel_token: Option<wasibox_core::CancellationToken>,
590 ) -> io::Result<()> {
591 loop {
592 write!(writer, "{}", prompt)?;
593 writer.flush()?;
594 match self.read_line_from(reader, writer, prompt, cancel_token.clone())? {
595 None => break,
596 Some(line) => {
597 let trimmed = line.trim();
598 if trimmed.is_empty() {
599 continue;
600 }
601 match handler.handle_line(trimmed) {
602 Ok(LoopAction::Continue) => {}
603 Ok(LoopAction::Break) => break,
604 Err(e) => {
605 writeln!(writer, "Error: {}", e)?;
606 }
607 }
608 }
609 }
610 }
611 Ok(())
612 }
613
614 pub fn read_line_from<R: Read, W: Write>(
617 &mut self,
618 reader: &mut R,
619 writer: &mut W,
620 prompt: &str,
621 cancel_token: Option<wasibox_core::CancellationToken>,
622 ) -> io::Result<Option<String>> {
623 self.start_new_line();
624
625 loop {
626 let b = {
627 let mut buf = [0u8; 1];
628 reader.read_exact(&mut buf)?;
629 buf[0]
630 };
631
632 let code = match b {
633 4 => {
635 if self.buffer().is_empty() {
636 write!(writer, "\r\n")?;
637 writer.flush()?;
638 return Ok(None);
639 }
640 4
641 }
642 3 => {
644 if let Some(token) = &cancel_token {
645 token.cancel();
646 }
647 write!(writer, "^C\r\n")?;
648 writer.flush()?;
649 self.start_new_line(); return Ok(Some(String::new()));
651 }
652 27 => {
654 let seq1 = {
655 let mut buf = [0u8; 1];
656 reader.read_exact(&mut buf)?;
657 buf[0]
658 };
659 if seq1 == b'[' {
660 let seq2 = {
661 let mut buf = [0u8; 1];
662 reader.read_exact(&mut buf)?;
663 buf[0]
664 };
665 match seq2 {
666 b'A' => 1001, b'B' => 1002, b'C' => 1003, b'D' => 1004, b'H' => 1005, b'F' => 1006, b'3' => {
673 let seq3 = {
674 let mut buf = [0u8; 1];
675 reader.read_exact(&mut buf)?;
676 buf[0]
677 };
678 if seq3 == b'~' {
679 1007 } else {
681 continue;
682 }
683 }
684 _ => continue,
685 }
686 } else {
687 continue;
688 }
689 }
690 other => other as u32,
691 };
692
693 let old_pos = self.cursor_pos();
694 let old_len = self.buffer().len();
695
696 if let Some(completed_line) = self.input_char(code) {
697 write!(writer, "\r\n")?;
698 writer.flush()?;
699 return Ok(Some(completed_line));
700 }
701
702 if code >= 0x20 && code < 1000 && old_pos == old_len && self.cursor_pos() == self.buffer().len() {
704 write!(writer, "{}", char::from_u32(code).unwrap())?;
705 writer.flush()?;
706 } else if code == 1004 && old_pos > self.cursor_pos() && old_pos > 0 { write!(writer, "\x1b[D")?;
708 writer.flush()?;
709 } else if code == 1003 && old_pos < self.cursor_pos() && old_pos < old_len { write!(writer, "\x1b[C")?;
711 writer.flush()?;
712 } else {
713 Self::redraw_line(writer, prompt, self.buffer(), self.cursor_pos())?;
714 }
715 }
716 }
717
718 fn redraw_line<W: Write>(
720 writer: &mut W,
721 prompt: &str,
722 line: &str,
723 cursor_pos: usize,
724 ) -> io::Result<()> {
725 write!(writer, "\r\x1b[K{}{}", prompt, line)?;
726 let total_len = prompt.len() + line.len();
727 let target = prompt.len() + cursor_pos;
728 if target < total_len {
729 write!(writer, "\x1b[{}D", total_len - target)?;
730 }
731 writer.flush()
732 }
733}
734
735#[cfg(test)]
740mod tests {
741 use super::*;
742 use std::io::Cursor;
743
744 fn keys(parts: &[&[u8]]) -> Cursor<Vec<u8>> {
746 let mut buf = Vec::new();
747 for part in parts {
748 buf.extend_from_slice(part);
749 }
750 Cursor::new(buf)
751 }
752
753 const UP: &[u8] = b"\x1b[A";
754 const DOWN: &[u8] = b"\x1b[B";
755 const ENTER: &[u8] = b"\r";
756
757 #[test]
758 fn test_line_editor_basic() {
759 let mut editor = LineEditor::new(0);
760
761 assert!(editor.input_char('a' as u32).is_none());
762 assert!(editor.input_char('b' as u32).is_none());
763 assert_eq!(editor.buffer(), "ab");
764 assert_eq!(editor.cursor_pos(), 2);
765
766 assert!(editor.input_char(1004).is_none()); assert_eq!(editor.cursor_pos(), 1);
768
769 assert!(editor.input_char('c' as u32).is_none());
770 assert_eq!(editor.buffer(), "acb");
771 assert_eq!(editor.cursor_pos(), 2);
772
773 assert!(editor.input_char(127).is_none()); assert_eq!(editor.buffer(), "ab");
775 assert_eq!(editor.cursor_pos(), 1);
776
777 let result = editor.input_char(13); assert_eq!(result, Some("ab".to_string()));
779 }
780
781 #[test]
782 fn test_line_editor_history() {
783 let mut editor = LineEditor::new(10);
784 editor.input_char('f' as u32);
785 editor.input_char('i' as u32);
786 editor.input_char('r' as u32);
787 editor.input_char('s' as u32);
788 editor.input_char('t' as u32);
789 editor.input_char(13); editor.input_char('s' as u32);
792 editor.input_char('e' as u32);
793 editor.input_char('c' as u32);
794 editor.input_char('o' as u32);
795 editor.input_char('n' as u32);
796 editor.input_char('d' as u32);
797 editor.input_char(13); editor.input_char(1001); assert_eq!(editor.buffer(), "second");
801
802 editor.input_char(1001); assert_eq!(editor.buffer(), "first");
804
805 editor.input_char(1002); assert_eq!(editor.buffer(), "second");
807
808 editor.input_char(1002); assert_eq!(editor.buffer(), ""); }
811
812 #[test]
813 fn test_simple_input() {
814 let mut reader = LineEditor::new(100);
815 let mut input = keys(&[b"hello", ENTER]);
816 let mut out = Vec::new();
817 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
818 assert_eq!(result, Some("hello".to_string()));
819 }
820
821 #[test]
822 fn test_eof_on_empty() {
823 let mut reader = LineEditor::new(100);
824 let mut input = Cursor::new(vec![4u8]); let mut out = Vec::new();
826 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
827 assert_eq!(result, None);
828 }
829
830 #[test]
831 fn test_history_up_arrow() {
832 let mut reader = LineEditor::new(100);
833 let mut out = Vec::new();
834
835 let mut input = keys(&[b"echo hello", ENTER]);
837 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
838
839 let mut input = keys(&[UP, ENTER]);
841 out.clear();
842 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
843 assert_eq!(result, Some("echo hello".to_string()));
844 }
845
846 #[test]
847 fn test_history_up_down_arrow() {
848 let mut reader = LineEditor::new(100);
849 let mut out = Vec::new();
850
851 let mut input = keys(&[b"first", ENTER]);
853 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
854 let mut input = keys(&[b"second", ENTER]);
855 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
856
857 let mut input = keys(&[UP, UP, DOWN, ENTER]);
859 out.clear();
860 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
861 assert_eq!(result, Some("second".to_string()));
862 }
863
864 #[test]
865 fn test_input_char_sequence() {
866 let mut editor = LineEditor::new(10);
867 editor.input_char('f' as u32);
868 editor.input_char('i' as u32);
869 editor.input_char('r' as u32);
870 editor.input_char('s' as u32);
871 editor.input_char('t' as u32);
872 editor.input_char(13); editor.input_char('s' as u32);
875 editor.input_char('e' as u32);
876 editor.input_char('c' as u32);
877 editor.input_char('o' as u32);
878 editor.input_char('n' as u32);
879 editor.input_char('d' as u32);
880 editor.input_char(13); editor.input_char(1001); editor.input_char(1001); assert_eq!(editor.buffer(), "first");
888
889 editor.input_char(27); editor.input_char(91); editor.input_char(66); assert_eq!(editor.buffer(), "second");
894 }
895
896 #[test]
897 fn test_history_down_restores_current_input() {
898 let mut reader = LineEditor::new(100);
899 let mut out = Vec::new();
900
901 let mut input = keys(&[b"old", ENTER]);
903 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
904
905 let mut input = keys(&[b"new", UP, DOWN, ENTER]);
907 out.clear();
908 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
909 assert_eq!(result, Some("new".to_string()));
910 }
911
912 #[test]
913 fn test_history_dedup() {
914 let mut reader = LineEditor::new(100);
915 let mut out = Vec::new();
916
917 let mut input = keys(&[b"dup", ENTER]);
919 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
920 let mut input = keys(&[b"dup", ENTER]);
921 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
922
923 let mut input = keys(&[UP, UP, ENTER]);
926 out.clear();
927 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
928 assert_eq!(result, Some("dup".to_string()));
929 }
930
931 #[test]
932 fn test_history_max_size() {
933 let mut reader = LineEditor::new(3);
934 let mut out = Vec::new();
935
936 for cmd in &["aaa", "bbb", "ccc", "ddd"] {
937 let mut input = keys(&[cmd.as_bytes(), ENTER]);
938 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
939 }
940
941 let mut input = keys(&[UP, UP, UP, ENTER]);
943 out.clear();
944 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
945 assert_eq!(result, Some("bbb".to_string()));
946 }
947
948 #[test]
949 fn test_backspace() {
950 let mut reader = LineEditor::new(100);
951 let mut input = keys(&[b"helloo", &[127], ENTER]);
952 let mut out = Vec::new();
953 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
954 assert_eq!(result, Some("hello".to_string()));
955 }
956
957 #[test]
958 fn test_ctrl_u_clears_line() {
959 let mut reader = LineEditor::new(100);
960 let mut input = keys(&[b"garbage", &[21], b"clean", ENTER]); let mut out = Vec::new();
962 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
963 assert_eq!(result, Some("clean".to_string()));
964 }
965
966 #[test]
967 fn test_empty_line_not_in_history() {
968 let mut reader = LineEditor::new(100);
969 let mut out = Vec::new();
970
971 let mut input = keys(&[b"real", ENTER]);
973 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
974
975 let mut input = keys(&[ENTER]);
977 reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
978
979 let mut input = keys(&[UP, ENTER]);
981 out.clear();
982 let result = reader.read_line_from(&mut input, &mut out, "$ ", None).unwrap();
983 assert_eq!(result, Some("real".to_string()));
984 }
985
986 #[test]
989 fn test_run_loop_with_handler() {
990 use std::sync::{Arc, Mutex};
991
992 let executed = Arc::new(Mutex::new(Vec::new()));
993 let exec_clone = Arc::clone(&executed);
994
995 let handler = move |line: &str| -> Result<LoopAction, String> {
996 exec_clone.lock().unwrap().push(line.to_string());
997 Ok(LoopAction::Continue)
998 };
999
1000 let mut reader = LineEditor::new(100);
1001 let mut input = keys(&[b"echo hello", ENTER, b"ls", ENTER, &[4]]);
1003 let mut out = Vec::new();
1004 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
1005
1006 let cmds = executed.lock().unwrap();
1007 assert_eq!(cmds.len(), 2);
1008 assert_eq!(cmds[0], "echo hello");
1009 assert_eq!(cmds[1], "ls");
1010 }
1011
1012 #[test]
1013 fn test_run_loop_break_on_exit() {
1014 let handler = |line: &str| -> Result<LoopAction, String> {
1015 if line == "exit" {
1016 Ok(LoopAction::Break)
1017 } else {
1018 Ok(LoopAction::Continue)
1019 }
1020 };
1021
1022 let mut reader = LineEditor::new(100);
1023 let mut input = keys(&[b"cmd1", ENTER, b"exit", ENTER, b"cmd2", ENTER]);
1024 let mut out = Vec::new();
1025 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
1026 }
1028
1029 #[test]
1030 fn test_run_loop_error_continues() {
1031 use std::sync::{Arc, Mutex};
1032
1033 let count = Arc::new(Mutex::new(0u32));
1034 let count_clone = Arc::clone(&count);
1035
1036 let handler = move |line: &str| -> Result<LoopAction, String> {
1037 *count_clone.lock().unwrap() += 1;
1038 if line == "fail" {
1039 Err("simulated error".to_string())
1040 } else {
1041 Ok(LoopAction::Continue)
1042 }
1043 };
1044
1045 let mut reader = LineEditor::new(100);
1046 let mut input = keys(&[b"ok", ENTER, b"fail", ENTER, b"ok2", ENTER, &[4]]);
1047 let mut out = Vec::new();
1048 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
1049
1050 assert_eq!(*count.lock().unwrap(), 3);
1052 }
1053
1054 #[test]
1055 fn test_run_loop_with_history_navigation() {
1056 use std::sync::{Arc, Mutex};
1057
1058 let executed = Arc::new(Mutex::new(Vec::new()));
1059 let exec_clone = Arc::clone(&executed);
1060
1061 let handler = move |line: &str| -> Result<LoopAction, String> {
1062 exec_clone.lock().unwrap().push(line.to_string());
1063 Ok(LoopAction::Continue)
1064 };
1065
1066 let mut reader = LineEditor::new(100);
1067 let mut input = keys(&[
1069 b"echo hello", ENTER,
1070 UP, ENTER, &[4], ]);
1073 let mut out = Vec::new();
1074 reader.run_loop_from(&mut input, &mut out, "$ ", &handler, None).unwrap();
1075
1076 let cmds = executed.lock().unwrap();
1077 assert_eq!(cmds.len(), 2);
1078 assert_eq!(cmds[0], "echo hello");
1079 assert_eq!(cmds[1], "echo hello"); }
1081
1082 #[test]
1083 fn test_run_loop_with_handle_parallel() {
1084 use std::sync::{Arc, Mutex};
1085 use crate::{CommandRegistry, handle_parallel, ArcVecWriter};
1086
1087 let registry = Arc::new(CommandRegistry::with_builtins());
1088 let output = Arc::new(Mutex::new(Vec::<u8>::new()));
1089
1090 let reg = Arc::clone(®istry);
1091 let out_ref = Arc::clone(&output);
1092
1093 let handler = move |line: &str| -> Result<LoopAction, String> {
1094 if line == "exit" {
1095 return Ok(LoopAction::Break);
1096 }
1097 let results = handle_parallel(
1098 vec![line.to_string()],
1099 Box::new(std::io::empty()),
1100 Box::new(ArcVecWriter { inner: Arc::clone(&out_ref) }),
1101 Arc::clone(®),
1102 wasibox_core::CancellationToken::new(),
1103 );
1104 for res in results {
1105 res?;
1106 }
1107 Ok(LoopAction::Continue)
1108 };
1109
1110 let mut reader = LineEditor::new(100);
1111 let mut input = keys(&[
1113 b"echo hello", ENTER,
1114 UP, ENTER, b"exit", ENTER,
1116 ]);
1117 let mut term_out = Vec::new();
1118 reader.run_loop_from(&mut input, &mut term_out, "$ ", &handler, None).unwrap();
1119
1120 let buf = output.lock().unwrap();
1121 let result = String::from_utf8_lossy(&buf);
1122 let lines: Vec<&str> = result.trim().lines().collect();
1123 assert_eq!(lines.len(), 2);
1124 assert_eq!(lines[0], "hello");
1125 assert_eq!(lines[1], "hello"); }
1127
1128 #[test]
1129 fn test_key_event_handler() {
1130 struct MockHandler {
1131 events: Vec<KeyEvent>,
1132 }
1133 impl KeyEventHandler for MockHandler {
1134 fn on_key_event(&mut self, key: KeyEvent) {
1135 self.events.push(key);
1136 }
1137 }
1138
1139 let mut editor = LineEditor::new(10);
1140 let mut handler = MockHandler { events: Vec::new() };
1141
1142 editor.input_char_with_handler('a' as u32, &mut handler);
1143 editor.input_char_with_handler('b' as u32, &mut handler);
1144 editor.input_char_with_handler(13, &mut handler);
1145
1146 assert_eq!(handler.events, vec![
1147 KeyEvent::Char('a'),
1148 KeyEvent::Char('b'),
1149 KeyEvent::Enter,
1150 ]);
1151 }
1152}