1use std::path::PathBuf;
2
3use itertools::Itertools;
4
5use crate::{enums::ReedlineRawEvent, CursorConfig};
6#[cfg(feature = "bashisms")]
7use crate::{
8 history::SearchFilter,
9 menu_functions::{parse_selection_char, ParseAction},
10};
11#[cfg(feature = "external_printer")]
12use {
13 crate::external_printer::ExternalPrinter,
14 crossbeam::channel::TryRecvError,
15 std::io::{Error, ErrorKind},
16};
17use {
18 crate::{
19 completion::{Completer, DefaultCompleter},
20 core_editor::Editor,
21 edit_mode::{EditMode, Emacs},
22 enums::{EventStatus, ReedlineEvent},
23 highlighter::SimpleMatchHighlighter,
24 hinter::Hinter,
25 history::{
26 FileBackedHistory, History, HistoryCursor, HistoryItem, HistoryItemId,
27 HistoryNavigationQuery, HistorySessionId, SearchDirection, SearchQuery,
28 },
29 painting::{Painter, PromptLines},
30 prompt::{PromptEditMode, PromptHistorySearchStatus},
31 result::{ReedlineError, ReedlineErrorVariants},
32 terminal_extensions::{bracketed_paste::BracketedPasteGuard, kitty::KittyProtocolGuard},
33 utils::text_manipulation,
34 EditCommand, ExampleHighlighter, Highlighter, LineBuffer, Menu, MenuEvent, Prompt,
35 PromptHistorySearch, ReedlineMenu, Signal, UndoBehavior, ValidationResult, Validator,
36 },
37 crossterm::{
38 cursor::{SetCursorStyle, Show},
39 event,
40 event::{Event, KeyCode, KeyEvent, KeyModifiers},
41 terminal, QueueableCommand,
42 },
43 std::{
44 fs::File, io, io::Result, io::Write, process::Command, time::Duration, time::SystemTime,
45 },
46};
47
48const POLL_WAIT: u64 = 10;
54const EVENTS_THRESHOLD: usize = 10;
59
60#[derive(Debug, PartialEq, Eq)]
64enum InputMode {
65 Regular,
68 HistorySearch,
72 HistoryTraversal,
76}
77
78pub struct Reedline {
98 editor: Editor,
99
100 history: Box<dyn History>,
102 history_cursor: HistoryCursor,
103 history_session_id: Option<HistorySessionId>,
104 history_last_run_id: Option<HistoryItemId>,
106 history_exclusion_prefix: Option<String>,
107 history_excluded_item: Option<HistoryItem>,
108 history_cursor_on_excluded: bool,
109 input_mode: InputMode,
110
111 validator: Option<Box<dyn Validator>>,
113
114 painter: Painter,
116
117 transient_prompt: Option<Box<dyn Prompt>>,
118
119 edit_mode: Box<dyn EditMode>,
121
122 completer: Box<dyn Completer>,
124 quick_completions: bool,
125 partial_completions: bool,
126
127 highlighter: Box<dyn Highlighter>,
129
130 hinter: Option<Box<dyn Hinter>>,
132 hide_hints: bool,
133
134 use_ansi_coloring: bool,
136
137 menus: Vec<ReedlineMenu>,
139
140 buffer_editor: Option<BufferEditor>,
142
143 cursor_shapes: Option<CursorConfig>,
145
146 bracketed_paste: BracketedPasteGuard,
148
149 kitty_protocol: KittyProtocolGuard,
151
152 #[cfg(feature = "external_printer")]
153 external_printer: Option<ExternalPrinter<String>>,
154}
155
156struct BufferEditor {
157 command: Command,
158 temp_file: PathBuf,
159}
160
161impl Drop for Reedline {
162 fn drop(&mut self) {
163 if self.cursor_shapes.is_some() {
164 let _ignore = terminal::enable_raw_mode();
165 let mut stdout = std::io::stdout();
166 let _ignore = stdout.queue(SetCursorStyle::DefaultUserShape);
167 let _ignore = stdout.queue(Show);
168 let _ignore = stdout.flush();
169 }
170
171 let _ignore = terminal::disable_raw_mode();
174 }
175}
176
177impl Reedline {
178 const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX);
179
180 #[must_use]
182 pub fn create() -> Self {
183 let history = Box::<FileBackedHistory>::default();
184 let painter = Painter::new(std::io::BufWriter::new(std::io::stderr()));
185 let buffer_highlighter = Box::<ExampleHighlighter>::default();
186 let completer = Box::<DefaultCompleter>::default();
187 let hinter = None;
188 let validator = None;
189 let edit_mode = Box::<Emacs>::default();
190 let hist_session_id = None;
191
192 Reedline {
193 editor: Editor::default(),
194 history,
195 history_cursor: HistoryCursor::new(
196 HistoryNavigationQuery::Normal(LineBuffer::default()),
197 hist_session_id,
198 ),
199 history_session_id: hist_session_id,
200 history_last_run_id: None,
201 history_exclusion_prefix: None,
202 history_excluded_item: None,
203 history_cursor_on_excluded: false,
204 input_mode: InputMode::Regular,
205 painter,
206 transient_prompt: None,
207 edit_mode,
208 completer,
209 quick_completions: false,
210 partial_completions: false,
211 highlighter: buffer_highlighter,
212 hinter,
213 hide_hints: false,
214 validator,
215 use_ansi_coloring: true,
216 menus: Vec::new(),
217 buffer_editor: None,
218 cursor_shapes: None,
219 bracketed_paste: BracketedPasteGuard::default(),
220 kitty_protocol: KittyProtocolGuard::default(),
221 #[cfg(feature = "external_printer")]
222 external_printer: None,
223 }
224 }
225
226 pub fn create_history_session_id() -> Option<HistorySessionId> {
228 let nanos = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
229 Ok(n) => n.as_nanos() as i64,
230 Err(_) => 0,
231 };
232
233 Some(HistorySessionId::new(nanos))
234 }
235
236 pub fn use_bracketed_paste(mut self, enable: bool) -> Self {
246 self.bracketed_paste.set(enable);
247 self
248 }
249
250 pub fn use_kitty_keyboard_enhancement(mut self, enable: bool) -> Self {
259 self.kitty_protocol.set(enable);
260 self
261 }
262
263 pub fn get_history_session_id(&self) -> Option<HistorySessionId> {
265 self.history_session_id
266 }
267
268 pub fn set_history_session_id(&mut self, session: Option<HistorySessionId>) -> Result<()> {
272 self.history_session_id = session;
273 Ok(())
274 }
275
276 #[must_use]
293 pub fn with_hinter(mut self, hinter: Box<dyn Hinter>) -> Self {
294 self.hinter = Some(hinter);
295 self
296 }
297
298 #[must_use]
300 pub fn disable_hints(mut self) -> Self {
301 self.hinter = None;
302 self
303 }
304
305 #[must_use]
323 pub fn with_completer(mut self, completer: Box<dyn Completer>) -> Self {
324 self.completer = completer;
325 self
326 }
327
328 #[must_use]
331 pub fn with_quick_completions(mut self, quick_completions: bool) -> Self {
332 self.quick_completions = quick_completions;
333 self
334 }
335
336 #[must_use]
339 pub fn with_partial_completions(mut self, partial_completions: bool) -> Self {
340 self.partial_completions = partial_completions;
341 self
342 }
343
344 #[must_use]
347 pub fn with_ansi_colors(mut self, use_ansi_coloring: bool) -> Self {
348 self.use_ansi_coloring = use_ansi_coloring;
349 self
350 }
351
352 #[must_use]
369 pub fn with_highlighter(mut self, highlighter: Box<dyn Highlighter>) -> Self {
370 self.highlighter = highlighter;
371 self
372 }
373
374 #[must_use]
389 pub fn with_history(mut self, history: Box<dyn History>) -> Self {
390 self.history = history;
391 self
392 }
393
394 #[must_use]
410 pub fn with_history_exclusion_prefix(mut self, ignore_prefix: Option<String>) -> Self {
411 self.history_exclusion_prefix = ignore_prefix;
412 self
413 }
414
415 #[must_use]
426 pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
427 self.validator = Some(validator);
428 self
429 }
430
431 #[must_use]
452 pub fn with_buffer_editor(mut self, editor: Command, temp_file: PathBuf) -> Self {
453 let mut editor = editor;
454 if !editor.get_args().contains(&temp_file.as_os_str()) {
455 editor.arg(&temp_file);
456 }
457 self.buffer_editor = Some(BufferEditor {
458 command: editor,
459 temp_file,
460 });
461 self
462 }
463
464 #[must_use]
466 pub fn disable_validator(mut self) -> Self {
467 self.validator = None;
468 self
469 }
470
471 #[must_use]
473 pub fn with_transient_prompt(mut self, transient_prompt: Box<dyn Prompt>) -> Self {
474 self.transient_prompt = Some(transient_prompt);
475 self
476 }
477
478 #[must_use]
480 pub fn with_edit_mode(mut self, edit_mode: Box<dyn EditMode>) -> Self {
481 self.edit_mode = edit_mode;
482 self
483 }
484
485 #[must_use]
487 pub fn with_menu(mut self, menu: ReedlineMenu) -> Self {
488 self.menus.push(menu);
489 self
490 }
491
492 #[must_use]
494 pub fn clear_menus(mut self) -> Self {
495 self.menus = Vec::new();
496 self
497 }
498
499 #[must_use]
501 pub fn with_history_session_id(mut self, session: Option<HistorySessionId>) -> Self {
502 self.history_session_id = session;
503 self
504 }
505
506 pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self {
510 self.cursor_shapes = Some(cursor_shapes);
511 self
512 }
513
514 pub fn prompt_edit_mode(&self) -> PromptEditMode {
516 self.edit_mode.edit_mode()
517 }
518
519 pub fn print_history(&mut self) -> Result<()> {
521 let history: Vec<_> = self
522 .history
523 .search(SearchQuery::everything(SearchDirection::Forward, None))
524 .expect("todo: error handling");
525
526 for (i, entry) in history.iter().enumerate() {
527 self.print_line(&format!("{}\t{}", i, entry.command_line))?;
528 }
529 Ok(())
530 }
531
532 pub fn print_history_session(&mut self) -> Result<()> {
534 let history: Vec<_> = self
535 .history
536 .search(SearchQuery::everything(
537 SearchDirection::Forward,
538 self.get_history_session_id(),
539 ))
540 .expect("todo: error handling");
541
542 for (i, entry) in history.iter().enumerate() {
543 self.print_line(&format!("{}\t{}", i, entry.command_line))?;
544 }
545 Ok(())
546 }
547
548 pub fn print_history_session_id(&mut self) -> Result<()> {
550 println!("History Session Id: {:?}", self.get_history_session_id());
551 Ok(())
552 }
553
554 pub fn toggle_history_session_matching(
556 &mut self,
557 session: Option<HistorySessionId>,
558 ) -> Result<()> {
559 self.history_session_id = match self.get_history_session_id() {
560 Some(_) => None,
561 None => session,
562 };
563 Ok(())
564 }
565
566 pub fn history(&self) -> &dyn History {
568 &*self.history
569 }
570
571 pub fn history_mut(&mut self) -> &mut dyn History {
573 &mut *self.history
574 }
575
576 pub fn sync_history(&mut self) -> std::io::Result<()> {
578 self.history.sync()
580 }
581
582 pub fn has_last_command_context(&self) -> bool {
587 self.history_last_run_id.is_some()
588 }
589
590 pub fn update_last_command_context(
592 &mut self,
593 f: &dyn Fn(HistoryItem) -> HistoryItem,
594 ) -> crate::Result<()> {
595 match &self.history_last_run_id {
596 Some(Self::FILTERED_ITEM_ID) => {
597 self.history_excluded_item = Some(f(self.history_excluded_item.take().unwrap()));
598 Ok(())
599 }
600 Some(r) => self.history.update(*r, f),
601 None => Err(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
602 "No command run",
603 ))),
604 }
605 }
606
607 pub fn read_line(&mut self, prompt: &dyn Prompt) -> Result<Signal> {
612 terminal::enable_raw_mode()?;
613 self.bracketed_paste.enter();
614 self.kitty_protocol.enter();
615
616 let result = self.read_line_helper(prompt);
617
618 self.bracketed_paste.exit();
619 self.kitty_protocol.exit();
620 terminal::disable_raw_mode()?;
621 result
622 }
623
624 pub fn current_insertion_point(&self) -> usize {
626 self.editor.insertion_point()
627 }
628
629 pub fn current_buffer_contents(&self) -> &str {
631 self.editor.get_buffer()
632 }
633
634 fn print_line(&mut self, msg: &str) -> Result<()> {
636 self.painter.paint_line(msg)
637 }
638
639 pub fn clear_screen(&mut self) -> Result<()> {
642 self.painter.clear_screen()?;
643
644 Ok(())
645 }
646
647 pub fn clear_scrollback(&mut self) -> Result<()> {
649 self.painter.clear_scrollback()?;
650
651 Ok(())
652 }
653
654 fn read_line_helper(&mut self, prompt: &dyn Prompt) -> Result<Signal> {
657 self.painter.initialize_prompt_position()?;
658 self.hide_hints = false;
659
660 self.repaint(prompt)?;
661
662 let mut crossterm_events: Vec<ReedlineRawEvent> = vec![];
663 let mut reedline_events: Vec<ReedlineEvent> = vec![];
664
665 loop {
666 let mut paste_enter_state = false;
667
668 #[cfg(feature = "external_printer")]
669 if let Some(ref external_printer) = self.external_printer {
670 let messages = Self::external_messages(external_printer)?;
672 if !messages.is_empty() {
673 self.painter.print_external_message(
675 messages,
676 self.editor.line_buffer(),
677 prompt,
678 )?;
679 self.repaint(prompt)?;
680 }
681 }
682
683 let mut latest_resize = None;
684 loop {
685 match event::read()? {
686 Event::Resize(x, y) => {
687 latest_resize = Some((x, y));
688 }
689 enter @ Event::Key(KeyEvent {
690 code: KeyCode::Enter,
691 modifiers: KeyModifiers::NONE,
692 ..
693 }) => {
694 let enter = ReedlineRawEvent::convert_from(enter);
695 if let Some(enter) = enter {
696 crossterm_events.push(enter);
697 paste_enter_state = crossterm_events.len() > EVENTS_THRESHOLD;
703 break;
704 }
705 }
706 x => {
707 let raw_event = ReedlineRawEvent::convert_from(x);
708 if let Some(evt) = raw_event {
709 crossterm_events.push(evt);
710 }
711 }
712 }
713
714 if !event::poll(Duration::from_millis(POLL_WAIT))? {
718 break;
719 }
720 }
721
722 if let Some((x, y)) = latest_resize {
723 reedline_events.push(ReedlineEvent::Resize(x, y));
724 }
725
726 let mut last_edit_commands = None;
730 for event in crossterm_events.drain(..) {
731 match (&mut last_edit_commands, self.edit_mode.parse_event(event)) {
732 (None, ReedlineEvent::Edit(ec)) => {
733 last_edit_commands = Some(ec);
734 }
735 (None, other_event) => {
736 reedline_events.push(other_event);
737 }
738 (Some(ref mut last_ecs), ReedlineEvent::Edit(ec)) => {
739 last_ecs.extend(ec);
740 }
741 (ref mut a @ Some(_), other_event) => {
742 reedline_events.push(ReedlineEvent::Edit(a.take().unwrap()));
743
744 reedline_events.push(other_event);
745 }
746 }
747 }
748 if let Some(ec) = last_edit_commands {
749 reedline_events.push(ReedlineEvent::Edit(ec));
750 }
751
752 for event in reedline_events.drain(..) {
753 match self.handle_event(prompt, event)? {
754 EventStatus::Exits(signal) => {
755 self.painter.move_cursor_to_end()?;
757 return Ok(signal);
758 }
759 EventStatus::Handled => {
760 if !paste_enter_state {
761 self.repaint(prompt)?;
762 }
763 }
764 EventStatus::Inapplicable => {
765 }
767 }
768 }
769 }
770 }
771
772 fn handle_event(&mut self, prompt: &dyn Prompt, event: ReedlineEvent) -> Result<EventStatus> {
773 if self.input_mode == InputMode::HistorySearch {
774 self.handle_history_search_event(event)
775 } else {
776 self.handle_editor_event(prompt, event)
777 }
778 }
779
780 fn handle_history_search_event(&mut self, event: ReedlineEvent) -> io::Result<EventStatus> {
781 match event {
782 ReedlineEvent::UntilFound(events) => {
783 for event in events {
784 match self.handle_history_search_event(event)? {
785 EventStatus::Inapplicable => {
786 }
788 success => {
789 return Ok(success);
790 }
791 }
792 }
793 Ok(EventStatus::Handled)
795 }
796 ReedlineEvent::CtrlD => {
797 if self.editor.is_empty() {
798 self.input_mode = InputMode::Regular;
799 self.editor.reset_undo_stack();
800 Ok(EventStatus::Exits(Signal::CtrlD))
801 } else {
802 self.run_history_commands(&[EditCommand::Delete]);
803 Ok(EventStatus::Handled)
804 }
805 }
806 ReedlineEvent::CtrlC => {
807 self.input_mode = InputMode::Regular;
808 Ok(EventStatus::Exits(Signal::CtrlC))
809 }
810 ReedlineEvent::ClearScreen => {
811 self.painter.clear_screen()?;
812 Ok(EventStatus::Handled)
813 }
814 ReedlineEvent::ClearScrollback => {
815 self.painter.clear_scrollback()?;
816 Ok(EventStatus::Handled)
817 }
818 ReedlineEvent::Enter
819 | ReedlineEvent::HistoryHintComplete
820 | ReedlineEvent::Submit
821 | ReedlineEvent::SubmitOrNewline => {
822 if let Some(string) = self.history_cursor.string_at_cursor() {
823 self.editor
824 .set_buffer(string, UndoBehavior::CreateUndoPoint);
825 }
826
827 self.input_mode = InputMode::Regular;
828 Ok(EventStatus::Handled)
829 }
830 ReedlineEvent::ExecuteHostCommand(host_command) => {
831 Ok(EventStatus::Exits(Signal::Success(host_command)))
833 }
834 ReedlineEvent::Edit(commands) => {
835 self.run_history_commands(&commands);
836 Ok(EventStatus::Handled)
837 }
838 ReedlineEvent::Mouse => Ok(EventStatus::Handled),
839 ReedlineEvent::Resize(width, height) => {
840 self.painter.handle_resize(width, height);
841 Ok(EventStatus::Inapplicable)
842 }
843 ReedlineEvent::Repaint => {
844 Ok(EventStatus::Handled)
846 }
847 ReedlineEvent::PreviousHistory | ReedlineEvent::Up | ReedlineEvent::SearchHistory => {
848 self.history_cursor
849 .back(self.history.as_ref())
850 .expect("todo: error handling");
851 Ok(EventStatus::Handled)
852 }
853 ReedlineEvent::NextHistory | ReedlineEvent::Down => {
854 self.history_cursor
855 .forward(self.history.as_ref())
856 .expect("todo: error handling");
857 if self.history_cursor.string_at_cursor().is_none() {
859 self.history_cursor
860 .back(self.history.as_ref())
861 .expect("todo: error handling");
862 }
863 Ok(EventStatus::Handled)
864 }
865 ReedlineEvent::Esc => {
866 self.input_mode = InputMode::Regular;
867 Ok(EventStatus::Handled)
868 }
869 ReedlineEvent::Right
871 | ReedlineEvent::Left
872 | ReedlineEvent::Multiple(_)
873 | ReedlineEvent::None
874 | ReedlineEvent::HistoryHintWordComplete
875 | ReedlineEvent::OpenEditor
876 | ReedlineEvent::Menu(_)
877 | ReedlineEvent::MenuNext
878 | ReedlineEvent::MenuPrevious
879 | ReedlineEvent::MenuUp
880 | ReedlineEvent::MenuDown
881 | ReedlineEvent::MenuLeft
882 | ReedlineEvent::MenuRight
883 | ReedlineEvent::MenuPageNext
884 | ReedlineEvent::MenuPagePrevious => Ok(EventStatus::Inapplicable),
885 }
886 }
887
888 fn handle_editor_event(
889 &mut self,
890 prompt: &dyn Prompt,
891 event: ReedlineEvent,
892 ) -> io::Result<EventStatus> {
893 match event {
894 ReedlineEvent::Menu(name) => {
895 if self.active_menu().is_none() {
896 if let Some(menu) = self.menus.iter_mut().find(|menu| menu.name() == name) {
897 menu.menu_event(MenuEvent::Activate(self.quick_completions));
898
899 if self.quick_completions && menu.can_quick_complete() {
900 menu.update_values(
901 &mut self.editor,
902 self.completer.as_mut(),
903 self.history.as_ref(),
904 );
905
906 if menu.get_values().len() == 1 {
907 return self.handle_editor_event(prompt, ReedlineEvent::Enter);
908 }
909 }
910
911 if self.partial_completions
912 && menu.can_partially_complete(
913 self.quick_completions,
914 &mut self.editor,
915 self.completer.as_mut(),
916 self.history.as_ref(),
917 )
918 {
919 return Ok(EventStatus::Handled);
920 }
921
922 return Ok(EventStatus::Handled);
923 }
924 }
925 Ok(EventStatus::Inapplicable)
926 }
927 ReedlineEvent::MenuNext => {
928 self.active_menu()
929 .map_or(Ok(EventStatus::Inapplicable), |menu| {
930 menu.menu_event(MenuEvent::NextElement);
931 Ok(EventStatus::Handled)
932 })
933 }
934 ReedlineEvent::MenuPrevious => {
935 self.active_menu()
936 .map_or(Ok(EventStatus::Inapplicable), |menu| {
937 menu.menu_event(MenuEvent::PreviousElement);
938 Ok(EventStatus::Handled)
939 })
940 }
941 ReedlineEvent::MenuUp => {
942 self.active_menu()
943 .map_or(Ok(EventStatus::Inapplicable), |menu| {
944 menu.menu_event(MenuEvent::MoveUp);
945 Ok(EventStatus::Handled)
946 })
947 }
948 ReedlineEvent::MenuDown => {
949 self.active_menu()
950 .map_or(Ok(EventStatus::Inapplicable), |menu| {
951 menu.menu_event(MenuEvent::MoveDown);
952 Ok(EventStatus::Handled)
953 })
954 }
955 ReedlineEvent::MenuLeft => {
956 self.active_menu()
957 .map_or(Ok(EventStatus::Inapplicable), |menu| {
958 menu.menu_event(MenuEvent::MoveLeft);
959 Ok(EventStatus::Handled)
960 })
961 }
962 ReedlineEvent::MenuRight => {
963 self.active_menu()
964 .map_or(Ok(EventStatus::Inapplicable), |menu| {
965 menu.menu_event(MenuEvent::MoveRight);
966 Ok(EventStatus::Handled)
967 })
968 }
969 ReedlineEvent::MenuPageNext => {
970 self.active_menu()
971 .map_or(Ok(EventStatus::Inapplicable), |menu| {
972 menu.menu_event(MenuEvent::NextPage);
973 Ok(EventStatus::Handled)
974 })
975 }
976 ReedlineEvent::MenuPagePrevious => {
977 self.active_menu()
978 .map_or(Ok(EventStatus::Inapplicable), |menu| {
979 menu.menu_event(MenuEvent::PreviousPage);
980 Ok(EventStatus::Handled)
981 })
982 }
983 ReedlineEvent::HistoryHintComplete => {
984 if let Some(hinter) = self.hinter.as_mut() {
985 let current_hint = hinter.complete_hint();
986 if self.hints_active()
987 && self.editor.is_cursor_at_buffer_end()
988 && !current_hint.is_empty()
989 && self.active_menu().is_none()
990 {
991 self.run_edit_commands(&[EditCommand::InsertString(current_hint)]);
992 return Ok(EventStatus::Handled);
993 }
994 }
995 Ok(EventStatus::Inapplicable)
996 }
997 ReedlineEvent::HistoryHintWordComplete => {
998 if let Some(hinter) = self.hinter.as_mut() {
999 let current_hint_part = hinter.next_hint_token();
1000 if self.hints_active()
1001 && self.editor.is_cursor_at_buffer_end()
1002 && !current_hint_part.is_empty()
1003 && self.active_menu().is_none()
1004 {
1005 self.run_edit_commands(&[EditCommand::InsertString(current_hint_part)]);
1006 return Ok(EventStatus::Handled);
1007 }
1008 }
1009 Ok(EventStatus::Inapplicable)
1010 }
1011 ReedlineEvent::Esc => {
1012 self.deactivate_menus();
1013 Ok(EventStatus::Handled)
1014 }
1015 ReedlineEvent::CtrlD => {
1016 if self.editor.is_empty() {
1017 self.editor.reset_undo_stack();
1018 Ok(EventStatus::Exits(Signal::CtrlD))
1019 } else {
1020 self.run_edit_commands(&[EditCommand::Delete]);
1021 Ok(EventStatus::Handled)
1022 }
1023 }
1024 ReedlineEvent::CtrlC => {
1025 self.deactivate_menus();
1026 self.run_edit_commands(&[EditCommand::Clear]);
1027 self.editor.reset_undo_stack();
1028 Ok(EventStatus::Exits(Signal::CtrlC))
1029 }
1030 ReedlineEvent::ClearScreen => {
1031 self.deactivate_menus();
1032 self.painter.clear_screen()?;
1033 Ok(EventStatus::Handled)
1034 }
1035 ReedlineEvent::ClearScrollback => {
1036 self.deactivate_menus();
1037 self.painter.clear_scrollback()?;
1038 Ok(EventStatus::Handled)
1039 }
1040 ReedlineEvent::Enter | ReedlineEvent::Submit | ReedlineEvent::SubmitOrNewline
1041 if self.menus.iter().any(|menu| menu.is_active()) =>
1042 {
1043 for menu in self.menus.iter_mut() {
1044 if menu.is_active() {
1045 menu.replace_in_buffer(&mut self.editor);
1046 menu.menu_event(MenuEvent::Deactivate);
1047
1048 return Ok(EventStatus::Handled);
1049 }
1050 }
1051 unreachable!()
1052 }
1053 ReedlineEvent::Enter => {
1054 #[cfg(feature = "bashisms")]
1055 if let Some(event) = self.parse_bang_command() {
1056 return self.handle_editor_event(prompt, event);
1057 }
1058
1059 let buffer = self.editor.get_buffer().to_string();
1060 match self.validator.as_mut().map(|v| v.validate(&buffer)) {
1061 None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
1062 Some(ValidationResult::Incomplete) => {
1063 self.run_edit_commands(&[EditCommand::InsertNewline]);
1064
1065 Ok(EventStatus::Handled)
1066 }
1067 }
1068 }
1069 ReedlineEvent::Submit => {
1070 #[cfg(feature = "bashisms")]
1071 if let Some(event) = self.parse_bang_command() {
1072 return self.handle_editor_event(prompt, event);
1073 }
1074 Ok(self.submit_buffer(prompt)?)
1075 }
1076 ReedlineEvent::SubmitOrNewline => {
1077 #[cfg(feature = "bashisms")]
1078 if let Some(event) = self.parse_bang_command() {
1079 return self.handle_editor_event(prompt, event);
1080 }
1081 let cursor_position_in_buffer = self.editor.insertion_point();
1082 let buffer = self.editor.get_buffer().to_string();
1083 if cursor_position_in_buffer < buffer.len() {
1084 self.run_edit_commands(&[EditCommand::InsertNewline]);
1085 return Ok(EventStatus::Handled);
1086 }
1087 match self.validator.as_mut().map(|v| v.validate(&buffer)) {
1088 None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
1089 Some(ValidationResult::Incomplete) => {
1090 self.run_edit_commands(&[EditCommand::InsertNewline]);
1091
1092 Ok(EventStatus::Handled)
1093 }
1094 }
1095 }
1096 ReedlineEvent::ExecuteHostCommand(host_command) => {
1097 Ok(EventStatus::Exits(Signal::Success(host_command)))
1099 }
1100 ReedlineEvent::Edit(commands) => {
1101 self.run_edit_commands(&commands);
1102 if let Some(menu) = self.menus.iter_mut().find(|men| men.is_active()) {
1103 if self.quick_completions && menu.can_quick_complete() {
1104 match commands.first() {
1105 Some(&EditCommand::Backspace)
1106 | Some(&EditCommand::BackspaceWord)
1107 | Some(&EditCommand::MoveToLineStart) => {
1108 menu.menu_event(MenuEvent::Deactivate)
1109 }
1110 _ => {
1111 menu.menu_event(MenuEvent::Edit(self.quick_completions));
1112 menu.update_values(
1113 &mut self.editor,
1114 self.completer.as_mut(),
1115 self.history.as_ref(),
1116 );
1117 if let Some(&EditCommand::Complete) = commands.first() {
1118 if menu.get_values().len() == 1 {
1119 return self
1120 .handle_editor_event(prompt, ReedlineEvent::Enter);
1121 } else if self.partial_completions
1122 && menu.can_partially_complete(
1123 self.quick_completions,
1124 &mut self.editor,
1125 self.completer.as_mut(),
1126 self.history.as_ref(),
1127 )
1128 {
1129 return Ok(EventStatus::Handled);
1130 }
1131 }
1132 }
1133 }
1134 }
1135 if self.editor.line_buffer().get_buffer().is_empty() {
1136 menu.menu_event(MenuEvent::Deactivate);
1137 } else {
1138 menu.menu_event(MenuEvent::Edit(self.quick_completions));
1139 }
1140 }
1141 Ok(EventStatus::Handled)
1142 }
1143 ReedlineEvent::OpenEditor => self.open_editor().map(|_| EventStatus::Handled),
1144 ReedlineEvent::Resize(width, height) => {
1145 self.painter.handle_resize(width, height);
1146 Ok(EventStatus::Inapplicable)
1147 }
1148 ReedlineEvent::Repaint => {
1149 Ok(EventStatus::Handled)
1151 }
1152 ReedlineEvent::PreviousHistory => {
1153 self.previous_history();
1154 Ok(EventStatus::Handled)
1155 }
1156 ReedlineEvent::NextHistory => {
1157 self.next_history();
1158 Ok(EventStatus::Handled)
1159 }
1160 ReedlineEvent::Up => {
1161 self.up_command();
1162 Ok(EventStatus::Handled)
1163 }
1164 ReedlineEvent::Down => {
1165 self.down_command();
1166 Ok(EventStatus::Handled)
1167 }
1168 ReedlineEvent::Left => {
1169 self.run_edit_commands(&[EditCommand::MoveLeft]);
1170 Ok(EventStatus::Handled)
1171 }
1172 ReedlineEvent::Right => {
1173 self.run_edit_commands(&[EditCommand::MoveRight]);
1174 Ok(EventStatus::Handled)
1175 }
1176 ReedlineEvent::SearchHistory => {
1177 self.enter_history_search();
1178 Ok(EventStatus::Handled)
1179 }
1180 ReedlineEvent::Multiple(events) => {
1181 let mut latest_signal = EventStatus::Inapplicable;
1182 for event in events {
1183 match self.handle_editor_event(prompt, event)? {
1184 EventStatus::Handled => {
1185 latest_signal = EventStatus::Handled;
1186 }
1187 EventStatus::Inapplicable => {
1188 }
1190 EventStatus::Exits(signal) => {
1191 return Ok(EventStatus::Exits(signal));
1195 }
1196 }
1197 }
1198
1199 Ok(latest_signal)
1200 }
1201 ReedlineEvent::UntilFound(events) => {
1202 for event in events {
1203 match self.handle_editor_event(prompt, event)? {
1204 EventStatus::Inapplicable => {
1205 }
1207 success => {
1208 return Ok(success);
1209 }
1210 }
1211 }
1212 Ok(EventStatus::Inapplicable)
1214 }
1215 ReedlineEvent::None | ReedlineEvent::Mouse => Ok(EventStatus::Inapplicable),
1216 }
1217 }
1218
1219 fn active_menu(&mut self) -> Option<&mut ReedlineMenu> {
1220 self.menus.iter_mut().find(|menu| menu.is_active())
1221 }
1222
1223 fn deactivate_menus(&mut self) {
1224 self.menus
1225 .iter_mut()
1226 .for_each(|menu| menu.menu_event(MenuEvent::Deactivate));
1227 }
1228
1229 fn previous_history(&mut self) {
1230 if self.history_cursor_on_excluded {
1231 self.history_cursor_on_excluded = false;
1232 }
1233 if self.input_mode != InputMode::HistoryTraversal {
1234 self.input_mode = InputMode::HistoryTraversal;
1235 self.history_cursor = HistoryCursor::new(
1236 self.get_history_navigation_based_on_line_buffer(),
1237 self.get_history_session_id(),
1238 );
1239
1240 if self.history_excluded_item.is_some() {
1241 self.history_cursor_on_excluded = true;
1242 }
1243 }
1244
1245 if !self.history_cursor_on_excluded {
1246 self.history_cursor
1247 .back(self.history.as_ref())
1248 .expect("todo: error handling");
1249 }
1250 self.update_buffer_from_history();
1251 self.editor.move_to_start(UndoBehavior::HistoryNavigation);
1252 self.editor
1253 .move_to_line_end(UndoBehavior::HistoryNavigation);
1254 }
1255
1256 fn next_history(&mut self) {
1257 if self.input_mode != InputMode::HistoryTraversal {
1258 self.input_mode = InputMode::HistoryTraversal;
1259 self.history_cursor = HistoryCursor::new(
1260 self.get_history_navigation_based_on_line_buffer(),
1261 self.get_history_session_id(),
1262 );
1263 }
1264
1265 if self.history_cursor_on_excluded {
1266 self.history_cursor_on_excluded = false;
1267 } else {
1268 let cursor_was_on_item = self.history_cursor.string_at_cursor().is_some();
1269 self.history_cursor
1270 .forward(self.history.as_ref())
1271 .expect("todo: error handling");
1272
1273 if cursor_was_on_item
1274 && self.history_cursor.string_at_cursor().is_none()
1275 && self.history_excluded_item.is_some()
1276 {
1277 self.history_cursor_on_excluded = true;
1278 }
1279 }
1280
1281 if self.history_cursor.string_at_cursor().is_none() && !self.history_cursor_on_excluded {
1282 self.input_mode = InputMode::Regular;
1283 }
1284 self.update_buffer_from_history();
1285 self.editor.move_to_end(UndoBehavior::HistoryNavigation);
1286 }
1287
1288 fn get_history_navigation_based_on_line_buffer(&self) -> HistoryNavigationQuery {
1292 if self.editor.is_empty() || !self.editor.is_cursor_at_buffer_end() {
1293 HistoryNavigationQuery::Normal(
1295 self.editor.line_buffer().clone(),
1297 )
1298 } else {
1299 let buffer = self.editor.get_buffer().to_string();
1305 HistoryNavigationQuery::PrefixSearch(buffer)
1306 }
1307 }
1308
1309 fn enter_history_search(&mut self) {
1313 self.history_cursor = HistoryCursor::new(
1314 HistoryNavigationQuery::SubstringSearch("".to_string()),
1315 self.get_history_session_id(),
1316 );
1317 self.input_mode = InputMode::HistorySearch;
1318 }
1319
1320 fn run_history_commands(&mut self, commands: &[EditCommand]) {
1324 for command in commands {
1325 match command {
1326 EditCommand::InsertChar(c) => {
1327 let navigation = self.history_cursor.get_navigation();
1328 if let HistoryNavigationQuery::SubstringSearch(mut substring) = navigation {
1329 substring.push(*c);
1330 self.history_cursor = HistoryCursor::new(
1331 HistoryNavigationQuery::SubstringSearch(substring),
1332 self.get_history_session_id(),
1333 );
1334 } else {
1335 self.history_cursor = HistoryCursor::new(
1336 HistoryNavigationQuery::SubstringSearch(String::from(*c)),
1337 self.get_history_session_id(),
1338 );
1339 }
1340 self.history_cursor
1341 .back(self.history.as_mut())
1342 .expect("todo: error handling");
1343 }
1344 EditCommand::Backspace => {
1345 let navigation = self.history_cursor.get_navigation();
1346
1347 if let HistoryNavigationQuery::SubstringSearch(substring) = navigation {
1348 let new_substring = text_manipulation::remove_last_grapheme(&substring);
1349
1350 self.history_cursor = HistoryCursor::new(
1351 HistoryNavigationQuery::SubstringSearch(new_substring.to_string()),
1352 self.get_history_session_id(),
1353 );
1354 self.history_cursor
1355 .back(self.history.as_mut())
1356 .expect("todo: error handling");
1357 }
1358 }
1359 _ => {
1360 self.input_mode = InputMode::Regular;
1361 }
1362 }
1363 }
1364 }
1365
1366 fn update_buffer_from_history(&mut self) {
1371 match self.history_cursor.get_navigation() {
1372 _ if self.history_cursor_on_excluded => self.editor.set_buffer(
1373 self.history_excluded_item
1374 .as_ref()
1375 .unwrap()
1376 .command_line
1377 .clone(),
1378 UndoBehavior::HistoryNavigation,
1379 ),
1380 HistoryNavigationQuery::Normal(original) => {
1381 if let Some(buffer_to_paint) = self.history_cursor.string_at_cursor() {
1382 self.editor
1383 .set_buffer(buffer_to_paint, UndoBehavior::HistoryNavigation);
1384 } else {
1385 self.editor
1387 .set_line_buffer(original, UndoBehavior::HistoryNavigation);
1388 }
1389 }
1390 HistoryNavigationQuery::PrefixSearch(prefix) => {
1391 if let Some(prefix_result) = self.history_cursor.string_at_cursor() {
1392 self.editor
1393 .set_buffer(prefix_result, UndoBehavior::HistoryNavigation);
1394 } else {
1395 self.editor
1396 .set_buffer(prefix, UndoBehavior::HistoryNavigation);
1397 }
1398 }
1399 HistoryNavigationQuery::SubstringSearch(_) => todo!(),
1400 }
1401 }
1402
1403 pub fn run_edit_commands(&mut self, commands: &[EditCommand]) {
1405 if self.input_mode == InputMode::HistoryTraversal {
1406 if matches!(
1407 self.history_cursor.get_navigation(),
1408 HistoryNavigationQuery::Normal(_)
1409 ) {
1410 if let Some(string) = self.history_cursor.string_at_cursor() {
1411 self.editor
1412 .set_buffer(string, UndoBehavior::HistoryNavigation);
1413 }
1414 }
1415 self.input_mode = InputMode::Regular;
1416 }
1417
1418 for command in commands {
1420 self.editor.run_edit_command(command);
1421 }
1422 }
1423
1424 fn up_command(&mut self) {
1425 if self.editor.is_cursor_at_first_line() {
1427 self.previous_history();
1429 } else {
1430 self.editor.move_line_up();
1431 }
1432 }
1433
1434 fn down_command(&mut self) {
1435 if self.editor.is_cursor_at_last_line() {
1437 self.next_history();
1439 } else {
1440 self.editor.move_line_down();
1441 }
1442 }
1443
1444 fn hints_active(&self) -> bool {
1446 !self.hide_hints && matches!(self.input_mode, InputMode::Regular)
1447 }
1448
1449 fn repaint(&mut self, prompt: &dyn Prompt) -> io::Result<()> {
1451 if self.input_mode == InputMode::HistorySearch {
1453 self.history_search_paint(prompt)
1454 } else {
1455 self.buffer_paint(prompt)
1456 }
1457 }
1458
1459 #[cfg(feature = "bashisms")]
1460 fn parse_bang_command(&mut self) -> Option<ReedlineEvent> {
1462 let buffer = self.editor.get_buffer();
1463 let parsed = parse_selection_char(buffer, '!');
1464
1465 if let Some(last) = parsed.remainder.chars().last() {
1466 if last != ' ' {
1467 return None;
1468 }
1469 }
1470
1471 let history_result = parsed
1472 .index
1473 .zip(parsed.marker)
1474 .and_then(|(index, indicator)| match parsed.action {
1475 ParseAction::LastCommand => self
1476 .history
1477 .search(SearchQuery {
1478 direction: SearchDirection::Backward,
1479 start_time: None,
1480 end_time: None,
1481 start_id: None,
1482 end_id: None,
1483 limit: Some(1), filter: SearchFilter::anything(self.get_history_session_id()),
1485 })
1486 .unwrap_or_else(|_| Vec::new())
1487 .get(index.saturating_sub(1))
1488 .map(|history| {
1489 (
1490 parsed.remainder.len(),
1491 indicator.len(),
1492 history.command_line.clone(),
1493 )
1494 }),
1495 ParseAction::BackwardSearch => self
1496 .history
1497 .search(SearchQuery {
1498 direction: SearchDirection::Backward,
1499 start_time: None,
1500 end_time: None,
1501 start_id: None,
1502 end_id: None,
1503 limit: Some(index as i64), filter: SearchFilter::anything(self.get_history_session_id()),
1505 })
1506 .unwrap_or_else(|_| Vec::new())
1507 .get(index.saturating_sub(1))
1508 .map(|history| {
1509 (
1510 parsed.remainder.len(),
1511 indicator.len(),
1512 history.command_line.clone(),
1513 )
1514 }),
1515 ParseAction::ForwardSearch => self
1516 .history
1517 .search(SearchQuery {
1518 direction: SearchDirection::Forward,
1519 start_time: None,
1520 end_time: None,
1521 start_id: None,
1522 end_id: None,
1523 limit: Some((index + 1) as i64), filter: SearchFilter::anything(self.get_history_session_id()),
1525 })
1526 .unwrap_or_else(|_| Vec::new())
1527 .get(index)
1528 .map(|history| {
1529 (
1530 parsed.remainder.len(),
1531 indicator.len(),
1532 history.command_line.clone(),
1533 )
1534 }),
1535 ParseAction::LastToken => self
1536 .history
1537 .search(SearchQuery::last_with_search(SearchFilter::anything(
1538 self.get_history_session_id(),
1539 )))
1540 .unwrap_or_else(|_| Vec::new())
1541 .first()
1542 .and_then(|history| history.command_line.split_whitespace().next_back())
1543 .map(|token| (parsed.remainder.len(), indicator.len(), token.to_string())),
1544 });
1545
1546 if let Some((start, size, history)) = history_result {
1547 let edits = vec![
1548 EditCommand::MoveToPosition(start),
1549 EditCommand::ReplaceChars(size, history),
1550 ];
1551
1552 Some(ReedlineEvent::Edit(edits))
1553 } else {
1554 None
1555 }
1556 }
1557
1558 fn open_editor(&mut self) -> Result<()> {
1559 match &mut self.buffer_editor {
1560 Some(BufferEditor {
1561 ref mut command,
1562 temp_file,
1563 }) => {
1564 {
1565 let mut file = File::create(&temp_file)?;
1566 write!(file, "{}", self.editor.get_buffer())?;
1567 }
1568 {
1569 let mut child = command.spawn()?;
1570 child.wait()?;
1571 }
1572
1573 let res = std::fs::read_to_string(temp_file)?;
1574 let res = res.trim_end().to_string();
1575
1576 self.editor.set_buffer(res, UndoBehavior::CreateUndoPoint);
1577
1578 Ok(())
1579 }
1580 _ => Ok(()),
1581 }
1582 }
1583
1584 fn history_search_paint(&mut self, prompt: &dyn Prompt) -> Result<()> {
1589 let navigation = self.history_cursor.get_navigation();
1590
1591 if let HistoryNavigationQuery::SubstringSearch(substring) = navigation {
1592 let status =
1593 if !substring.is_empty() && self.history_cursor.string_at_cursor().is_none() {
1594 PromptHistorySearchStatus::Failing
1595 } else {
1596 PromptHistorySearchStatus::Passing
1597 };
1598
1599 let prompt_history_search = PromptHistorySearch::new(status, substring.clone());
1600
1601 let res_string = self.history_cursor.string_at_cursor().unwrap_or_default();
1602
1603 let res_string = if self.use_ansi_coloring {
1605 let match_highlighter = SimpleMatchHighlighter::new(substring);
1606 let styled = match_highlighter.highlight(&res_string, 0);
1607 styled.render_simple()
1608 } else {
1609 res_string
1610 };
1611
1612 let lines = PromptLines::new(
1613 prompt,
1614 self.prompt_edit_mode(),
1615 Some(prompt_history_search),
1616 &res_string,
1617 "",
1618 "",
1619 );
1620
1621 self.painter.repaint_buffer(
1622 prompt,
1623 &lines,
1624 self.prompt_edit_mode(),
1625 None,
1626 self.use_ansi_coloring,
1627 &self.cursor_shapes,
1628 )?;
1629 }
1630
1631 Ok(())
1632 }
1633
1634 fn buffer_paint(&mut self, prompt: &dyn Prompt) -> Result<()> {
1638 let cursor_position_in_buffer = self.editor.insertion_point();
1639 let buffer_to_paint = self.editor.get_buffer();
1640
1641 let (before_cursor, after_cursor) = self
1642 .highlighter
1643 .highlight(buffer_to_paint, cursor_position_in_buffer)
1644 .render_around_insertion_point(
1645 cursor_position_in_buffer,
1646 prompt,
1647 self.use_ansi_coloring,
1648 );
1649
1650 let hint: String = if self.hints_active() {
1651 self.hinter.as_mut().map_or_else(String::new, |hinter| {
1652 hinter.handle(
1653 buffer_to_paint,
1654 cursor_position_in_buffer,
1655 self.history.as_ref(),
1656 self.use_ansi_coloring,
1657 )
1658 })
1659 } else {
1660 String::new()
1661 };
1662
1663 let lines = PromptLines::new(
1667 prompt,
1668 self.prompt_edit_mode(),
1669 None,
1670 &before_cursor,
1671 &after_cursor,
1672 &hint,
1673 );
1674
1675 for menu in self.menus.iter_mut() {
1677 if menu.is_active() {
1678 menu.update_working_details(
1679 &mut self.editor,
1680 self.completer.as_mut(),
1681 self.history.as_ref(),
1682 &self.painter,
1683 );
1684 }
1685 }
1686
1687 let menu = self.menus.iter().find(|menu| menu.is_active());
1688
1689 self.painter.repaint_buffer(
1690 prompt,
1691 &lines,
1692 self.prompt_edit_mode(),
1693 menu,
1694 self.use_ansi_coloring,
1695 &self.cursor_shapes,
1696 )
1697 }
1698
1699 #[cfg(feature = "external_printer")]
1704 pub fn with_external_printer(mut self, printer: ExternalPrinter<String>) -> Self {
1705 self.external_printer = Some(printer);
1706 self
1707 }
1708
1709 #[cfg(feature = "external_printer")]
1710 fn external_messages(external_printer: &ExternalPrinter<String>) -> Result<Vec<String>> {
1711 let mut messages = Vec::new();
1712 loop {
1713 let result = external_printer.receiver().try_recv();
1714 match result {
1715 Ok(line) => {
1716 let lines = line.lines().map(String::from).collect::<Vec<_>>();
1717 messages.extend(lines);
1718 }
1719 Err(TryRecvError::Empty) => {
1720 break;
1721 }
1722 Err(TryRecvError::Disconnected) => {
1723 return Err(Error::new(
1724 ErrorKind::NotConnected,
1725 TryRecvError::Disconnected,
1726 ));
1727 }
1728 }
1729 }
1730 Ok(messages)
1731 }
1732
1733 fn submit_buffer(&mut self, prompt: &dyn Prompt) -> io::Result<EventStatus> {
1734 let buffer = self.editor.get_buffer().to_string();
1735 self.hide_hints = true;
1736 if let Some(transient_prompt) = self.transient_prompt.take() {
1738 self.repaint(transient_prompt.as_ref())?;
1739 self.transient_prompt = Some(transient_prompt);
1740 } else {
1741 self.repaint(prompt)?;
1742 }
1743 if !buffer.is_empty() {
1744 let mut entry = HistoryItem::from_command_line(&buffer);
1745 entry.session_id = self.get_history_session_id();
1746
1747 if self
1748 .history_exclusion_prefix
1749 .as_ref()
1750 .map(|prefix| buffer.starts_with(prefix))
1751 .unwrap_or(false)
1752 {
1753 entry.id = Some(Self::FILTERED_ITEM_ID);
1754 self.history_last_run_id = entry.id;
1755 self.history_excluded_item = Some(entry);
1756 } else {
1757 entry = self.history.save(entry).expect("todo: error handling");
1758 self.history_last_run_id = entry.id;
1759 self.history_excluded_item = None;
1760 }
1761 }
1762 self.run_edit_commands(&[EditCommand::Clear]);
1763 self.editor.reset_undo_stack();
1764
1765 Ok(EventStatus::Exits(Signal::Success(buffer)))
1766 }
1767}
1768
1769#[test]
1770fn thread_safe() {
1771 fn f<S: Send>(_: S) {}
1772 f(Reedline::create());
1773}