1mod os_specific_apis;
7mod read_provider;
8pub mod replacement;
9mod state;
10
11use crate::{
12 app_name_to_prefix,
13 errors::LisaError,
14 input::{
15 InputProvider, TerminalInputEvent,
16 autocomplete::AutocompleteProvider,
17 history::HistoryProvider,
18 stdin::{
19 os_specific_apis::{
20 CachedModeType, default_cached_mode, disable_raw_mode, enable_raw_mode,
21 os_pre_reqs, raise_sigint,
22 },
23 read_provider::ReadProvider,
24 replacement::parse_character_replacements,
25 state::{StdinInputState, transaction::InputStateTransaction},
26 },
27 },
28};
29use arboard::Clipboard;
30use fnv::FnvHashMap;
31use parking_lot::Mutex;
32#[cfg(test)]
33use std::collections::VecDeque;
34use std::{
35 env::var as env_var,
36 hash::BuildHasherDefault,
37 io::Stdin,
38 sync::{
39 OnceLock,
40 atomic::{AtomicBool, Ordering},
41 },
42};
43
44type NewlineHookFn = dyn Fn(&str) -> bool + Send + Sync;
47
48static CLIPBOARD_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
55
56#[cfg(test)]
58static TEST_OVERRIDE_CLIPBOARD: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
59
60pub struct StdinInputProvider<ReadTy: ReadProvider> {
62 active: AtomicBool,
64 autocomplete_provider: Option<Box<dyn AutocompleteProvider>>,
66 cached_mode: CachedModeType,
68 current_input: StdinInputState,
70 completed_inputs: Vec<String>,
72 history_provider: Option<Box<dyn HistoryProvider>>,
74 in_raw_mode: AtomicBool,
76 newline_hook: Option<Box<NewlineHookFn>>,
78 read_provider: ReadTy,
80 replacements: FnvHashMap<char, Option<char>>,
82}
83
84impl StdinInputProvider<Stdin> {
85 pub fn new(app_name: &'static str) -> Result<Self, LisaError> {
98 os_pre_reqs()?;
99
100 let environment_prefix = app_name_to_prefix(app_name);
101 let replacements =
102 if let Ok(env_var_value) = env_var(format!("{environment_prefix}_STTY_REPLACEMENTS")) {
103 parse_character_replacements(&env_var_value)?
104 } else {
105 FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default())
106 };
107
108 Ok(Self {
109 active: AtomicBool::new(false),
110 autocomplete_provider: None,
111 cached_mode: default_cached_mode(),
112 current_input: StdinInputState::new(),
113 completed_inputs: Vec::with_capacity(1),
114 history_provider: None,
115 in_raw_mode: AtomicBool::new(false),
116 newline_hook: None,
117 read_provider: std::io::stdin(),
118 replacements,
119 })
120 }
121}
122
123impl<ReadTy: ReadProvider> StdinInputProvider<ReadTy> {
124 pub fn new_with_provider(app_name: &'static str, provider: ReadTy) -> Result<Self, LisaError> {
137 os_pre_reqs()?;
138
139 let environment_prefix = app_name_to_prefix(app_name);
140 let replacements =
141 if let Ok(env_var_value) = env_var(format!("{environment_prefix}_STTY_REPLACEMENTS")) {
142 parse_character_replacements(&env_var_value)?
143 } else {
144 FnvHashMap::with_capacity_and_hasher(0, BuildHasherDefault::default())
145 };
146
147 Ok(Self {
148 active: AtomicBool::new(false),
149 autocomplete_provider: None,
150 cached_mode: default_cached_mode(),
151 current_input: StdinInputState::new(),
152 completed_inputs: Vec::with_capacity(1),
153 history_provider: None,
154 in_raw_mode: AtomicBool::new(false),
155 newline_hook: None,
156 read_provider: provider,
157 replacements,
158 })
159 }
160
161 pub fn manually_poll_input_and_consume(&self) -> Result<String, LisaError> {
170 self.read_provider.non_blocking_read().map(|string| {
171 string
172 .chars()
173 .filter_map(|character| {
174 if let Some(replacement) = self.replacements.get(&character) {
175 *replacement
176 } else {
177 Some(character)
178 }
179 })
180 .collect::<String>()
181 })
182 }
183
184 pub fn set_newline_hook(&mut self, hook: Box<dyn Fn(&str) -> bool + Send + Sync>) {
190 _ = self.newline_hook.insert(hook);
191 }
192
193 fn process_inputs(&mut self, item: &str, ansi_supported: bool) -> Vec<TerminalInputEvent> {
199 let mut input_events = Vec::with_capacity(0);
201 let had_started = self.current_input.started();
203 let append_ledger = self.calculate_append_ledger(&mut input_events, item, ansi_supported);
204
205 let item_events = if let Some(ledger) = append_ledger {
206 if ledger.is_empty() {
207 Vec::with_capacity(0)
208 } else if ledger.len() == 1
209 && let Some(char) = ledger.chars().next()
210 {
211 vec![TerminalInputEvent::InputAppend(char)]
212 } else if !ledger.is_empty() {
213 vec![TerminalInputEvent::InputMassAppend(ledger)]
214 } else {
215 Vec::with_capacity(0)
216 }
217 } else if !input_events.last().is_some_and(|ie| {
218 matches!(
219 ie,
220 TerminalInputEvent::CursorMoveLeft(_) | TerminalInputEvent::CursorMoveRight(_)
221 )
222 }) {
223 input_events.truncate(0);
224 if had_started {
225 vec![TerminalInputEvent::InputChanged(
226 self.current_input.cursor_position(),
227 )]
228 } else {
229 vec![
230 TerminalInputEvent::InputStarted,
231 TerminalInputEvent::InputChanged(self.current_input.cursor_position()),
232 ]
233 }
234 } else {
235 let buffer = self.current_input();
237 if buffer.is_empty() {
238 Vec::with_capacity(0)
239 } else {
240 input_events.truncate(0);
241 if had_started {
242 vec![TerminalInputEvent::InputChanged(
243 self.current_input.cursor_position(),
244 )]
245 } else {
246 vec![
247 TerminalInputEvent::InputStarted,
248 TerminalInputEvent::InputChanged(self.current_input.cursor_position()),
249 ]
250 }
251 }
252 };
253 input_events.extend(item_events);
254 input_events
255 }
256
257 fn calculate_append_ledger(
262 &mut self,
263 input_events: &mut Vec<TerminalInputEvent>,
264 item: &str,
265 ansi_supported: bool,
266 ) -> Option<String> {
267 let mut processing = self.current_input.process();
268
269 let mut processed_amount = 0_usize;
270 for character in item.chars() {
271 processed_amount += 1;
272 if Self::route_character(
273 self.autocomplete_provider.as_deref(),
274 self.history_provider.as_deref(),
275 self.newline_hook.as_deref(),
276 ansi_supported,
277 character,
278 &mut processing,
279 ) {
280 std::mem::drop(processing);
282 self.raise_sigint();
283 return None;
284 }
285
286 if let Some((move_left, move_amount, buffer, original_position)) = processing
287 .did_trigger_multibyte_cursor(
288 self.autocomplete_provider.as_deref(),
289 self.history_provider.as_deref(),
290 ansi_supported,
291 ) {
292 if let Some(buff) = buffer {
293 if buff.is_empty() {
294 } else if buff.len() == 1 {
296 input_events.push(TerminalInputEvent::InputAppend(
297 buff.chars().next().unwrap_or_else(|| unreachable!()),
298 ));
299 } else {
300 input_events.push(TerminalInputEvent::InputMassAppend(buff));
301 }
302 } else if processed_amount > 0 {
303 input_events.push(TerminalInputEvent::InputChanged(original_position));
304 }
305
306 if move_left {
307 input_events.push(TerminalInputEvent::CursorMoveLeft(move_amount));
308 } else {
309 input_events.push(TerminalInputEvent::CursorMoveRight(move_amount));
310 }
311 }
312 if processing.did_trigger_multibyte_paste()
313 && let Some(data) = Self::get_clipboard()
314 {
315 processing.new_string_action(&data, self.history_provider.as_deref());
316 }
317 if processing.did_trigger_start() {
318 input_events.push(TerminalInputEvent::InputStarted);
319 }
320 if processing.did_trigger_cancel() {
321 if input_events.last() == Some(&TerminalInputEvent::InputStarted) {
322 input_events.pop();
323 } else {
324 input_events.push(TerminalInputEvent::InputCancelled);
325 }
326 }
327 if processing.did_trigger_clear() {
328 input_events.push(TerminalInputEvent::ClearScreen);
329 input_events.push(TerminalInputEvent::InputChanged(
331 processing.cursor_position(),
332 ));
333 }
334 if processing.did_trigger_pause() {
335 input_events.push(TerminalInputEvent::ToggleOutputPause);
336 }
337 if let Some((append_buffer, finished)) = processing.did_trigger_finish() {
338 if let Some(hprovider) = self.history_provider.as_ref() {
339 hprovider.insert_command(&finished);
340 }
341 self.completed_inputs.push(finished);
342
343 if let Some(append) = append_buffer {
344 if append.len() == 1
345 && let Some(character) = append.chars().next()
346 {
347 input_events.push(TerminalInputEvent::InputAppend(character));
348 } else if !append.is_empty() {
349 input_events.push(TerminalInputEvent::InputMassAppend(append));
350 }
351 } else {
352 input_events.push(TerminalInputEvent::InputChanged(
353 processing.cursor_position(),
354 ));
355 }
356 input_events.push(TerminalInputEvent::InputFinished);
357 }
358 }
359
360 processing.finished_processing()
361 }
362
363 fn route_character(
367 autocomplete_provider: Option<&dyn AutocompleteProvider>,
368 history_provider: Option<&dyn HistoryProvider>,
369 newline_hook: Option<&NewlineHookFn>,
370 ansi_supported: bool,
371 character: char,
372 processing: &mut InputStateTransaction<'_>,
373 ) -> bool {
374 match character {
375 '\u{0}' => {}
378 '\u{7f}' | '\u{8}' => processing.backspace(),
380 '\u{3}' => {
382 if !processing.cancel() {
383 return true;
384 }
385 }
386 '\u{4}' => {
392 return true;
393 }
394 '\u{16}' => {
396 if let Some(data) = Self::get_clipboard() {
397 processing.new_string_action(&data, history_provider);
398 }
399 }
400 '\u{10}' => processing.previous_history_action(history_provider),
402 '\u{e}' => processing.next_history_action(history_provider),
404 '\u{c}' => processing.clear(),
406 '\u{13}' => processing.pause_output(),
408 '\u{2}' => {
410 if ansi_supported {
411 _ = processing.move_cursor_left(1);
412 }
413 }
414 '\u{6}' => {
416 if ansi_supported {
417 _ = processing.move_cursor_right(1);
418 }
419 }
420 '\u{1}' => {
422 if ansi_supported {
423 _ = processing.move_cursor_left(processing.cursor_position() + 1);
424 }
425 }
426 '\u{5}' => {
428 if ansi_supported {
429 _ = processing.move_cursor_right(processing.current_input().len());
430 }
431 }
432 '\u{15}' => {
434 if ansi_supported {
435 processing.uwk_cursor_to_begin();
436 }
437 }
438 '\u{b}' => {
440 if ansi_supported {
441 processing.uwk_cursor_to_end();
442 }
443 }
444 '\u{17}' => {
446 if ansi_supported {
447 processing.uwk_cursor_word();
448 }
449 }
450 '\u{19}' => {
452 if ansi_supported {
453 processing.paste_uwk();
454 }
455 }
456 '\u{14}' => {
458 if ansi_supported {
459 processing.swap_last_two_characters();
460 }
461 }
462 '\u{7}' => _ = processing.cancel(),
464 '\u{12}' => {
466 if ansi_supported {
467 processing.start_history_search_action(history_provider);
468 }
469 }
470 '\n' => processing.complete(),
474 '\r' => {
476 if let Some(hook) = newline_hook {
477 if (*hook)(processing.current_input().as_ref()) {
478 processing.complete();
479 } else {
480 processing.new_character_action('\n', history_provider);
481 }
482 } else {
483 processing.complete();
484 }
485 }
486 '\t' => processing.tab_action(autocomplete_provider, history_provider, true, false),
488 _ => processing.new_character_action(character, history_provider),
490 }
491
492 false
493 }
494
495 fn get_clipboard() -> Option<String> {
496 #[cfg(test)]
498 {
499 let mut clipboard = TEST_OVERRIDE_CLIPBOARD
500 .get_or_init(|| Mutex::new(VecDeque::new()))
501 .lock();
502 if let Some(value) = clipboard.pop_front() {
503 return Some(value);
504 }
505 }
506
507 let guard = CLIPBOARD_LOCK.get_or_init(|| Mutex::new(())).lock();
508 let Ok(mut clipboard) = Clipboard::new() else {
512 return None;
513 };
514 let text = clipboard.get_text().ok();
515 std::mem::drop(guard);
516 text
517 }
518
519 fn enable_raw_mode(&mut self) -> Result<(), LisaError> {
520 if self.in_raw_mode.swap(true, Ordering::AcqRel) {
521 return Ok(());
522 }
523
524 self.cached_mode = enable_raw_mode()?;
525 Ok(())
526 }
527
528 fn disable_raw_mode(&mut self) -> Result<(), LisaError> {
529 if !self.in_raw_mode.swap(false, Ordering::AcqRel) {
530 return Ok(());
531 }
532
533 self.cached_mode = disable_raw_mode(self.cached_mode)?;
534 Ok(())
535 }
536
537 fn raise_sigint(&mut self) {
538 _ = self.disable_raw_mode();
542 if let Some(hprovider) = self.history_provider.as_ref() {
543 hprovider.attempt_to_do_full_sync();
544 }
545 raise_sigint();
546 }
547}
548
549impl<ReadTy: ReadProvider> InputProvider for StdinInputProvider<ReadTy> {
550 fn is_stdin(&self) -> bool {
551 true
552 }
553
554 fn is_active(&self) -> bool {
555 self.active.load(Ordering::Relaxed)
556 }
557 fn set_active(&mut self, active: bool) -> Result<(), LisaError> {
558 if active {
559 self.enable_raw_mode()?;
560 } else {
561 self.disable_raw_mode()?;
562 }
563
564 self.active.store(active, Ordering::SeqCst);
565 Ok(())
566 }
567
568 fn input_in_progress(&self) -> bool {
569 self.current_input.started()
570 }
571 fn current_input(&self) -> String {
572 self.current_input.input()
573 }
574 fn poll_for_input(&mut self, ansi_supported: bool) -> Vec<TerminalInputEvent> {
575 let read_value = self.read_provider.non_blocking_read().map(|string| {
576 string
577 .chars()
578 .filter_map(|character| {
579 if let Some(replacement) = self.replacements.get(&character) {
580 *replacement
581 } else {
582 Some(character)
583 }
584 })
585 .collect::<String>()
586 });
587
588 match read_value {
589 Ok(item) => self.process_inputs(&item, ansi_supported),
590 Err(_cause) => Vec::with_capacity(0),
591 }
592 }
593
594 fn set_autocomplete_provider(&mut self, autocomplete: Box<dyn AutocompleteProvider>) {
595 _ = self.autocomplete_provider.insert(autocomplete);
596 }
597 fn current_autocomplete_suggestion(&self) -> Option<String> {
598 self.autocomplete_provider
599 .as_ref()
600 .and_then(|ap| ap.get_displayable_suggestion(self.current_input.input()))
601 }
602 fn autocomplete_suggestion_pending(&self) -> bool {
603 self.current_input.autocomplete_pending()
604 }
605
606 fn set_history_provider(&mut self, history: Box<dyn HistoryProvider>) {
608 _ = self.history_provider.insert(history);
609 }
610 fn current_history_search_value(&self) -> Option<String> {
611 self.current_input.current_history_search_value()
612 }
613 fn is_doing_history_search(&self) -> bool {
614 self.current_input.is_doing_history_search()
615 }
616
617 fn inputs(&mut self) -> Vec<String> {
618 self.completed_inputs.drain(..).collect()
619 }
620}
621
622impl<ReadTy: ReadProvider> Drop for StdinInputProvider<ReadTy> {
623 fn drop(&mut self) {
624 if self.in_raw_mode.load(Ordering::Relaxed) {
625 _ = self.disable_raw_mode();
626 }
627 }
628}
629
630#[derive(Copy, Clone, Debug, PartialEq, Eq)]
632enum MultibyteCursorControl {
633 ShiftTab,
635 UpArrow,
637 DownArrow,
639 LeftArrow,
641 RightArrow,
643 ShiftLeftArrow,
645 ShiftRightArrow,
647}
648
649#[cfg(test)]
650pub mod test_helpers {
651 use super::*;
652
653 pub fn insert_next_paste_value(new_value: String) {
655 let mut clipboard = TEST_OVERRIDE_CLIPBOARD
656 .get_or_init(|| Mutex::new(VecDeque::new()))
657 .lock();
658 clipboard.push_back(new_value);
659 }
660}
661
662#[cfg(test)]
663mod unit_tests {
664 use super::{test_helpers::insert_next_paste_value, *};
665 use crate::input::{
666 autocomplete::test_helpers::AutocompleteSuggestionList, history::SimpleHistoryProvider,
667 };
668 use parking_lot::Mutex;
669 use tokio::sync::mpsc::channel;
670
671 #[test]
672 pub fn always_stdin() {
673 {
675 let provider = StdinInputProvider::new("test")
676 .expect("Failed to create new stdin input provider!");
677 assert!(provider.is_stdin());
678 }
679
680 {
682 let (_sender, raw_receiver) = channel(8);
683 let read_provider = Mutex::new(raw_receiver);
684 let provider = StdinInputProvider::new_with_provider("test", read_provider)
685 .expect("Failed to create new stdin input provider!");
686
687 assert!(provider.is_stdin());
688 }
689 }
690
691 #[tokio::test]
693 pub async fn manually_poll_for_input_consumes() {
694 let (sender, raw_receiver) = channel(8);
695 let read_provider = Mutex::new(raw_receiver);
696 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
697 .expect("Failed to create new stdin input provider!");
698 sender
699 .send("abcdefghijklmnopqrstuvwxyz".to_owned())
700 .await
701 .expect("Failed to send example input");
702
703 assert_eq!(
704 provider
705 .manually_poll_input_and_consume()
706 .expect("Failed to manually poll input and consume"),
707 "abcdefghijklmnopqrstuvwxyz",
708 );
709 assert_eq!(provider.poll_for_input(true), Vec::with_capacity(0));
710 assert!(provider.manually_poll_input_and_consume().is_err());
711 }
712
713 #[tokio::test]
714 pub async fn simple_type_enter_with_hooks_and_without_hooks() {
715 {
717 let (sender, raw_receiver) = channel(8);
718 let read_provider = Mutex::new(raw_receiver);
719 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
720 .expect("Failed to create new stdin input provider!");
721
722 sender
723 .send("abcdefghijklmnopqrstuvwxyz(\r".to_owned())
724 .await
725 .expect("Failed to send example input");
726 assert_eq!(
727 provider.poll_for_input(true),
728 vec![
729 TerminalInputEvent::InputStarted,
730 TerminalInputEvent::InputMassAppend("abcdefghijklmnopqrstuvwxyz(".to_owned()),
731 TerminalInputEvent::InputFinished,
732 ],
733 );
734
735 provider.set_newline_hook(Box::new(|data: &str| -> bool {
736 data.chars().filter(|character| *character == '(').count()
737 == data.chars().filter(|character| *character == ')').count()
738 }));
739 sender
740 .send("abcdefghijklmnopqrstuvwxyz(\r".to_owned())
741 .await
742 .expect("Failed to send example input");
743 assert_eq!(
744 provider.poll_for_input(true),
745 vec![
746 TerminalInputEvent::InputStarted,
747 TerminalInputEvent::InputMassAppend("abcdefghijklmnopqrstuvwxyz(\n".to_owned()),
748 ],
749 );
750 sender
751 .send(")\r".to_owned())
752 .await
753 .expect("Failed to send example input!");
754 assert_eq!(
755 provider.poll_for_input(true),
756 vec![
757 TerminalInputEvent::InputAppend(')'),
758 TerminalInputEvent::InputFinished,
759 ],
760 );
761 }
762
763 {
765 let (sender, raw_receiver) = channel(8);
766 let read_provider = Mutex::new(raw_receiver);
767 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
768 .expect("Failed to create new stdin input provider!");
769
770 sender
771 .send("abcdefghijklmnopqrstuvwxyz(\r".to_owned())
772 .await
773 .expect("Failed to send example input");
774 assert_eq!(
775 provider.poll_for_input(false),
776 vec![
777 TerminalInputEvent::InputStarted,
778 TerminalInputEvent::InputMassAppend("abcdefghijklmnopqrstuvwxyz(".to_owned()),
779 TerminalInputEvent::InputFinished,
780 ],
781 );
782
783 provider.set_newline_hook(Box::new(|data: &str| -> bool {
784 data.chars().filter(|character| *character == '(').count()
785 == data.chars().filter(|character| *character == ')').count()
786 }));
787 sender
788 .send("abcdefghijklmnopqrstuvwxyz(\r".to_owned())
789 .await
790 .expect("Failed to send example input");
791 assert_eq!(
792 provider.poll_for_input(false),
793 vec![
794 TerminalInputEvent::InputStarted,
795 TerminalInputEvent::InputMassAppend("abcdefghijklmnopqrstuvwxyz(\n".to_owned()),
796 ],
797 );
798 sender
799 .send(")\r".to_owned())
800 .await
801 .expect("Failed to send example input!");
802 assert_eq!(
803 provider.poll_for_input(false),
804 vec![
805 TerminalInputEvent::InputAppend(')'),
806 TerminalInputEvent::InputFinished,
807 ],
808 );
809 }
810 }
811
812 #[tokio::test]
813 pub async fn respects_replacements() {
814 unsafe {
815 std::env::set_var(
816 "TEST_TOTALLY_UNIQUE_RESPECTS_REPLACEMENTS_STTY_REPLACEMENTS",
817 "z=0xd",
818 );
819 }
820
821 let mut expected_replacement_map = FnvHashMap::default();
822 expected_replacement_map.insert('z', Some('\r'));
823 {
825 let state = StdinInputProvider::new("test-totally-unique-respects-replacements")
826 .expect("Failed to create stdininputprovider");
827 assert_eq!(state.replacements, expected_replacement_map);
828 }
829 {
831 let (sender, raw_receiver) = channel(8);
832 let read_provider = Mutex::new(raw_receiver);
833 let mut provider = StdinInputProvider::new_with_provider(
834 "test-totally-unique-respects-replacements",
835 read_provider,
836 )
837 .expect("Failed to create new stdin input provider!");
838
839 sender
840 .send("abcdefghijklmnopqrstuvwxy(z".to_owned())
841 .await
842 .expect("Failed to send example input");
843 assert_eq!(
844 provider.poll_for_input(false),
845 vec![
846 TerminalInputEvent::InputStarted,
847 TerminalInputEvent::InputMassAppend("abcdefghijklmnopqrstuvwxy(".to_owned()),
848 TerminalInputEvent::InputFinished,
849 ],
850 );
851 sender
852 .send("abcdefghijklmnopqrstuvwxy(z".to_owned())
853 .await
854 .expect("Failed to send example input");
855 assert_eq!(
856 provider
857 .manually_poll_input_and_consume()
858 .expect("Failed to manually poll input and consume!"),
859 "abcdefghijklmnopqrstuvwxy(\r".to_owned(),
860 );
861 }
862
863 unsafe {
865 std::env::set_var(
866 "TEST_TOTALLY_UNIQUE_RESPECTS_REPLACEMENTS_STTY_REPLACEMENTS",
867 "undef=",
868 );
869 }
870
871 assert!(
872 StdinInputProvider::new("test-totally-unique-respects-replacements").is_err(),
873 "Invalid STTY replacements did not error `StdinInputProvider`",
874 );
875 assert!(
876 StdinInputProvider::new_with_provider(
877 "test-totally-unique-respects-replacements",
878 std::io::stdin(),
879 )
880 .is_err(),
881 "Invalid STTY replacements did not error `StdinInputProvider`",
882 );
883 }
884
885 #[tokio::test]
886 pub async fn append_ledger_to_events() {
887 let (sender, raw_receiver) = channel(8);
888 let read_provider = Mutex::new(raw_receiver);
889 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
890 .expect("Failed to create new stdin input provider!");
891
892 sender
894 .send("a\u{7f}".to_owned())
895 .await
896 .expect("Failed to start input!");
897 _ = provider.poll_for_input(true);
898 sender
900 .send("".to_owned())
901 .await
902 .expect("Failed to send input");
903 assert_eq!(provider.poll_for_input(true), vec![]);
904 sender
906 .send("a".to_owned())
907 .await
908 .expect("Failed to send input!");
909 assert_eq!(
910 provider.poll_for_input(true),
911 vec![TerminalInputEvent::InputAppend('a')]
912 );
913 sender
915 .send("bcd".to_owned())
916 .await
917 .expect("Failed to send input!");
918 assert_eq!(
919 provider.poll_for_input(true),
920 vec![TerminalInputEvent::InputMassAppend("bcd".to_owned())],
921 );
922
923 sender
925 .send("\u{7f}a".to_owned())
926 .await
927 .expect("failed to send input!");
928 assert_eq!(
929 provider.poll_for_input(true),
930 vec![TerminalInputEvent::InputChanged(4)],
931 );
932 sender
935 .send("\u{7f}abcdef\u{1b}[D\u{7f}c".to_owned())
936 .await
937 .expect("");
938 assert_eq!(
939 provider.poll_for_input(true),
940 vec![TerminalInputEvent::InputChanged(8)],
941 );
942 }
943
944 #[tokio::test]
945 pub async fn cursor_operations_no_happen_when_no_ansi() {
946 let (sender, raw_receiver) = channel(8);
947 let read_provider = Mutex::new(raw_receiver);
948 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
949 .expect("Failed to create new stdin input provider!");
950
951 sender
953 .send("abcdefghijklmnopqrstuvwxyz".to_owned())
954 .await
955 .expect("Failed to send base input data");
956 _ = provider.poll_for_input(false);
957 sender.send(
958 "\u{2}\u{2}\u{2}\u{6}\u{5}\u{1}\u{6}\u{15}\u{19}\u{17}\u{19}\u{b}\u{14}\u{12}\u{1b}[C\u{1b}[C\u{1b}[D\u{1b}[1;2C\u{1b}[1;2D".to_owned()
959 ).await.expect("Failed to send input data!");
960 assert_eq!(provider.poll_for_input(false), Vec::with_capacity(0));
961 assert_eq!(provider.current_input.cursor_position(), 26);
962
963 sender
964 .send("\u{3}".to_owned())
965 .await
966 .expect("Failed to send cancel!");
967 _ = provider.poll_for_input(true);
968
969 sender
970 .send("abcdefghijklmnopqrstuvwxyz".to_owned())
971 .await
972 .expect("Failed to send base input data");
973 _ = provider.poll_for_input(false);
974 sender.send(
975 "\u{2}\u{2}\u{2}\u{6}\u{5}\u{1}\u{6}\u{15}\u{19}\u{17}\u{19}\u{b}\u{14}\u{12}\u{1b}[C\u{1b}[C\u{1b}[D\u{1b}[1;2C\u{1b}[1;2D".to_owned()
976 ).await.expect("Failed to send input data!");
977 assert_eq!(
978 provider.poll_for_input(true),
979 vec![
980 TerminalInputEvent::InputChanged(26),
981 TerminalInputEvent::CursorMoveLeft(1),
982 TerminalInputEvent::InputChanged(26),
983 TerminalInputEvent::CursorMoveRight(1),
984 TerminalInputEvent::CursorMoveLeft(26),
985 ],
986 );
987 assert_eq!(provider.current_input.cursor_position(), 0);
988 }
989
990 #[cfg(feature = "tests-with-signals")]
991 #[tokio::test]
992 pub async fn ctrl_c_and_ctrl_d() {
993 use std::sync::{Arc, atomic::AtomicU8};
994 let (sender, raw_receiver) = channel(8);
995 let read_provider = Mutex::new(raw_receiver);
996 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
997 .expect("Failed to create new stdin input provider!");
998
999 let ctrl_c_handler = Arc::new(AtomicU8::new(0));
1000 let cloned_handler = ctrl_c_handler.clone();
1001 ctrlc::set_handler(move || {
1002 cloned_handler.fetch_add(1, Ordering::SeqCst);
1003 })
1004 .expect("Failed to set ctrl-c handler!");
1005
1006 sender
1007 .send("abcdefghijklmnopqrstuvwxyz\u{3}".to_owned())
1008 .await
1009 .expect("Failed to send first input + ctrl-c");
1010 _ = provider.poll_for_input(true);
1012 assert_eq!(ctrl_c_handler.load(Ordering::SeqCst), 0);
1013 sender
1014 .send("abcdefghijklmnopqrstuvwxyz\u{3}".to_owned())
1015 .await
1016 .expect("Failed to send first input + ctrl-c");
1017 _ = provider.poll_for_input(false);
1018 assert_eq!(ctrl_c_handler.load(Ordering::SeqCst), 0);
1019 sender
1020 .send("\u{3}".to_owned())
1021 .await
1022 .expect("Failed to send second ctrl-c!");
1023 _ = provider.poll_for_input(true);
1024 sender
1025 .send("\u{3}".to_owned())
1026 .await
1027 .expect("Failed to send second ctrl-c!");
1028 _ = provider.poll_for_input(false);
1029 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
1031 assert_eq!(ctrl_c_handler.load(Ordering::SeqCst), 2);
1032
1033 sender
1034 .send("\u{4}".to_owned())
1035 .await
1036 .expect("Failed to send second ctrl-c!");
1037 _ = provider.poll_for_input(true);
1038 sender
1039 .send("\u{4}".to_owned())
1040 .await
1041 .expect("Failed to send second ctrl-c!");
1042 _ = provider.poll_for_input(false);
1043 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
1045 assert_eq!(ctrl_c_handler.load(Ordering::SeqCst), 4);
1046 }
1047
1048 #[tokio::test]
1049 pub async fn autocomplete() {
1050 let suggestion = Box::new(AutocompleteSuggestionList(vec![
1051 "command long".to_owned(),
1052 "command short".to_owned(),
1053 "command longer".to_owned(),
1054 "command shorter".to_owned(),
1055 "other command".to_owned(),
1056 ]));
1057
1058 let (sender, raw_receiver) = channel(8);
1059 let read_provider = Mutex::new(raw_receiver);
1060 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
1061 .expect("Failed to create new stdin input provider!");
1062 provider.set_autocomplete_provider(suggestion);
1063
1064 sender
1065 .send("command ".to_owned())
1066 .await
1067 .expect("Failed to send start of input");
1068 _ = provider.poll_for_input(true);
1069 assert!(provider.input_in_progress());
1070 assert!(!provider.autocomplete_suggestion_pending());
1071 assert_eq!(
1072 provider.current_autocomplete_suggestion(),
1073 Some("long".to_owned()),
1074 );
1075
1076 sender
1077 .send("\t".to_owned())
1078 .await
1079 .expect("Failed to send tab input!");
1080 assert_eq!(
1082 provider.poll_for_input(true),
1083 vec![TerminalInputEvent::InputChanged(12)],
1084 );
1085 assert!(provider.autocomplete_suggestion_pending());
1086 assert_eq!(provider.current_input(), "command long");
1087
1088 sender
1090 .send("\t".to_owned())
1091 .await
1092 .expect("Failed to send tab input!");
1093 assert_eq!(
1094 provider.poll_for_input(true),
1095 vec![TerminalInputEvent::InputChanged(13)],
1096 );
1097 assert!(provider.autocomplete_suggestion_pending());
1098 assert_eq!(provider.current_input(), "command short");
1099
1100 sender
1102 .send("\t".to_owned())
1103 .await
1104 .expect("Failed to send tab input!");
1105 assert_eq!(
1106 provider.poll_for_input(true),
1107 vec![TerminalInputEvent::InputChanged(14)],
1108 );
1109 assert!(provider.autocomplete_suggestion_pending());
1110 assert_eq!(provider.current_input(), "command longer");
1111
1112 sender
1113 .send("\u{1b}[Z".to_owned())
1114 .await
1115 .expect("Failed to send shift tab input!");
1116 assert_eq!(
1117 provider.poll_for_input(true),
1118 vec![TerminalInputEvent::InputChanged(13)],
1119 "Post shift tab Current input is: [{}] @ {}",
1120 provider.current_input(),
1121 provider.current_input.cursor_position(),
1122 );
1123 assert!(provider.autocomplete_suggestion_pending());
1124 assert_eq!(provider.current_input(), "command short");
1125
1126 sender
1127 .send("a".to_owned())
1128 .await
1129 .expect("Failed to send promotion input!");
1130 assert_eq!(
1131 provider.poll_for_input(true),
1132 vec![TerminalInputEvent::InputAppend('a')],
1133 );
1134 assert!(!provider.autocomplete_suggestion_pending());
1135 assert_eq!(provider.current_input(), "command shorta");
1136
1137 sender
1139 .send("\u{3}".to_owned())
1140 .await
1141 .expect("Failed to send cancellation!");
1142 _ = provider.poll_for_input(true);
1143
1144 sender
1146 .send("command \u{1b}[D\u{1b}[D\u{1b}[D\u{1b}[D\u{1b}[D".to_owned())
1147 .await
1148 .expect("Failed to send start of input");
1149 _ = provider.poll_for_input(true);
1150 assert!(provider.input_in_progress());
1151 assert!(!provider.autocomplete_suggestion_pending());
1152 assert_eq!(
1153 provider.current_autocomplete_suggestion(),
1154 Some("long".to_owned()),
1155 );
1156
1157 sender
1158 .send("\t\u{1b}[C\u{1b}[C\u{1b}[C\u{1b}[C\u{1b}[C".to_owned())
1159 .await
1160 .expect("Failed to start autocomplete input!");
1161 assert_eq!(
1162 provider.poll_for_input(true),
1163 vec![TerminalInputEvent::InputChanged(12)],
1164 "Current input is: [{:?}] @ {}",
1165 provider.current_input(),
1166 provider.current_input.cursor_position(),
1167 );
1168 assert!(!provider.autocomplete_suggestion_pending());
1169 assert_eq!(provider.current_input(), "command long".to_owned());
1170 }
1171
1172 #[tokio::test]
1173 pub async fn history_non_search() {
1174 let history_provider = Box::new(SimpleHistoryProvider::new_in_memory(10));
1175 history_provider.insert_command("previous command");
1176 history_provider.insert_command("short");
1177 history_provider.insert_command("cos sdkversion");
1178
1179 let (sender, raw_receiver) = channel(8);
1180 let read_provider = Mutex::new(raw_receiver);
1181 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
1182 .expect("Failed to create new stdin input provider!");
1183 provider.set_history_provider(history_provider);
1184
1185 sender
1187 .send("new command\rtest".to_owned())
1188 .await
1189 .expect("Failed to send newer command");
1190 _ = provider.poll_for_input(false);
1191
1192 sender
1193 .send("\u{1b}[A".to_owned())
1194 .await
1195 .expect("Failed to send up arrow!");
1196 assert_eq!(
1197 provider.poll_for_input(false),
1198 vec![TerminalInputEvent::InputChanged(4)],
1199 );
1200 assert_eq!(provider.current_input(), "new command");
1201
1202 sender
1203 .send("\u{1b}[A".to_owned())
1204 .await
1205 .expect("Failed to send up arrow!");
1206 assert_eq!(
1207 provider.poll_for_input(true),
1208 vec![TerminalInputEvent::InputChanged(4)],
1209 "Current input is: [{:?}] @ {}",
1210 provider.current_input(),
1211 provider.current_input.cursor_position(),
1212 );
1213 assert_eq!(provider.current_input(), "cos sdkversion");
1214
1215 sender
1217 .send("\u{1b}[C\u{1b}[C\u{1b}[C\u{1b}[C\u{1b}[C\u{1b}[C".to_owned())
1218 .await
1219 .expect("failed to send some right arrows!");
1220 _ = provider.poll_for_input(true);
1221
1222 sender
1223 .send("\u{1b}[A".to_owned())
1224 .await
1225 .expect("Failed to send up arrow!");
1226 assert_eq!(
1228 provider.poll_for_input(true),
1229 vec![TerminalInputEvent::InputChanged(5)],
1230 "Current input is: [{:?}] @ {}",
1231 provider.current_input(),
1232 provider.current_input.cursor_position(),
1233 );
1234 assert_eq!(provider.current_input(), "short");
1235
1236 sender
1237 .send("\u{1b}[A".to_owned())
1238 .await
1239 .expect("Failed to send up arrow!");
1240 assert_eq!(
1242 provider.poll_for_input(true),
1243 vec![TerminalInputEvent::InputChanged(5)],
1244 "Current input is: [{:?}] @ {}",
1245 provider.current_input(),
1246 provider.current_input.cursor_position(),
1247 );
1248 assert_eq!(provider.current_input(), "previous command");
1249
1250 sender
1252 .send("\u{1b}[C\u{1b}[C\u{1b}[B".to_owned())
1253 .await
1254 .expect("Failed to send up arrow!");
1255 assert_eq!(
1256 provider.poll_for_input(true),
1257 vec![TerminalInputEvent::InputChanged(5)],
1258 "Current input is: [{:?}] @ {}",
1259 provider.current_input(),
1260 provider.current_input.cursor_position(),
1261 );
1262 assert_eq!(provider.current_input(), "short");
1263
1264 sender
1266 .send("\u{1b}[B\u{1b}[B\u{1b}[B\u{1b}[B\u{1b}[B\u{1b}[B".to_owned())
1267 .await
1268 .expect("Failed to send down arrow!");
1269 assert_eq!(
1270 provider.poll_for_input(true),
1271 vec![TerminalInputEvent::InputChanged(4)],
1272 );
1273 assert_eq!(provider.current_input(), "test");
1274 assert_eq!(provider.current_input.cursor_position(), 4);
1275
1276 sender
1278 .send("\u{3}".to_owned())
1279 .await
1280 .expect("Failed to send cancel input");
1281 _ = provider.poll_for_input(true);
1282 sender
1283 .send("\u{1b}[A".to_owned())
1284 .await
1285 .expect("Failed to send up arrow!");
1286 assert_eq!(
1287 provider.poll_for_input(true),
1288 vec![
1289 TerminalInputEvent::InputStarted,
1290 TerminalInputEvent::InputChanged(11),
1291 ],
1292 );
1293 }
1294
1295 #[tokio::test]
1296 pub async fn history_searching() {
1297 let history_provider = Box::new(SimpleHistoryProvider::new_in_memory(15));
1298 history_provider.insert_command("word swap again");
1299 history_provider.insert_command("word swap more");
1300 history_provider.insert_command("swap word again");
1301 history_provider.insert_command("swap word more");
1302 history_provider.insert_command("comamand longer1");
1303 history_provider.insert_command("command long");
1304 history_provider.insert_command("command longer");
1305 history_provider.insert_command("command short");
1306 history_provider.insert_command("command shorter");
1307 history_provider.insert_command("other");
1308
1309 let (sender, raw_receiver) = channel(8);
1310 let read_provider = Mutex::new(raw_receiver);
1311 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
1312 .expect("Failed to create new stdin input provider!");
1313 provider.set_history_provider(history_provider);
1314
1315 sender
1317 .send("command ".to_owned())
1318 .await
1319 .expect("Failed to start history search");
1320 assert_eq!(
1321 provider.poll_for_input(true),
1322 vec![
1323 TerminalInputEvent::InputStarted,
1324 TerminalInputEvent::InputMassAppend("command ".to_owned()),
1325 ],
1326 );
1327 assert!(!provider.is_doing_history_search());
1328 assert!(provider.current_history_search_value().is_none());
1329
1330 sender
1331 .send("\u{12}".to_owned())
1332 .await
1333 .expect("Failed to send ctrl-r to input to start history search!");
1334 assert_eq!(
1335 provider.poll_for_input(true),
1336 vec![TerminalInputEvent::InputChanged(8)],
1337 );
1338 assert!(provider.is_doing_history_search());
1339 assert_eq!(
1340 provider.current_history_search_value(),
1341 Some("shorter".to_owned()),
1342 );
1343 assert_eq!(provider.current_input(), "command shorter");
1345
1346 sender
1347 .send("l".to_owned())
1348 .await
1349 .expect("Failed to send key press to terminal");
1350 assert_eq!(
1351 provider.poll_for_input(true),
1352 vec![TerminalInputEvent::InputAppend('l')],
1353 );
1354 assert!(provider.is_doing_history_search());
1355 assert_eq!(
1356 provider.current_history_search_value(),
1357 Some("onger".to_owned()),
1358 );
1359 assert_eq!(provider.current_input(), "command longer");
1360
1361 sender
1362 .send("\u{1b}[D\u{1b}[D\u{2}\u{2}\u{1b}[D\u{1b}[Da".to_owned())
1363 .await
1364 .expect("Failed to send cursor movement and append for history!");
1365 assert_eq!(
1366 provider.poll_for_input(true),
1367 vec![TerminalInputEvent::InputChanged(4)],
1368 "Current Input Is ({:?}) @ {}",
1369 provider.current_input(),
1370 provider.current_input.cursor_position(),
1371 );
1372 assert!(provider.is_doing_history_search());
1373 assert_eq!(
1374 provider.current_history_search_value(),
1375 Some("onger1".to_owned()),
1376 );
1377
1378 sender
1379 .send("\u{1b}[1;2Conger1".to_owned())
1380 .await
1381 .expect("Failed to complete history search!");
1382 assert_eq!(
1383 provider.poll_for_input(true),
1384 vec![TerminalInputEvent::InputChanged(14)],
1385 );
1386 assert!(provider.is_doing_history_search());
1387 assert!(provider.current_history_search_value().is_none());
1388
1389 sender
1390 .send("\u{3}".to_owned())
1391 .await
1392 .expect("Failed to send cancel!");
1393 _ = provider.poll_for_input(true);
1394
1395 sender
1396 .send("word swap\u{12}".to_owned())
1397 .await
1398 .expect("Failed to send history search start!");
1399 assert_eq!(
1400 provider.poll_for_input(true),
1401 vec![
1402 TerminalInputEvent::InputStarted,
1403 TerminalInputEvent::InputChanged(9),
1404 ],
1405 );
1406 sender
1407 .send("\u{1b}t a".to_owned())
1408 .await
1409 .expect("Failed to swap words and type a!");
1410 assert_eq!(
1411 provider.poll_for_input(true),
1412 vec![TerminalInputEvent::InputChanged(11)],
1413 "Current input is: [{:?}] @ {}",
1414 provider.current_input.input(),
1415 provider.current_input.cursor_position(),
1416 );
1417 assert!(provider.is_doing_history_search());
1418 assert_eq!(
1419 provider.current_history_search_value(),
1420 Some("gain".to_owned()),
1421 );
1422 assert_eq!(provider.current_input.input(), "swap word again");
1423
1424 sender
1425 .send("\r".to_owned())
1426 .await
1427 .expect("Failed to send enter early!");
1428 assert_eq!(
1429 provider.poll_for_input(true),
1430 vec![TerminalInputEvent::InputFinished],
1431 );
1432 assert_eq!(
1433 provider.completed_inputs.last().map(Clone::clone),
1434 Some("swap word again".to_owned())
1435 );
1436
1437 assert!(!provider.is_doing_history_search());
1438 sender
1439 .send("command \u{12}".to_owned())
1440 .await
1441 .expect("Failed to test history search without ANSI!");
1442 assert_eq!(
1443 provider.poll_for_input(false),
1444 vec![
1445 TerminalInputEvent::InputStarted,
1446 TerminalInputEvent::InputMassAppend("command ".to_owned())
1447 ],
1448 );
1449 assert!(!provider.is_doing_history_search());
1450 }
1451
1452 #[tokio::test]
1453 pub async fn paste_control_codes() {
1454 let (sender, raw_receiver) = channel(8);
1455 let read_provider = Mutex::new(raw_receiver);
1456 let mut provider = StdinInputProvider::new_with_provider("test", read_provider)
1457 .expect("Failed to create new stdin input provider!");
1458
1459 insert_next_paste_value("\u{1b}[31m\u{1b}[1;31min red!\u{1b}[0mjust normal!".to_owned());
1460 sender
1461 .send("\u{16}".to_owned())
1462 .await
1463 .expect("Failed to send paste!");
1464 assert_eq!(
1465 provider.poll_for_input(true),
1466 vec![
1467 TerminalInputEvent::InputStarted,
1468 TerminalInputEvent::InputMassAppend(
1469 "\u{1b}[31m\u{1b}[1;31min red!\u{1b}[0mjust normal!".to_owned()
1470 ),
1471 ],
1472 );
1473 assert_eq!(provider.current_input.cursor_position(), 35);
1474 insert_next_paste_value("\u{16}\u{12}\u{2}\u{1b}[D\u{1b}[D\u{1b}[D".to_owned());
1475 sender
1476 .send("\u{16}".to_owned())
1477 .await
1478 .expect("Failed to send paste!");
1479 assert_eq!(
1480 provider.poll_for_input(true),
1481 vec![TerminalInputEvent::InputMassAppend(
1482 "\u{16}\u{12}\u{2}\u{1b}[D\u{1b}[D\u{1b}[D".to_owned()
1483 )],
1484 );
1485 assert!(!provider.current_input.is_doing_history_search());
1486 assert_eq!(provider.current_input.cursor_position(), 47);
1487 }
1488}