1use std::any::Any;
7use std::sync::{Arc, Mutex};
8
9use super::component::{Component, Focusable};
10use super::keybindings::Keybindings;
11use super::kill_ring::KillRing;
12use super::undo_stack::UndoStack;
13use super::word_navigation::{find_word_backward, find_word_forward};
14use crate::ansi::CURSOR_MARKER;
15use crate::utils::{slice_by_column, visible_width};
16
17fn byte_offset(value: &str, char_index: usize) -> usize {
25 value
26 .char_indices()
27 .nth(char_index)
28 .map(|(offset, _)| offset)
29 .unwrap_or(value.len())
30}
31
32fn char_count(value: &str) -> usize {
34 value.chars().count()
35}
36
37#[derive(Debug, Clone)]
39struct InputState {
40 value: String,
41 cursor: usize,
42}
43
44impl Default for InputState {
45 fn default() -> Self {
46 Self {
47 value: String::new(),
48 cursor: 0,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq)]
55enum LastAction {
56 None,
57 Kill,
58 Yank,
59 TypeWord,
60}
61
62pub struct Input {
64 state: Mutex<InputState>,
65 prompt: String,
66 placeholder: Option<String>,
67 #[allow(dead_code)]
68 keybindings: Arc<Keybindings>,
69 kill_ring: Mutex<KillRing>,
70 undo_stack: Mutex<UndoStack<InputState>>,
71 last_action: Mutex<LastAction>,
72 focused: Mutex<bool>,
73 on_submit: Mutex<Option<Arc<dyn Fn(&str) + Send + Sync>>>,
74 on_escape: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
75 on_change: Mutex<Option<Arc<dyn Fn(&str) + Send + Sync>>>,
76}
77
78impl Input {
79 pub fn new() -> Self {
81 Self {
82 state: Mutex::new(InputState::default()),
83 prompt: "> ".to_string(),
84 placeholder: None,
85 keybindings: Arc::new(Keybindings::new()),
86 kill_ring: Mutex::new(KillRing::new()),
87 undo_stack: Mutex::new(UndoStack::new()),
88 last_action: Mutex::new(LastAction::None),
89 focused: Mutex::new(false),
90 on_submit: Mutex::new(None),
91 on_escape: Mutex::new(None),
92 on_change: Mutex::new(None),
93 }
94 }
95
96 pub fn with_prompt(prompt: &str) -> Self {
98 let mut input = Self::new();
99 input.prompt = prompt.to_string();
100 input
101 }
102
103 pub fn with_placeholder(placeholder: &str) -> Self {
105 let mut input = Self::new();
106 input.placeholder = Some(placeholder.to_string());
107 input
108 }
109
110 pub fn get_value(&self) -> String {
112 self.state
113 .lock()
114 .map(|s| s.value.clone())
115 .unwrap_or_default()
116 }
117
118 pub fn set_value(&self, value: &str) {
120 if let Ok(mut state) = self.state.lock() {
121 state.value = value.to_string();
122 state.cursor = char_count(value);
124 }
125 }
126
127 pub fn clear(&self) {
129 if let Ok(mut state) = self.state.lock() {
130 state.value.clear();
131 state.cursor = 0;
132 }
133 }
134
135 pub fn on_submit(&self, callback: Arc<dyn Fn(&str) + Send + Sync>) {
137 if let Ok(mut cb) = self.on_submit.lock() {
138 *cb = Some(callback);
139 }
140 }
141
142 pub fn on_escape(&self, callback: Arc<dyn Fn() + Send + Sync>) {
144 if let Ok(mut cb) = self.on_escape.lock() {
145 *cb = Some(callback);
146 }
147 }
148
149 pub fn on_change(&self, callback: Arc<dyn Fn(&str) + Send + Sync>) {
151 if let Ok(mut cb) = self.on_change.lock() {
152 *cb = Some(callback);
153 }
154 }
155
156 pub fn handle_paste(&self, pasted_text: &str) {
158 self.set_last_action(LastAction::None);
159
160 let clean_text = pasted_text
162 .replace("\r\n", "")
163 .replace("\r", "")
164 .replace("\n", "")
165 .replace("\t", " ");
166
167 self.edit(false, |state| {
168 if clean_text.is_empty() {
169 return false;
170 }
171 let at = byte_offset(&state.value, state.cursor);
172 state.value.insert_str(at, &clean_text);
173 state.cursor += clean_text.chars().count();
174 true
175 });
176 }
177
178 fn insert_character(&self, char: &str) {
180 let whitespace = char
182 .chars()
183 .next()
184 .map(|c| c.is_whitespace())
185 .unwrap_or(true);
186 let coalesce = !whitespace && self.get_last_action() == LastAction::TypeWord;
187 self.set_last_action(LastAction::TypeWord);
188
189 self.edit(coalesce, |state| {
190 if char.is_empty() {
191 return false;
192 }
193 let at = byte_offset(&state.value, state.cursor);
194 state.value.insert_str(at, char);
195 state.cursor += char.chars().count();
196 true
197 });
198 }
199
200 fn handle_backspace(&self) {
202 self.set_last_action(LastAction::None);
203 self.edit(false, |state| {
204 if state.cursor == 0 {
205 return false;
206 }
207 let start = byte_offset(&state.value, state.cursor - 1);
208 let end = byte_offset(&state.value, state.cursor);
209 state.value.replace_range(start..end, "");
210 state.cursor -= 1;
211 true
212 });
213 }
214
215 fn handle_forward_delete(&self) {
217 self.set_last_action(LastAction::None);
218 self.edit(false, |state| {
219 if state.cursor >= char_count(&state.value) {
220 return false;
221 }
222 let start = byte_offset(&state.value, state.cursor);
223 let end = byte_offset(&state.value, state.cursor + 1);
224 state.value.replace_range(start..end, "");
225 true
226 });
227 }
228
229 fn delete_to_line_start(&self) {
231 self.edit(false, |state| {
232 if state.cursor == 0 {
233 return false;
234 }
235 let at = byte_offset(&state.value, state.cursor);
236 let deleted = state.value[..at].to_string();
237 if let Ok(mut kill_ring) = self.kill_ring.lock() {
238 use super::kill_ring::PushOptions;
239 let accumulate = self.get_last_action() == LastAction::Kill;
240 kill_ring.push(
241 &deleted,
242 PushOptions {
243 prepend: true,
244 accumulate,
245 },
246 );
247 }
248 self.set_last_action(LastAction::Kill);
249 state.value.replace_range(..at, "");
250 state.cursor = 0;
251 true
252 });
253 }
254
255 fn delete_to_line_end(&self) {
257 self.edit(false, |state| {
258 if state.cursor >= char_count(&state.value) {
259 return false;
260 }
261 let at = byte_offset(&state.value, state.cursor);
262 let deleted = state.value[at..].to_string();
263 if let Ok(mut kill_ring) = self.kill_ring.lock() {
264 use super::kill_ring::PushOptions;
265 let accumulate = self.get_last_action() == LastAction::Kill;
266 kill_ring.push(
267 &deleted,
268 PushOptions {
269 prepend: false,
270 accumulate,
271 },
272 );
273 }
274 self.set_last_action(LastAction::Kill);
275 state.value.truncate(at);
276 true
277 });
278 }
279
280 fn delete_word_backwards(&self) {
282 self.edit(false, |state| {
283 if state.cursor == 0 {
284 return false;
285 }
286 let was_kill = self.get_last_action() == LastAction::Kill;
287 let old_cursor = state.cursor;
288 let new_cursor = find_word_backward(&state.value, old_cursor);
289 let start = byte_offset(&state.value, new_cursor);
290 let end = byte_offset(&state.value, old_cursor);
291 let deleted = state.value[start..end].to_string();
292 if let Ok(mut kill_ring) = self.kill_ring.lock() {
293 use super::kill_ring::PushOptions;
294 kill_ring.push(
295 &deleted,
296 PushOptions {
297 prepend: true,
298 accumulate: was_kill,
299 },
300 );
301 }
302 self.set_last_action(LastAction::Kill);
303 state.value.replace_range(start..end, "");
304 state.cursor = new_cursor;
305 true
306 });
307 }
308
309 fn delete_word_forward(&self) {
311 self.edit(false, |state| {
312 if state.cursor >= char_count(&state.value) {
313 return false;
314 }
315 let was_kill = self.get_last_action() == LastAction::Kill;
316 let old_cursor = state.cursor;
317 let new_cursor = find_word_forward(&state.value, old_cursor);
318 let start = byte_offset(&state.value, old_cursor);
319 let end = byte_offset(&state.value, new_cursor);
320 let deleted = state.value[start..end].to_string();
321 if let Ok(mut kill_ring) = self.kill_ring.lock() {
322 use super::kill_ring::PushOptions;
323 kill_ring.push(
324 &deleted,
325 PushOptions {
326 prepend: false,
327 accumulate: was_kill,
328 },
329 );
330 }
331 self.set_last_action(LastAction::Kill);
332 state.value.replace_range(start..end, "");
333 state.cursor = old_cursor;
334 true
335 });
336 }
337
338 fn yank(&self) {
340 let text = match self.kill_ring.lock() {
341 Ok(kill_ring) => kill_ring.peek().map(|s| s.to_string()),
342 Err(_) => None,
343 };
344
345 if let Some(text) = text {
346 self.set_last_action(LastAction::Yank);
347 self.edit(false, |state| {
348 let at = byte_offset(&state.value, state.cursor);
349 state.value.insert_str(at, &text);
350 state.cursor += text.chars().count();
351 true
352 });
353 }
354 }
355
356 #[allow(dead_code)]
358 fn yank_pop(&self) {
359 if self.get_last_action() != LastAction::Yank {
360 return;
361 }
362
363 let (previous, replacement) = {
366 let Ok(mut kill_ring) = self.kill_ring.lock() else {
367 return;
368 };
369 if kill_ring.len() <= 1 {
370 return;
371 }
372 let previous = kill_ring.peek().unwrap_or("").to_string();
373 kill_ring.rotate();
374 let replacement = kill_ring.peek().unwrap_or("").to_string();
375 (previous, replacement)
376 };
377
378 self.edit(false, |state| {
379 let previous_len = previous.chars().count();
380 if state.cursor < previous_len {
381 return false;
382 }
383 let start = byte_offset(&state.value, state.cursor - previous_len);
385 let end = byte_offset(&state.value, state.cursor);
386 state.value.replace_range(start..end, &replacement);
387 state.cursor = state.cursor - previous_len + replacement.chars().count();
388 true
389 });
390 self.set_last_action(LastAction::Yank);
391 }
392
393 fn move_cursor_left(&self) {
395 self.set_last_action(LastAction::None);
396 if let Ok(mut state) = self.state.lock() {
397 if state.cursor > 0 {
398 state.cursor -= 1;
399 }
400 }
401 }
402
403 fn move_cursor_right(&self) {
405 self.set_last_action(LastAction::None);
406 if let Ok(mut state) = self.state.lock() {
407 if state.cursor < char_count(&state.value) {
408 state.cursor += 1;
409 }
410 }
411 }
412
413 fn move_to_line_start(&self) {
415 self.set_last_action(LastAction::None);
416 if let Ok(mut state) = self.state.lock() {
417 state.cursor = 0;
418 }
419 }
420
421 fn move_to_line_end(&self) {
423 self.set_last_action(LastAction::None);
424 if let Ok(mut state) = self.state.lock() {
425 state.cursor = char_count(&state.value);
426 }
427 }
428
429 fn move_word_left(&self) {
431 self.set_last_action(LastAction::None);
432 if let Ok(mut state) = self.state.lock() {
433 state.cursor = find_word_backward(&state.value, state.cursor);
434 }
435 }
436
437 fn move_word_right(&self) {
439 self.set_last_action(LastAction::None);
440 if let Ok(mut state) = self.state.lock() {
441 state.cursor = find_word_forward(&state.value, state.cursor);
442 }
443 }
444
445 fn undo(&self) {
447 if let Ok(mut undo_stack) = self.undo_stack.lock() {
448 if let Some(snapshot) = undo_stack.pop() {
449 drop(undo_stack);
450 if let Ok(mut state) = self.state.lock() {
451 *state = snapshot;
452 }
453 self.set_last_action(LastAction::None);
454 self.notify_change();
455 }
456 }
457 }
458
459 fn edit(&self, coalesce: bool, mutate: impl FnOnce(&mut InputState) -> bool) {
472 let snapshot = {
473 let Ok(mut state) = self.state.lock() else {
474 return;
475 };
476 let snapshot = if coalesce {
477 None
478 } else {
479 Some(InputState {
480 value: state.value.clone(),
481 cursor: state.cursor,
482 })
483 };
484 if !mutate(&mut state) {
485 return;
486 }
487 snapshot
488 };
489 if let Some(snapshot) = snapshot {
490 if let Ok(mut undo_stack) = self.undo_stack.lock() {
491 undo_stack.push(snapshot);
492 }
493 }
494 self.notify_change();
495 }
496
497 fn get_last_action(&self) -> LastAction {
499 self.last_action
500 .lock()
501 .map(|a| *a)
502 .unwrap_or(LastAction::None)
503 }
504
505 fn set_last_action(&self, action: LastAction) {
507 if let Ok(mut a) = self.last_action.lock() {
508 *a = action;
509 }
510 }
511
512 fn notify_change(&self) {
514 if let Ok(cb) = self.on_change.lock() {
515 if let Some(callback) = cb.as_ref() {
516 let value = self.get_value();
517 callback(&value);
518 }
519 }
520 }
521
522 pub fn handle_key(&self, key: crossterm::event::KeyEvent) {
524 use crossterm::event::{KeyCode, KeyModifiers};
525
526 match (key.code, key.modifiers) {
527 (KeyCode::Enter, _) | (KeyCode::Char('\n'), _) => {
529 if let Ok(cb) = self.on_submit.lock() {
530 if let Some(callback) = cb.as_ref() {
531 let value = self.get_value();
532 callback(&value);
533 }
534 }
535 }
536 (KeyCode::Esc, _) => {
538 if let Ok(cb) = self.on_escape.lock() {
539 if let Some(callback) = cb.as_ref() {
540 callback();
541 }
542 }
543 }
544 (KeyCode::Left, KeyModifiers::CONTROL) => self.move_word_left(),
546 (KeyCode::Right, KeyModifiers::CONTROL) => self.move_word_right(),
547 (KeyCode::Left, _) => self.move_cursor_left(),
548 (KeyCode::Right, _) => self.move_cursor_right(),
549 (KeyCode::Home, _) => self.move_to_line_start(),
550 (KeyCode::End, _) => self.move_to_line_end(),
551 (KeyCode::Backspace, KeyModifiers::CONTROL) => self.delete_word_backwards(),
553 (KeyCode::Delete, KeyModifiers::CONTROL) => self.delete_word_forward(),
554 (KeyCode::Backspace, _) => self.handle_backspace(),
555 (KeyCode::Delete, _) => self.handle_forward_delete(),
556 (KeyCode::Char(c), KeyModifiers::CONTROL) => match c {
558 'a' => self.move_to_line_start(),
559 'e' => self.move_to_line_end(),
560 'b' => self.move_cursor_left(),
561 'f' => self.move_cursor_right(),
562 'w' => self.delete_word_backwards(),
563 'k' => self.delete_to_line_end(),
564 'u' => self.delete_to_line_start(),
565 'y' => self.yank(),
566 '_' | '/' => self.undo(),
567 _ => {}
568 },
569 (KeyCode::Char(c), KeyModifiers::ALT) => match c {
570 'b' => self.move_word_left(),
571 'f' => self.move_word_right(),
572 'd' => self.delete_word_forward(),
573 _ => {}
574 },
575 (KeyCode::Char(c), _) => {
576 self.insert_character(&c.to_string());
577 }
578 _ => {}
579 }
580 }
581}
582
583impl Default for Input {
584 fn default() -> Self {
585 Self::new()
586 }
587}
588
589impl Component for Input {
590 fn render(&self, width: usize) -> Vec<String> {
591 let state = self.state.lock().unwrap_or_else(|e| e.into_inner());
592 let focused = *self.focused.lock().unwrap_or_else(|e| e.into_inner());
593
594 let prompt_len = visible_width(&self.prompt);
595 let available_width = width.saturating_sub(prompt_len);
596
597 if available_width == 0 {
598 return vec![self.prompt.clone()];
599 }
600
601 let value = &state.value;
602 let cursor = state.cursor.min(char_count(value));
603
604 if value.is_empty() && !focused {
606 if let Some(ref placeholder) = self.placeholder {
607 let dimmed = format!("\x1b[90m{}\x1b[0m", placeholder);
608 return vec![format!("{}{}", self.prompt, dimmed)];
609 }
610 }
611
612 let total_width = visible_width(value);
613 let cursor_byte = byte_offset(value, cursor);
614 let cursor_col = visible_width(&value[..cursor_byte]);
615
616 let (visible_text, cursor_display) = if total_width < available_width {
624 (value.clone(), cursor)
625 } else {
626 let scroll_width = if cursor == char_count(value) {
627 available_width.saturating_sub(1)
628 } else {
629 available_width
630 };
631
632 if scroll_width == 0 {
633 (String::new(), 0)
634 } else {
635 let half_width = scroll_width / 2;
636 let start_col = if cursor_col < half_width {
637 0
638 } else if cursor_col > total_width.saturating_sub(half_width) {
639 total_width.saturating_sub(scroll_width)
640 } else {
641 cursor_col.saturating_sub(half_width)
642 };
643 let visible = slice_by_column(value, start_col, scroll_width, true);
644 let caret_cols = cursor_col.saturating_sub(start_col).min(scroll_width);
645 let before_caret = slice_by_column(value, start_col, caret_cols, true);
646 let caret = before_caret.chars().count().min(char_count(&visible));
647 (visible, caret)
648 }
649 };
650
651 let chars: Vec<char> = visible_text.chars().collect();
653 let cursor_display = cursor_display.min(chars.len());
654 let before_cursor: String = chars.iter().take(cursor_display).collect();
655 let at_cursor = chars.get(cursor_display).copied().unwrap_or(' ');
656 let after_cursor: String = chars.iter().skip(cursor_display + 1).collect();
657
658 let marker = if focused { CURSOR_MARKER } else { "" };
659 let cursor_char = if focused {
660 format!("\x1b[7m{}\x1b[27m", at_cursor)
661 } else {
662 at_cursor.to_string()
663 };
664
665 let line = format!(
671 "{}{}{}{}{}{}",
672 self.prompt, before_cursor, marker, cursor_char, after_cursor, "\x1b[0m"
673 );
674
675 vec![line]
676 }
677
678 fn invalidate(&self) {
679 }
681
682 fn as_any(&self) -> &dyn Any {
683 self
684 }
685}
686
687impl Focusable for Input {
688 fn set_focused(&self, focused: bool) {
689 if let Ok(mut f) = self.focused.lock() {
690 *f = focused;
691 }
692 }
693
694 fn is_focused(&self) -> bool {
695 *self.focused.lock().unwrap_or_else(|e| e.into_inner())
696 }
697}
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use crate::ansi::strip_ansi;
702 use crossterm::event::{KeyCode, KeyModifiers};
703
704 fn key(input: &Input, code: KeyCode) {
706 input.handle_key(crossterm::event::KeyEvent::new(code, KeyModifiers::NONE));
707 }
708
709 #[test]
710 fn test_input_value() {
711 let input = Input::new();
712 input.set_value("hello");
713 assert_eq!(input.get_value(), "hello");
714 }
715
716 #[test]
717 fn test_input_clear() {
718 let input = Input::new();
719 input.set_value("hello");
720 input.clear();
721 assert_eq!(input.get_value(), "");
722 }
723
724 #[test]
725 fn test_input_render() {
726 let input = Input::new();
727 input.set_value("hello");
728 let lines = input.render(20);
729 assert_eq!(lines.len(), 1);
730 assert!(lines[0].contains("hello"));
731 }
732
733 #[test]
734 fn test_input_placeholder() {
735 let input = Input::with_placeholder("Enter text...");
736 let lines = input.render(20);
737 assert!(lines[0].contains("Enter text..."));
738 }
739
740 #[test]
745 fn deletion_keys_do_not_deadlock() {
746 use std::sync::mpsc;
747 use std::time::Duration;
748
749 let press = |code: KeyCode, mods: KeyModifiers| {
750 let input = Arc::new(Input::new());
751 input.set_value("hello world");
752 let (tx, rx) = mpsc::channel();
753 let handle = input.clone();
754 std::thread::spawn(move || {
755 handle.handle_key(crossterm::event::KeyEvent::new(code, mods));
756 let _ = tx.send(handle.get_value());
757 });
758 match rx.recv_timeout(Duration::from_secs(5)) {
759 Ok(value) => value,
760 Err(_) => panic!("{code:?} deadlocked"),
761 }
762 };
763
764 assert_eq!(press(KeyCode::Backspace, KeyModifiers::NONE), "hello worl");
765 assert_eq!(press(KeyCode::Delete, KeyModifiers::NONE), "hello world");
766 assert_eq!(press(KeyCode::Backspace, KeyModifiers::CONTROL), "hello ");
767 assert_eq!(
770 press(KeyCode::Char('k'), KeyModifiers::CONTROL),
771 "hello world"
772 );
773 assert_eq!(press(KeyCode::Char('u'), KeyModifiers::CONTROL), "");
774 }
775
776 #[test]
781 fn cursor_marker_sits_at_the_caret() {
782 let input = Input::new();
783 input.set_focused(true);
784 input.set_value("hello world");
785 let line = input.render(40).remove(0);
786 let marker_at = line.find(CURSOR_MARKER).expect("marker present");
787 assert_eq!(
789 visible_width(&line[..marker_at]),
790 visible_width("> hello world"),
791 "caret not at end: {line:?}"
792 );
793 }
794
795 #[test]
799 fn cursor_marker_tracks_scrolled_multibyte_text() {
800 let input = Input::new();
801 input.set_focused(true);
802
803 let long: String = "0123456789".repeat(6);
804 input.set_value(&long);
805 let line = input.render(20).remove(0);
806 let marker_at = line.find(CURSOR_MARKER).expect("marker present");
807 let caret_col = visible_width(&line[..marker_at]);
808 let plain = strip_ansi(&line.replace(CURSOR_MARKER, ""));
809 assert!(
810 caret_col > visible_width("> "),
811 "caret stuck at the prompt: col={caret_col}"
812 );
813 assert!(plain.contains("89"), "window lost the tail: {plain:?}");
814
815 input.set_value("\u{4e2d}\u{6587}\u{6d4b}\u{8bd5}");
817 let line = input.render(40).remove(0);
818 let marker_at = line.find(CURSOR_MARKER).expect("marker present");
819 assert_eq!(
820 visible_width(&line[..marker_at]),
821 visible_width("> \u{4e2d}\u{6587}\u{6d4b}\u{8bd5}")
822 );
823 assert!(strip_ansi(&line).contains("\u{4e2d}\u{6587}\u{6d4b}\u{8bd5}"));
824 }
825
826 #[test]
830 fn deletions_are_char_aware() {
831 let input = Input::new();
832
833 input.set_value("ab\u{4e2d}");
835 key(&input, KeyCode::Backspace);
836 assert_eq!(input.get_value(), "ab");
837 key(&input, KeyCode::Backspace);
838 assert_eq!(input.get_value(), "a");
839
840 input.set_value("\u{4e2d}b");
842 key(&input, KeyCode::Left);
843 key(&input, KeyCode::Delete);
844 assert_eq!(input.get_value(), "\u{4e2d}");
845 key(&input, KeyCode::Left);
846 key(&input, KeyCode::Delete);
847 assert_eq!(input.get_value(), "");
848
849 input.set_value("\u{4e2d}\u{6587}");
851 key(&input, KeyCode::Char('X'));
852 assert_eq!(input.get_value(), "\u{4e2d}\u{6587}X");
853
854 input.set_value("a\u{4e2d}b");
856 key(&input, KeyCode::Left);
857 key(&input, KeyCode::Char('X'));
858 assert_eq!(input.get_value(), "a\u{4e2d}Xb");
859 key(&input, KeyCode::Backspace);
860 assert_eq!(input.get_value(), "a\u{4e2d}b");
861 key(&input, KeyCode::Backspace);
862 assert_eq!(input.get_value(), "ab");
863 }
864}