1use std::io::{self, Read, Write};
2use unicode_segmentation::UnicodeSegmentation;
3use unicode_width::UnicodeWidthStr;
4
5const KEY_UP: u32 = 0x110001;
6const KEY_DOWN: u32 = 0x110002;
7const KEY_RIGHT: u32 = 0x110003;
8const KEY_LEFT: u32 = 0x110004;
9const KEY_HOME: u32 = 0x110005;
10const KEY_END: u32 = 0x110006;
11const KEY_DELETE: u32 = 0x110007;
12
13#[cfg(unix)]
18struct RawModeGuard {
19 original: libc::termios,
20}
21
22#[cfg(unix)]
23impl RawModeGuard {
24 fn enter() -> io::Result<Self> {
25 let mut original: libc::termios = unsafe { std::mem::zeroed() };
26 if unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut original) } != 0 {
27 return Err(io::Error::last_os_error());
28 }
29 let mut raw = original;
30 raw.c_lflag &= !(libc::ICANON | libc::ECHO | libc::ISIG);
31 raw.c_cc[libc::VMIN] = 1;
32 raw.c_cc[libc::VTIME] = 0;
33 if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &raw) } != 0 {
34 return Err(io::Error::last_os_error());
35 }
36 Ok(Self { original })
37 }
38}
39
40#[cfg(unix)]
41impl Drop for RawModeGuard {
42 fn drop(&mut self) {
43 unsafe {
44 libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.original);
45 }
46 }
47}
48
49#[cfg(windows)]
52mod win32 {
53 #[link(name = "kernel32")]
54 unsafe extern "system" {
55 pub fn GetStdHandle(nStdHandle: u32) -> isize;
56 pub fn GetConsoleMode(hConsoleHandle: isize, lpMode: *mut u32) -> i32;
57 pub fn SetConsoleMode(hConsoleHandle: isize, dwMode: u32) -> i32;
58 }
59
60 pub const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6; pub const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
62 pub const ENABLE_LINE_INPUT: u32 = 0x0002;
63 pub const ENABLE_ECHO_INPUT: u32 = 0x0004;
64 pub const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
65}
66
67#[cfg(windows)]
68struct RawModeGuard {
69 handle: isize,
70 original_mode: u32,
71}
72
73#[cfg(windows)]
74impl RawModeGuard {
75 fn enter() -> io::Result<Self> {
76 let handle = unsafe { win32::GetStdHandle(win32::STD_INPUT_HANDLE) };
77 if handle == -1 {
78 return Err(io::Error::last_os_error());
79 }
80 let mut original_mode: u32 = 0;
81 if unsafe { win32::GetConsoleMode(handle, &mut original_mode) } == 0 {
82 return Err(io::Error::last_os_error());
83 }
84 let new_mode = (original_mode
85 & !(win32::ENABLE_LINE_INPUT
86 | win32::ENABLE_ECHO_INPUT
87 | win32::ENABLE_PROCESSED_INPUT))
88 | win32::ENABLE_VIRTUAL_TERMINAL_INPUT;
89 if unsafe { win32::SetConsoleMode(handle, new_mode) } == 0 {
90 return Err(io::Error::last_os_error());
91 }
92 Ok(Self {
93 handle,
94 original_mode,
95 })
96 }
97}
98
99#[cfg(windows)]
100impl Drop for RawModeGuard {
101 fn drop(&mut self) {
102 unsafe {
103 win32::SetConsoleMode(self.handle, self.original_mode);
104 }
105 }
106}
107
108#[cfg(target_os = "wasi")]
111struct RawModeGuard;
112
113#[cfg(target_os = "wasi")]
114impl RawModeGuard {
115 fn enter() -> io::Result<Self> {
116 Ok(Self)
117 }
118}
119
120pub trait LineHandler {
134 fn handle_line(&self, line: &str) -> Result<LoopAction, String>;
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum LoopAction {
140 Continue,
142 Break,
144}
145
146impl<F> LineHandler for F
148where
149 F: Fn(&str) -> Result<LoopAction, String>,
150{
151 fn handle_line(&self, line: &str) -> Result<LoopAction, String> {
152 self(line)
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum KeyEvent {
162 Char(char),
163 Enter,
164 Backspace,
165 Delete,
166 Up,
167 Down,
168 Left,
169 Right,
170 Home,
171 End,
172 CtrlA,
173 CtrlE,
174 CtrlU,
175 CtrlK,
176 CtrlW,
177 CtrlD,
178 CtrlC,
179 Esc,
180}
181
182pub trait KeyEventHandler {
183 fn on_key_event(&mut self, key: KeyEvent);
184}
185
186struct NoopKeyEventHandler;
187
188impl KeyEventHandler for NoopKeyEventHandler {
189 fn on_key_event(&mut self, _key: KeyEvent) {}
190}
191
192fn previous_grapheme_boundary(s: &str, cursor: usize) -> usize {
193 s[..cursor]
194 .grapheme_indices(true)
195 .map(|(idx, _)| idx)
196 .last()
197 .unwrap_or(0)
198}
199
200fn next_grapheme_boundary(s: &str, cursor: usize) -> usize {
201 if cursor >= s.len() {
202 return s.len();
203 }
204
205 let mut iter = s[cursor..].grapheme_indices(true);
206 iter.next();
207 iter.next().map(|(idx, _)| cursor + idx).unwrap_or(s.len())
208}
209
210pub struct LineBuffer {
212 pub buffer: String,
213 pub cursor_pos: usize,
214}
215
216impl LineBuffer {
217 pub const fn new() -> Self {
219 Self {
220 buffer: String::new(),
221 cursor_pos: 0,
222 }
223 }
224
225 pub fn set_buffer(&mut self, text: String) {
226 self.buffer = text;
227 self.cursor_pos = self.buffer.len();
228 }
229
230 pub fn apply_key(&mut self, key: KeyEvent) {
231 match key {
232 KeyEvent::Char(ch) => {
233 self.buffer.insert(self.cursor_pos, ch);
234 self.cursor_pos += ch.len_utf8();
235 }
236 KeyEvent::Backspace => {
237 if self.cursor_pos > 0 {
238 let start = previous_grapheme_boundary(&self.buffer, self.cursor_pos);
239 self.buffer.drain(start..self.cursor_pos);
240 self.cursor_pos = start;
241 }
242 }
243 KeyEvent::Delete => {
244 if self.cursor_pos < self.buffer.len() {
245 let end = next_grapheme_boundary(&self.buffer, self.cursor_pos);
246 self.buffer.drain(self.cursor_pos..end);
247 }
248 }
249 KeyEvent::Left => {
250 if self.cursor_pos > 0 {
251 self.cursor_pos = previous_grapheme_boundary(&self.buffer, self.cursor_pos);
252 }
253 }
254 KeyEvent::Right => {
255 if self.cursor_pos < self.buffer.len() {
256 self.cursor_pos = next_grapheme_boundary(&self.buffer, self.cursor_pos);
257 }
258 }
259 KeyEvent::Home | KeyEvent::CtrlA => {
260 self.cursor_pos = 0;
261 }
262 KeyEvent::End | KeyEvent::CtrlE => {
263 self.cursor_pos = self.buffer.len();
264 }
265 KeyEvent::CtrlU => {
266 self.buffer.clear();
267 self.cursor_pos = 0;
268 }
269 KeyEvent::CtrlK => {
270 self.buffer.truncate(self.cursor_pos);
271 }
272 KeyEvent::CtrlW => {
273 if self.cursor_pos > 0 {
274 let before_cursor = &self.buffer[..self.cursor_pos];
275 let mut new_pos = 0;
276 let mut seen_word = false;
277 for (idx, grapheme) in before_cursor.grapheme_indices(true).rev() {
278 let is_space = grapheme.chars().all(char::is_whitespace);
279 if !seen_word && is_space {
280 continue;
281 }
282 if is_space {
283 new_pos = idx + grapheme.len();
284 break;
285 }
286 seen_word = true;
287 new_pos = idx;
288 }
289 self.buffer.drain(new_pos..self.cursor_pos);
290 self.cursor_pos = new_pos;
291 }
292 }
293 KeyEvent::Esc => {}
294 _ => {}
295 }
296 }
297}
298
299pub trait History {
304 fn push(&mut self, line: &str);
305 fn len(&self) -> usize;
306 fn get(&self, index: usize) -> Option<String>;
307 fn is_empty(&self) -> bool {
308 self.len() == 0
309 }
310}
311
312pub struct InMemoryHistory {
313 entries: Vec<String>,
314 max_len: usize,
315}
316
317impl InMemoryHistory {
318 pub const fn new(max_len: usize) -> Self {
319 Self {
320 entries: Vec::new(),
321 max_len,
322 }
323 }
324}
325
326impl History for InMemoryHistory {
327 fn push(&mut self, line: &str) {
328 let trimmed = line.trim();
329 if trimmed.is_empty() {
330 return;
331 }
332 if self.entries.last().map(|s| s.as_str()) == Some(trimmed) {
333 return;
334 }
335 self.entries.push(trimmed.to_string());
336 if self.entries.len() > self.max_len {
337 self.entries.remove(0);
338 }
339 }
340
341 fn len(&self) -> usize {
342 self.entries.len()
343 }
344
345 fn get(&self, index: usize) -> Option<String> {
346 self.entries.get(index).cloned()
347 }
348}
349
350pub struct LineEditor<H: History = InMemoryHistory> {
366 line_buffer: LineBuffer,
367 history: H,
368 history_idx: usize,
369 saved_input: String,
370 esc_buf: smallvec::SmallVec<[u8; 4]>,
371}
372
373impl LineEditor<InMemoryHistory> {
374 pub const fn new(max_history: usize) -> Self {
376 Self::with_history_and_len(InMemoryHistory::new(max_history), 0)
377 }
378}
379
380impl<H: History> LineEditor<H> {
381 pub fn with_history(history: H) -> Self {
382 let history_idx = history.len();
383 Self {
384 line_buffer: LineBuffer::new(),
385 history,
386 history_idx,
387 saved_input: String::new(),
388 esc_buf: smallvec::SmallVec::new_const(),
389 }
390 }
391
392 pub const fn with_history_and_len(history: H, len: usize) -> Self {
393 Self {
394 line_buffer: LineBuffer::new(),
395 history,
396 history_idx: len,
397 saved_input: String::new(),
398 esc_buf: smallvec::SmallVec::new_const(),
399 }
400 }
401
402 pub fn buffer(&self) -> &str {
403 &self.line_buffer.buffer
404 }
405
406 pub fn cursor_pos(&self) -> usize {
407 self.line_buffer.cursor_pos
408 }
409
410 pub fn start_new_line(&mut self) {
412 self.line_buffer.buffer.clear();
413 self.line_buffer.cursor_pos = 0;
414 self.history_idx = self.history.len();
415 self.saved_input.clear();
416 self.esc_buf.clear();
417 }
418
419 pub fn input_char(&mut self, code: u32) -> Option<String> {
420 self.input_char_with_handler(code, &mut NoopKeyEventHandler)
421 }
422
423 pub fn input_char_with_handler<K: KeyEventHandler>(
424 &mut self,
425 code: u32,
426 handler: &mut K,
427 ) -> Option<String> {
428 if code == 27 {
430 self.esc_buf.clear();
431 self.esc_buf.push(27);
432 return None;
433 }
434
435 if !self.esc_buf.is_empty() {
436 self.esc_buf.push(code as u8);
437 let seq = self.esc_buf.as_slice();
438
439 let key = match seq {
440 [27, b'[', b'A'] => Some(KeyEvent::Up),
441 [27, b'[', b'B'] => Some(KeyEvent::Down),
442 [27, b'[', b'C'] => Some(KeyEvent::Right),
443 [27, b'[', b'D'] => Some(KeyEvent::Left),
444 [27, b'[', b'H'] => Some(KeyEvent::Home),
445 [27, b'[', b'F'] => Some(KeyEvent::End),
446 [27, b'[', b'3', b'~'] => Some(KeyEvent::Delete),
447 _ => {
448 if seq.len() >= 4 || (seq.len() == 2 && seq[1] != b'[') {
450 self.esc_buf.clear();
452 None
453 } else {
454 return None;
456 }
457 }
458 };
459
460 if let Some(k) = key {
461 self.esc_buf.clear();
462 return self.handle_key_event(k, handler);
463 }
464 }
466
467 let key = match code {
469 1 => KeyEvent::CtrlA,
471 3 => KeyEvent::CtrlC,
472 4 => KeyEvent::CtrlD,
473 5 => KeyEvent::CtrlE,
474 8 | 127 => KeyEvent::Backspace,
475 11 => KeyEvent::CtrlK,
476 13 | 10 => KeyEvent::Enter,
477 21 => KeyEvent::CtrlU,
478 23 => KeyEvent::CtrlW,
479 27 => KeyEvent::Esc,
480
481 KEY_UP => KeyEvent::Up,
483 KEY_DOWN => KeyEvent::Down,
484 KEY_RIGHT => KeyEvent::Right,
485 KEY_LEFT => KeyEvent::Left,
486 KEY_HOME => KeyEvent::Home,
487 KEY_END => KeyEvent::End,
488 KEY_DELETE => KeyEvent::Delete,
489
490 c if c >= 0x20 => match char::from_u32(c) {
492 Some(ch) if !ch.is_control() => KeyEvent::Char(ch),
493 _ => return None,
494 },
495 _ => return None,
496 };
497
498 self.handle_key_event(key, handler)
499 }
500
501 fn handle_key_event<K: KeyEventHandler>(
502 &mut self,
503 key: KeyEvent,
504 handler: &mut K,
505 ) -> Option<String> {
506 handler.on_key_event(key);
507
508 if key == KeyEvent::Enter {
509 let final_line = self.line_buffer.buffer.clone();
510 self.history.push(&final_line);
511 self.start_new_line();
512 return Some(final_line);
513 }
514
515 match key {
516 KeyEvent::Up => {
517 if !self.history.is_empty() && self.history_idx > 0 {
518 if self.history_idx == self.history.len() {
519 self.saved_input = self.line_buffer.buffer.clone();
520 }
521 self.history_idx -= 1;
522 if let Some(hist_line) = self.history.get(self.history_idx) {
523 self.line_buffer.set_buffer(hist_line);
524 }
525 }
526 }
527 KeyEvent::Down => {
528 if self.history_idx < self.history.len() {
529 self.history_idx += 1;
530 if self.history_idx == self.history.len() {
531 self.line_buffer.set_buffer(self.saved_input.clone());
532 } else if let Some(hist_line) = self.history.get(self.history_idx) {
533 self.line_buffer.set_buffer(hist_line);
534 }
535 }
536 }
537 _ => {
538 self.line_buffer.apply_key(key);
539 }
540 }
541
542 None
543 }
544
545 pub fn read_line(
549 &mut self,
550 prompt: &str,
551 cancel_token: Option<wasibox_core::CancellationToken>,
552 ) -> io::Result<Option<String>> {
553 let mut stdout = io::stdout();
554 write!(stdout, "{}", prompt)?;
555 stdout.flush()?;
556
557 let _guard = RawModeGuard::enter()?;
558
559 let mut reader = io::stdin();
560 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
561 }
562
563 pub fn read_line_with_stdin(
565 &mut self,
566 prompt: &str,
567 cancel_token: Option<wasibox_core::CancellationToken>,
568 mut reader: Box<dyn Read>,
569 ) -> io::Result<Option<String>> {
570 let mut stdout = io::stdout();
571 write!(stdout, "{}", prompt)?;
572 stdout.flush()?;
573
574 let _guard = RawModeGuard::enter()?;
575
576 self.read_line_from(&mut reader, &mut stdout, prompt, cancel_token)
577 }
578
579 pub fn run_loop<P, L>(
586 &mut self,
587 prompt_fn: P,
588 handler: &L,
589 cancel_token: wasibox_core::CancellationToken,
590 ) -> io::Result<()>
591 where
592 P: Fn() -> String,
593 L: LineHandler,
594 {
595 loop {
596 let prompt = prompt_fn();
597 match self.read_line(&prompt, Some(cancel_token.clone()))? {
598 None => break,
599 Some(line) => {
600 let trimmed = line.trim();
601 if trimmed.is_empty() {
602 continue;
603 }
604 match handler.handle_line(trimmed) {
605 Ok(LoopAction::Continue) => {}
606 Ok(LoopAction::Break) => break,
607 Err(e) => {
608 eprintln!("{}", e);
609 }
610 }
611 }
612 }
613 }
614 Ok(())
615 }
616
617 pub fn run_loop_with_stdin<P, L>(
619 &mut self,
620 prompt_fn: P,
621 handler: &L,
622 cancel_token: wasibox_core::CancellationToken,
623 mut reader: Box<dyn Read>,
624 ) -> io::Result<()>
625 where
626 P: Fn() -> String,
627 L: LineHandler,
628 {
629 loop {
630 let prompt = prompt_fn();
631 let _guard = RawModeGuard::enter()?;
632 match self.read_line_from(
633 &mut reader,
634 &mut io::stdout(),
635 &prompt,
636 Some(cancel_token.clone()),
637 )? {
638 None => break,
639 Some(line) => {
640 let trimmed = line.trim();
641 if trimmed.is_empty() {
642 continue;
643 }
644 match handler.handle_line(trimmed) {
645 Ok(LoopAction::Continue) => {}
646 Ok(LoopAction::Break) => break,
647 Err(e) => {
648 eprintln!("{}", e);
649 }
650 }
651 }
652 }
653 }
654 Ok(())
655 }
656
657 #[cfg(test)]
659 fn run_loop_from<R: Read, W: Write, L: LineHandler>(
660 &mut self,
661 reader: &mut R,
662 writer: &mut W,
663 prompt: &str,
664 handler: &L,
665 cancel_token: Option<wasibox_core::CancellationToken>,
666 ) -> io::Result<()> {
667 loop {
668 write!(writer, "{}", prompt)?;
669 writer.flush()?;
670 match self.read_line_from(reader, writer, prompt, cancel_token.clone())? {
671 None => break,
672 Some(line) => {
673 let trimmed = line.trim();
674 if trimmed.is_empty() {
675 continue;
676 }
677 match handler.handle_line(trimmed) {
678 Ok(LoopAction::Continue) => {}
679 Ok(LoopAction::Break) => break,
680 Err(e) => {
681 writeln!(writer, "Error: {}", e)?;
682 }
683 }
684 }
685 }
686 }
687 Ok(())
688 }
689
690 pub fn read_line_from<R: Read, W: Write>(
693 &mut self,
694 reader: &mut R,
695 writer: &mut W,
696 prompt: &str,
697 cancel_token: Option<wasibox_core::CancellationToken>,
698 ) -> io::Result<Option<String>> {
699 self.start_new_line();
700
701 loop {
702 let b = {
703 let mut buf = [0u8; 1];
704 reader.read_exact(&mut buf)?;
705 buf[0]
706 };
707
708 let code = match b {
709 4 => {
711 if self.buffer().is_empty() {
712 write!(writer, "\r\n")?;
713 writer.flush()?;
714 return Ok(None);
715 }
716 4
717 }
718 3 => {
720 if let Some(token) = &cancel_token {
721 token.cancel();
722 }
723 write!(writer, "^C\r\n")?;
724 writer.flush()?;
725 self.start_new_line(); return Ok(Some(String::new()));
727 }
728 27 => {
730 let seq1 = {
731 let mut buf = [0u8; 1];
732 reader.read_exact(&mut buf)?;
733 buf[0]
734 };
735 if seq1 == b'[' {
736 let seq2 = {
737 let mut buf = [0u8; 1];
738 reader.read_exact(&mut buf)?;
739 buf[0]
740 };
741 match seq2 {
742 b'A' => KEY_UP, b'B' => KEY_DOWN, b'C' => KEY_RIGHT, b'D' => KEY_LEFT, b'H' => KEY_HOME, b'F' => KEY_END, b'3' => {
749 let seq3 = {
750 let mut buf = [0u8; 1];
751 reader.read_exact(&mut buf)?;
752 buf[0]
753 };
754 if seq3 == b'~' {
755 KEY_DELETE } else {
757 continue;
758 }
759 }
760 _ => continue,
761 }
762 } else {
763 continue;
764 }
765 }
766 other if other < 0x80 => other as u32,
767 other if other >= 0xC0 && other <= 0xF7 => {
768 let mut bytes = vec![other];
769 let expected = if other >= 0xF0 {
770 4
771 } else if other >= 0xE0 {
772 3
773 } else {
774 2
775 };
776 while bytes.len() < expected {
777 let mut cb = [0u8; 1];
778 match reader.read_exact(&mut cb) {
779 Ok(_) => bytes.push(cb[0]),
780 Err(_) => return Ok(None),
781 }
782 }
783 match std::str::from_utf8(&bytes) {
784 Ok(s) => s.chars().next().map(|c| c as u32).unwrap_or(0xFFFD),
785 Err(_) => 0xFFFD,
786 }
787 }
788 _ => 0xFFFD,
789 };
790
791 let old_pos = self.cursor_pos();
792 let old_len = self.buffer().len();
793
794 if let Some(completed_line) = self.input_char(code) {
795 write!(writer, "\r\n")?;
796 writer.flush()?;
797 return Ok(Some(completed_line));
798 }
799
800 if char::from_u32(code).is_some_and(|ch| !ch.is_control())
802 && old_pos == old_len
803 && self.cursor_pos() == self.buffer().len()
804 {
805 write!(writer, "{}", char::from_u32(code).unwrap())?;
806 writer.flush()?;
807 } else if code == KEY_LEFT && old_pos > self.cursor_pos() && old_pos > 0 {
808 let crossed = &self.buffer()[self.cursor_pos()..old_pos];
810 write!(writer, "\x1b[{}D", UnicodeWidthStr::width(crossed))?;
811 writer.flush()?;
812 } else if code == KEY_RIGHT && old_pos < self.cursor_pos() && old_pos < old_len {
813 let crossed = &self.buffer()[old_pos..self.cursor_pos()];
815 write!(writer, "\x1b[{}C", UnicodeWidthStr::width(crossed))?;
816 writer.flush()?;
817 } else {
818 Self::redraw_line(writer, prompt, self.buffer(), self.cursor_pos())?;
819 }
820 }
821 }
822
823 fn redraw_line<W: Write>(
825 writer: &mut W,
826 prompt: &str,
827 line: &str,
828 cursor_pos: usize,
829 ) -> io::Result<()> {
830 write!(writer, "\r\x1b[K{}{}", prompt, line)?;
831 let total_width = UnicodeWidthStr::width(prompt) + UnicodeWidthStr::width(line);
832 let target_width =
833 UnicodeWidthStr::width(prompt) + UnicodeWidthStr::width(&line[..cursor_pos]);
834 if target_width < total_width {
835 write!(writer, "\x1b[{}D", total_width - target_width)?;
836 }
837 writer.flush()
838 }
839}
840
841#[cfg(test)]
846mod tests {
847 use super::*;
848 use std::io::Cursor;
849
850 fn keys(parts: &[&[u8]]) -> Cursor<Vec<u8>> {
852 let mut buf = Vec::new();
853 for part in parts {
854 buf.extend_from_slice(part);
855 }
856 Cursor::new(buf)
857 }
858
859 const UP: &[u8] = b"\x1b[A";
860 const DOWN: &[u8] = b"\x1b[B";
861 const ENTER: &[u8] = b"\r";
862
863 #[test]
864 fn test_line_editor_basic() {
865 let mut editor = LineEditor::new(0);
866
867 assert!(editor.input_char('a' as u32).is_none());
868 assert!(editor.input_char('b' as u32).is_none());
869 assert_eq!(editor.buffer(), "ab");
870 assert_eq!(editor.cursor_pos(), 2);
871
872 assert!(editor.input_char(KEY_LEFT).is_none()); assert_eq!(editor.cursor_pos(), 1);
874
875 assert!(editor.input_char('c' as u32).is_none());
876 assert_eq!(editor.buffer(), "acb");
877 assert_eq!(editor.cursor_pos(), 2);
878
879 assert!(editor.input_char(127).is_none()); assert_eq!(editor.buffer(), "ab");
881 assert_eq!(editor.cursor_pos(), 1);
882
883 let result = editor.input_char(13); assert_eq!(result, Some("ab".to_string()));
885 }
886
887 #[test]
888 fn test_line_editor_history() {
889 let mut editor = LineEditor::new(10);
890 editor.input_char('f' as u32);
891 editor.input_char('i' as u32);
892 editor.input_char('r' as u32);
893 editor.input_char('s' as u32);
894 editor.input_char('t' as u32);
895 editor.input_char(13); editor.input_char('s' as u32);
898 editor.input_char('e' as u32);
899 editor.input_char('c' as u32);
900 editor.input_char('o' as u32);
901 editor.input_char('n' as u32);
902 editor.input_char('d' as u32);
903 editor.input_char(13); editor.input_char(KEY_UP); assert_eq!(editor.buffer(), "second");
907
908 editor.input_char(KEY_UP); assert_eq!(editor.buffer(), "first");
910
911 editor.input_char(KEY_DOWN); assert_eq!(editor.buffer(), "second");
913
914 editor.input_char(KEY_DOWN); assert_eq!(editor.buffer(), ""); }
917
918 #[test]
919 fn test_simple_input() {
920 let mut reader = LineEditor::new(100);
921 let mut input = keys(&[b"hello", ENTER]);
922 let mut out = Vec::new();
923 let result = reader
924 .read_line_from(&mut input, &mut out, "$ ", None)
925 .unwrap();
926 assert_eq!(result, Some("hello".to_string()));
927 }
928
929 #[test]
930 fn test_eof_on_empty() {
931 let mut reader = LineEditor::new(100);
932 let mut input = Cursor::new(vec![4u8]); let mut out = Vec::new();
934 let result = reader
935 .read_line_from(&mut input, &mut out, "$ ", None)
936 .unwrap();
937 assert_eq!(result, None);
938 }
939
940 #[test]
941 fn test_history_up_arrow() {
942 let mut reader = LineEditor::new(100);
943 let mut out = Vec::new();
944
945 let mut input = keys(&[b"echo hello", ENTER]);
947 reader
948 .read_line_from(&mut input, &mut out, "$ ", None)
949 .unwrap();
950
951 let mut input = keys(&[UP, ENTER]);
953 out.clear();
954 let result = reader
955 .read_line_from(&mut input, &mut out, "$ ", None)
956 .unwrap();
957 assert_eq!(result, Some("echo hello".to_string()));
958 }
959
960 #[test]
961 fn test_history_up_down_arrow() {
962 let mut reader = LineEditor::new(100);
963 let mut out = Vec::new();
964
965 let mut input = keys(&[b"first", ENTER]);
967 reader
968 .read_line_from(&mut input, &mut out, "$ ", None)
969 .unwrap();
970 let mut input = keys(&[b"second", ENTER]);
971 reader
972 .read_line_from(&mut input, &mut out, "$ ", None)
973 .unwrap();
974
975 let mut input = keys(&[UP, UP, DOWN, ENTER]);
977 out.clear();
978 let result = reader
979 .read_line_from(&mut input, &mut out, "$ ", None)
980 .unwrap();
981 assert_eq!(result, Some("second".to_string()));
982 }
983
984 #[test]
985 fn test_input_char_sequence() {
986 let mut editor = LineEditor::new(10);
987 editor.input_char('f' as u32);
988 editor.input_char('i' as u32);
989 editor.input_char('r' as u32);
990 editor.input_char('s' as u32);
991 editor.input_char('t' as u32);
992 editor.input_char(13); editor.input_char('s' as u32);
995 editor.input_char('e' as u32);
996 editor.input_char('c' as u32);
997 editor.input_char('o' as u32);
998 editor.input_char('n' as u32);
999 editor.input_char('d' as u32);
1000 editor.input_char(13); editor.input_char(KEY_UP); editor.input_char(KEY_UP); assert_eq!(editor.buffer(), "first");
1008
1009 editor.input_char(27); editor.input_char(91); editor.input_char(66); assert_eq!(editor.buffer(), "second");
1014 }
1015
1016 #[test]
1017 fn test_history_down_restores_current_input() {
1018 let mut reader = LineEditor::new(100);
1019 let mut out = Vec::new();
1020
1021 let mut input = keys(&[b"old", ENTER]);
1023 reader
1024 .read_line_from(&mut input, &mut out, "$ ", None)
1025 .unwrap();
1026
1027 let mut input = keys(&[b"new", UP, DOWN, ENTER]);
1029 out.clear();
1030 let result = reader
1031 .read_line_from(&mut input, &mut out, "$ ", None)
1032 .unwrap();
1033 assert_eq!(result, Some("new".to_string()));
1034 }
1035
1036 #[test]
1037 fn test_history_dedup() {
1038 let mut reader = LineEditor::new(100);
1039 let mut out = Vec::new();
1040
1041 let mut input = keys(&[b"dup", ENTER]);
1043 reader
1044 .read_line_from(&mut input, &mut out, "$ ", None)
1045 .unwrap();
1046 let mut input = keys(&[b"dup", ENTER]);
1047 reader
1048 .read_line_from(&mut input, &mut out, "$ ", None)
1049 .unwrap();
1050
1051 let mut input = keys(&[UP, UP, ENTER]);
1054 out.clear();
1055 let result = reader
1056 .read_line_from(&mut input, &mut out, "$ ", None)
1057 .unwrap();
1058 assert_eq!(result, Some("dup".to_string()));
1059 }
1060
1061 #[test]
1062 fn test_history_max_size() {
1063 let mut reader = LineEditor::new(3);
1064 let mut out = Vec::new();
1065
1066 for cmd in &["aaa", "bbb", "ccc", "ddd"] {
1067 let mut input = keys(&[cmd.as_bytes(), ENTER]);
1068 reader
1069 .read_line_from(&mut input, &mut out, "$ ", None)
1070 .unwrap();
1071 }
1072
1073 let mut input = keys(&[UP, UP, UP, ENTER]);
1075 out.clear();
1076 let result = reader
1077 .read_line_from(&mut input, &mut out, "$ ", None)
1078 .unwrap();
1079 assert_eq!(result, Some("bbb".to_string()));
1080 }
1081
1082 #[test]
1083 fn test_backspace() {
1084 let mut reader = LineEditor::new(100);
1085 let mut input = keys(&[b"helloo", &[127], ENTER]);
1086 let mut out = Vec::new();
1087 let result = reader
1088 .read_line_from(&mut input, &mut out, "$ ", None)
1089 .unwrap();
1090 assert_eq!(result, Some("hello".to_string()));
1091 }
1092
1093 #[test]
1094 fn test_ctrl_u_clears_line() {
1095 let mut reader = LineEditor::new(100);
1096 let mut input = keys(&[b"garbage", &[21], b"clean", ENTER]); let mut out = Vec::new();
1098 let result = reader
1099 .read_line_from(&mut input, &mut out, "$ ", None)
1100 .unwrap();
1101 assert_eq!(result, Some("clean".to_string()));
1102 }
1103
1104 #[test]
1105 fn test_empty_line_not_in_history() {
1106 let mut reader = LineEditor::new(100);
1107 let mut out = Vec::new();
1108
1109 let mut input = keys(&[b"real", ENTER]);
1111 reader
1112 .read_line_from(&mut input, &mut out, "$ ", None)
1113 .unwrap();
1114
1115 let mut input = keys(&[ENTER]);
1117 reader
1118 .read_line_from(&mut input, &mut out, "$ ", None)
1119 .unwrap();
1120
1121 let mut input = keys(&[UP, ENTER]);
1123 out.clear();
1124 let result = reader
1125 .read_line_from(&mut input, &mut out, "$ ", None)
1126 .unwrap();
1127 assert_eq!(result, Some("real".to_string()));
1128 }
1129
1130 #[test]
1133 fn test_run_loop_with_handler() {
1134 use std::sync::{Arc, Mutex};
1135
1136 let executed = Arc::new(Mutex::new(Vec::new()));
1137 let exec_clone = Arc::clone(&executed);
1138
1139 let handler = move |line: &str| -> Result<LoopAction, String> {
1140 exec_clone.lock().unwrap().push(line.to_string());
1141 Ok(LoopAction::Continue)
1142 };
1143
1144 let mut reader = LineEditor::new(100);
1145 let mut input = keys(&[b"echo hello", ENTER, b"ls", ENTER, &[4]]);
1147 let mut out = Vec::new();
1148 reader
1149 .run_loop_from(&mut input, &mut out, "$ ", &handler, None)
1150 .unwrap();
1151
1152 let cmds = executed.lock().unwrap();
1153 assert_eq!(cmds.len(), 2);
1154 assert_eq!(cmds[0], "echo hello");
1155 assert_eq!(cmds[1], "ls");
1156 }
1157
1158 #[test]
1159 fn test_run_loop_break_on_exit() {
1160 let handler = |line: &str| -> Result<LoopAction, String> {
1161 if line == "exit" {
1162 Ok(LoopAction::Break)
1163 } else {
1164 Ok(LoopAction::Continue)
1165 }
1166 };
1167
1168 let mut reader = LineEditor::new(100);
1169 let mut input = keys(&[b"cmd1", ENTER, b"exit", ENTER, b"cmd2", ENTER]);
1170 let mut out = Vec::new();
1171 reader
1172 .run_loop_from(&mut input, &mut out, "$ ", &handler, None)
1173 .unwrap();
1174 }
1176
1177 #[test]
1178 fn test_run_loop_error_continues() {
1179 use std::sync::{Arc, Mutex};
1180
1181 let count = Arc::new(Mutex::new(0u32));
1182 let count_clone = Arc::clone(&count);
1183
1184 let handler = move |line: &str| -> Result<LoopAction, String> {
1185 *count_clone.lock().unwrap() += 1;
1186 if line == "fail" {
1187 Err("simulated error".to_string())
1188 } else {
1189 Ok(LoopAction::Continue)
1190 }
1191 };
1192
1193 let mut reader = LineEditor::new(100);
1194 let mut input = keys(&[b"ok", ENTER, b"fail", ENTER, b"ok2", ENTER, &[4]]);
1195 let mut out = Vec::new();
1196 reader
1197 .run_loop_from(&mut input, &mut out, "$ ", &handler, None)
1198 .unwrap();
1199
1200 assert_eq!(*count.lock().unwrap(), 3);
1202 }
1203
1204 #[test]
1205 fn test_run_loop_with_history_navigation() {
1206 use std::sync::{Arc, Mutex};
1207
1208 let executed = Arc::new(Mutex::new(Vec::new()));
1209 let exec_clone = Arc::clone(&executed);
1210
1211 let handler = move |line: &str| -> Result<LoopAction, String> {
1212 exec_clone.lock().unwrap().push(line.to_string());
1213 Ok(LoopAction::Continue)
1214 };
1215
1216 let mut reader = LineEditor::new(100);
1217 let mut input = keys(&[
1219 b"echo hello",
1220 ENTER,
1221 UP,
1222 ENTER, &[4], ]);
1225 let mut out = Vec::new();
1226 reader
1227 .run_loop_from(&mut input, &mut out, "$ ", &handler, None)
1228 .unwrap();
1229
1230 let cmds = executed.lock().unwrap();
1231 assert_eq!(cmds.len(), 2);
1232 assert_eq!(cmds[0], "echo hello");
1233 assert_eq!(cmds[1], "echo hello"); }
1235
1236 #[test]
1237 fn test_run_loop_with_handle_parallel() {
1238 use crate::{ArcVecWriter, CommandRegistry, handle_parallel};
1239 use std::sync::{Arc, Mutex};
1240
1241 let registry = Arc::new(CommandRegistry::with_builtins());
1242 let output = Arc::new(Mutex::new(Vec::<u8>::new()));
1243
1244 let reg = Arc::clone(®istry);
1245 let out_ref = Arc::clone(&output);
1246
1247 let handler = move |line: &str| -> Result<LoopAction, String> {
1248 if line == "exit" {
1249 return Ok(LoopAction::Break);
1250 }
1251 let results = handle_parallel(
1252 vec![line.to_string()],
1253 Box::new(std::io::empty()),
1254 Box::new(ArcVecWriter {
1255 inner: Arc::clone(&out_ref),
1256 }),
1257 Arc::clone(®),
1258 wasibox_core::CancellationToken::new(),
1259 );
1260 for res in results {
1261 res?;
1262 }
1263 Ok(LoopAction::Continue)
1264 };
1265
1266 let mut reader = LineEditor::new(100);
1267 let mut input = keys(&[
1269 b"echo hello",
1270 ENTER,
1271 UP,
1272 ENTER, b"exit",
1274 ENTER,
1275 ]);
1276 let mut term_out = Vec::new();
1277 reader
1278 .run_loop_from(&mut input, &mut term_out, "$ ", &handler, None)
1279 .unwrap();
1280
1281 let buf = output.lock().unwrap();
1282 let result = String::from_utf8_lossy(&buf);
1283 let lines: Vec<&str> = result.trim().lines().collect();
1284 assert_eq!(lines.len(), 2);
1285 assert_eq!(lines[0], "hello");
1286 assert_eq!(lines[1], "hello"); }
1288
1289 #[test]
1290 fn test_key_event_handler() {
1291 struct MockHandler {
1292 events: Vec<KeyEvent>,
1293 }
1294 impl KeyEventHandler for MockHandler {
1295 fn on_key_event(&mut self, key: KeyEvent) {
1296 self.events.push(key);
1297 }
1298 }
1299
1300 let mut editor = LineEditor::new(10);
1301 let mut handler = MockHandler { events: Vec::new() };
1302
1303 editor.input_char_with_handler('a' as u32, &mut handler);
1304 editor.input_char_with_handler('b' as u32, &mut handler);
1305 editor.input_char_with_handler(13, &mut handler);
1306
1307 assert_eq!(
1308 handler.events,
1309 vec![KeyEvent::Char('a'), KeyEvent::Char('b'), KeyEvent::Enter,]
1310 );
1311 }
1312
1313 #[test]
1314 fn test_unicode_japanese_input_char() {
1315 let mut editor = LineEditor::new(10);
1316 editor.input_char('あ' as u32);
1317 assert_eq!(editor.buffer(), "あ");
1318 assert_eq!(editor.input_char(13), Some("あ".to_string()));
1319 }
1320
1321 #[test]
1322 fn test_unicode_emoji_input_char() {
1323 let mut editor = LineEditor::new(10);
1324 editor.input_char('🦀' as u32);
1325 assert_eq!(editor.buffer(), "🦀");
1326 assert_eq!(editor.input_char(13), Some("🦀".to_string()));
1327 }
1328
1329 #[test]
1330 fn test_read_line_from_decodes_utf8_input() {
1331 let mut reader = LineEditor::new(10);
1332 let mut input = Cursor::new("echo あ🦀\r".as_bytes().to_vec());
1333 let mut out = Vec::new();
1334 let result = reader
1335 .read_line_from(&mut input, &mut out, "$ ", None)
1336 .unwrap();
1337 assert_eq!(result, Some("echo あ🦀".to_string()));
1338 }
1339
1340 #[test]
1341 fn test_read_line_from_decodes_complex_emoji_input() {
1342 let family = "👨👩👧👦";
1343 let mut reader = LineEditor::new(10);
1344 let mut input = Cursor::new(format!("echo {family}\r").into_bytes());
1345 let mut out = Vec::new();
1346 let result = reader
1347 .read_line_from(&mut input, &mut out, "$ ", None)
1348 .unwrap();
1349 assert_eq!(result, Some(format!("echo {family}")));
1350 }
1351
1352 #[test]
1353 fn test_backspace_removes_complex_emoji_grapheme() {
1354 let family = "👨👩👧👦";
1355 let mut editor = LineEditor::new(10);
1356 for ch in format!("aあ🦀{family}").chars() {
1357 editor.input_char(ch as u32);
1358 }
1359
1360 editor.input_char(127);
1361 assert_eq!(editor.buffer(), "aあ🦀");
1362
1363 editor.input_char(127);
1364 assert_eq!(editor.buffer(), "aあ");
1365
1366 editor.input_char(127);
1367 assert_eq!(editor.buffer(), "a");
1368 }
1369
1370 #[test]
1371 fn test_left_right_do_not_split_graphemes() {
1372 let family = "👨👩👧👦";
1373 let mut editor = LineEditor::new(10);
1374 for ch in format!("a{family}b").chars() {
1375 editor.input_char(ch as u32);
1376 }
1377
1378 editor.input_char(KEY_LEFT);
1379 editor.input_char(KEY_LEFT);
1380 editor.input_char('X' as u32);
1381
1382 assert_eq!(editor.buffer(), format!("aX{family}b"));
1383 }
1384
1385 #[test]
1386 fn test_coptic_codepoint_no_longer_collides_with_special_keys() {
1387 let mut editor = LineEditor::new(10);
1388 let coptic = char::from_u32(1001).unwrap();
1389 editor.input_char(1001);
1390 assert_eq!(editor.buffer(), coptic.to_string());
1391 }
1392}