1use std::path::PathBuf;
2
3use itertools::Itertools;
4use nu_ansi_term::{Color, Style};
5
6use crate::{enums::ReedlineRawEvent, CursorConfig};
7#[cfg(feature = "bashisms")]
8use crate::{
9 history::SearchFilter,
10 menu_functions::{parse_selection_char, ParseAction},
11};
12#[cfg(feature = "external_printer")]
13use {
14 crate::external_printer::ExternalPrinter,
15 crossbeam::channel::TryRecvError,
16 std::io::{Error, ErrorKind},
17};
18use {
19 crate::{
20 completion::{Completer, DefaultCompleter},
21 core_editor::Editor,
22 edit_mode::{EditMode, Emacs},
23 enums::{EventStatus, ReedlineEvent},
24 highlighter::SimpleMatchHighlighter,
25 hinter::Hinter,
26 history::{
27 FileBackedHistory, History, HistoryCursor, HistoryItem, HistoryItemId,
28 HistoryNavigationQuery, HistorySessionId, SearchDirection, SearchQuery,
29 },
30 painting::{Painter, PainterSuspendedState, PromptLines},
31 prompt::{PromptEditMode, PromptHistorySearchStatus},
32 result::{ReedlineError, ReedlineErrorVariants},
33 terminal_extensions::{bracketed_paste::BracketedPasteGuard, kitty::KittyProtocolGuard},
34 utils::text_manipulation,
35 EditCommand, ExampleHighlighter, Highlighter, LineBuffer, Menu, MenuEvent, Prompt,
36 PromptHistorySearch, ReedlineMenu, Signal, UndoBehavior, ValidationResult, Validator,
37 },
38 crossterm::{
39 cursor::{SetCursorStyle, Show},
40 event,
41 event::{Event, KeyCode, KeyEvent, KeyModifiers},
42 terminal, QueueableCommand,
43 },
44 std::{
45 fs::File, io, io::Result, io::Write, process::Command, time::Duration, time::SystemTime,
46 },
47};
48
49const POLL_WAIT: Duration = Duration::from_millis(100);
55const EVENTS_THRESHOLD: usize = 10;
59
60#[cfg(feature = "external_printer")]
63const EXTERNAL_PRINTER_WAIT: Duration = Duration::from_millis(100);
64
65#[derive(Debug, PartialEq, Eq)]
69enum InputMode {
70 Regular,
73 HistorySearch,
77 HistoryTraversal,
81}
82
83pub struct Reedline {
103 editor: Editor,
104
105 history: Box<dyn History>,
107 history_cursor: HistoryCursor,
108 history_session_id: Option<HistorySessionId>,
109 history_last_run_id: Option<HistoryItemId>,
111 history_exclusion_prefix: Option<String>,
112 history_excluded_item: Option<HistoryItem>,
113 history_cursor_on_excluded: bool,
114 input_mode: InputMode,
115
116 suspended_state: Option<PainterSuspendedState>,
119
120 validator: Option<Box<dyn Validator>>,
122
123 painter: Painter,
125
126 transient_prompt: Option<Box<dyn Prompt>>,
127
128 edit_mode: Box<dyn EditMode>,
130
131 completer: Box<dyn Completer>,
133 quick_completions: bool,
134 partial_completions: bool,
135
136 highlighter: Box<dyn Highlighter>,
138
139 visual_selection_style: Style,
141
142 hinter: Option<Box<dyn Hinter>>,
144 hide_hints: bool,
145
146 use_ansi_coloring: bool,
148
149 cwd: Option<String>,
152
153 menus: Vec<ReedlineMenu>,
155
156 buffer_editor: Option<BufferEditor>,
158
159 cursor_shapes: Option<CursorConfig>,
161
162 bracketed_paste: BracketedPasteGuard,
164
165 kitty_protocol: KittyProtocolGuard,
167
168 immediately_accept: bool,
170
171 #[cfg(feature = "external_printer")]
172 external_printer: Option<ExternalPrinter<String>>,
173}
174
175struct BufferEditor {
176 command: Command,
177 temp_file: PathBuf,
178}
179
180impl Drop for Reedline {
181 fn drop(&mut self) {
182 if self.cursor_shapes.is_some() {
183 let _ignore = terminal::enable_raw_mode();
184 let mut stdout = std::io::stdout();
185 let _ignore = stdout.queue(SetCursorStyle::DefaultUserShape);
186 let _ignore = stdout.queue(Show);
187 let _ignore = stdout.flush();
188 }
189
190 let _ignore = terminal::disable_raw_mode();
193 }
194}
195
196impl Reedline {
197 const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX);
198
199 #[must_use]
201 pub fn create() -> Self {
202 let history = Box::<FileBackedHistory>::default();
203 let painter = Painter::new(std::io::BufWriter::new(std::io::stderr()));
204 let buffer_highlighter = Box::<ExampleHighlighter>::default();
205 let visual_selection_style = Style::new().on(Color::LightGray);
206 let completer = Box::<DefaultCompleter>::default();
207 let hinter = None;
208 let validator = None;
209 let edit_mode = Box::<Emacs>::default();
210 let hist_session_id = None;
211
212 Reedline {
213 editor: Editor::default(),
214 history,
215 history_cursor: HistoryCursor::new(
216 HistoryNavigationQuery::Normal(LineBuffer::default()),
217 hist_session_id,
218 ),
219 history_session_id: hist_session_id,
220 history_last_run_id: None,
221 history_exclusion_prefix: None,
222 history_excluded_item: None,
223 history_cursor_on_excluded: false,
224 input_mode: InputMode::Regular,
225 suspended_state: None,
226 painter,
227 transient_prompt: None,
228 edit_mode,
229 completer,
230 quick_completions: false,
231 partial_completions: false,
232 highlighter: buffer_highlighter,
233 visual_selection_style,
234 hinter,
235 hide_hints: false,
236 validator,
237 use_ansi_coloring: true,
238 cwd: None,
239 menus: Vec::new(),
240 buffer_editor: None,
241 cursor_shapes: None,
242 bracketed_paste: BracketedPasteGuard::default(),
243 kitty_protocol: KittyProtocolGuard::default(),
244 immediately_accept: false,
245 #[cfg(feature = "external_printer")]
246 external_printer: None,
247 }
248 }
249
250 pub fn create_history_session_id() -> Option<HistorySessionId> {
252 let nanos = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
253 Ok(n) => n.as_nanos() as i64,
254 Err(_) => 0,
255 };
256
257 Some(HistorySessionId::new(nanos))
258 }
259
260 pub fn use_bracketed_paste(mut self, enable: bool) -> Self {
270 self.bracketed_paste.set(enable);
271 self
272 }
273
274 pub fn use_kitty_keyboard_enhancement(mut self, enable: bool) -> Self {
283 self.kitty_protocol.set(enable);
284 self
285 }
286
287 pub fn get_history_session_id(&self) -> Option<HistorySessionId> {
289 self.history_session_id
290 }
291
292 pub fn set_history_session_id(&mut self, session: Option<HistorySessionId>) -> Result<()> {
296 self.history_session_id = session;
297 Ok(())
298 }
299
300 #[must_use]
317 pub fn with_hinter(mut self, hinter: Box<dyn Hinter>) -> Self {
318 self.hinter = Some(hinter);
319 self
320 }
321
322 #[must_use]
324 pub fn disable_hints(mut self) -> Self {
325 self.hinter = None;
326 self
327 }
328
329 #[must_use]
347 pub fn with_completer(mut self, completer: Box<dyn Completer>) -> Self {
348 self.completer = completer;
349 self
350 }
351
352 #[must_use]
355 pub fn with_quick_completions(mut self, quick_completions: bool) -> Self {
356 self.quick_completions = quick_completions;
357 self
358 }
359
360 #[must_use]
363 pub fn with_partial_completions(mut self, partial_completions: bool) -> Self {
364 self.partial_completions = partial_completions;
365 self
366 }
367
368 #[must_use]
371 pub fn with_ansi_colors(mut self, use_ansi_coloring: bool) -> Self {
372 self.use_ansi_coloring = use_ansi_coloring;
373 self
374 }
375
376 #[must_use]
378 pub fn with_cwd(mut self, cwd: Option<String>) -> Self {
379 self.cwd = cwd;
380 self
381 }
382
383 #[must_use]
400 pub fn with_highlighter(mut self, highlighter: Box<dyn Highlighter>) -> Self {
401 self.highlighter = highlighter;
402 self
403 }
404
405 #[must_use]
407 pub fn with_visual_selection_style(mut self, style: Style) -> Self {
408 self.visual_selection_style = style;
409 self
410 }
411
412 #[must_use]
427 pub fn with_history(mut self, history: Box<dyn History>) -> Self {
428 self.history = history;
429 self
430 }
431
432 #[must_use]
448 pub fn with_history_exclusion_prefix(mut self, ignore_prefix: Option<String>) -> Self {
449 self.history_exclusion_prefix = ignore_prefix;
450 self
451 }
452
453 #[must_use]
464 pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
465 self.validator = Some(validator);
466 self
467 }
468
469 #[must_use]
490 pub fn with_buffer_editor(mut self, editor: Command, temp_file: PathBuf) -> Self {
491 let mut editor = editor;
492 if !editor.get_args().contains(&temp_file.as_os_str()) {
493 editor.arg(&temp_file);
494 }
495 self.buffer_editor = Some(BufferEditor {
496 command: editor,
497 temp_file,
498 });
499 self
500 }
501
502 #[must_use]
504 pub fn disable_validator(mut self) -> Self {
505 self.validator = None;
506 self
507 }
508
509 #[must_use]
511 pub fn with_transient_prompt(mut self, transient_prompt: Box<dyn Prompt>) -> Self {
512 self.transient_prompt = Some(transient_prompt);
513 self
514 }
515
516 #[must_use]
518 pub fn with_edit_mode(mut self, edit_mode: Box<dyn EditMode>) -> Self {
519 self.edit_mode = edit_mode;
520 self
521 }
522
523 #[must_use]
525 pub fn with_menu(mut self, menu: ReedlineMenu) -> Self {
526 self.menus.push(menu);
527 self
528 }
529
530 #[must_use]
532 pub fn clear_menus(mut self) -> Self {
533 self.menus = Vec::new();
534 self
535 }
536
537 #[must_use]
539 pub fn with_history_session_id(mut self, session: Option<HistorySessionId>) -> Self {
540 self.history_session_id = session;
541 self
542 }
543
544 pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self {
548 self.cursor_shapes = Some(cursor_shapes);
549 self
550 }
551
552 pub fn with_immediately_accept(mut self, immediately_accept: bool) -> Self {
554 self.immediately_accept = immediately_accept;
555 self
556 }
557
558 pub fn prompt_edit_mode(&self) -> PromptEditMode {
560 self.edit_mode.edit_mode()
561 }
562
563 pub fn print_history(&mut self) -> Result<()> {
565 let history: Vec<_> = self
566 .history
567 .search(SearchQuery::everything(SearchDirection::Forward, None))
568 .expect("todo: error handling");
569
570 for (i, entry) in history.iter().enumerate() {
571 self.print_line(&format!("{}\t{}", i, entry.command_line))?;
572 }
573 Ok(())
574 }
575
576 pub fn print_history_session(&mut self) -> Result<()> {
578 let history: Vec<_> = self
579 .history
580 .search(SearchQuery::everything(
581 SearchDirection::Forward,
582 self.get_history_session_id(),
583 ))
584 .expect("todo: error handling");
585
586 for (i, entry) in history.iter().enumerate() {
587 self.print_line(&format!("{}\t{}", i, entry.command_line))?;
588 }
589 Ok(())
590 }
591
592 pub fn print_history_session_id(&mut self) -> Result<()> {
594 println!("History Session Id: {:?}", self.get_history_session_id());
595 Ok(())
596 }
597
598 pub fn toggle_history_session_matching(
600 &mut self,
601 session: Option<HistorySessionId>,
602 ) -> Result<()> {
603 self.history_session_id = match self.get_history_session_id() {
604 Some(_) => None,
605 None => session,
606 };
607 Ok(())
608 }
609
610 pub fn history(&self) -> &dyn History {
612 &*self.history
613 }
614
615 pub fn history_mut(&mut self) -> &mut dyn History {
617 &mut *self.history
618 }
619
620 pub fn sync_history(&mut self) -> std::io::Result<()> {
622 self.history.sync()
624 }
625
626 pub fn has_last_command_context(&self) -> bool {
631 self.history_last_run_id.is_some()
632 }
633
634 pub fn update_last_command_context(
636 &mut self,
637 f: &dyn Fn(HistoryItem) -> HistoryItem,
638 ) -> crate::Result<()> {
639 match &self.history_last_run_id {
640 Some(Self::FILTERED_ITEM_ID) => {
641 self.history_excluded_item = Some(f(self.history_excluded_item.take().unwrap()));
642 Ok(())
643 }
644 Some(r) => self.history.update(*r, f),
645 None => Err(ReedlineError(ReedlineErrorVariants::OtherHistoryError(
646 "No command run",
647 ))),
648 }
649 }
650
651 pub fn read_line(&mut self, prompt: &dyn Prompt) -> Result<Signal> {
656 terminal::enable_raw_mode()?;
657 self.bracketed_paste.enter();
658 self.kitty_protocol.enter();
659
660 let result = self.read_line_helper(prompt);
661
662 self.bracketed_paste.exit();
663 self.kitty_protocol.exit();
664 terminal::disable_raw_mode()?;
665 result
666 }
667
668 pub fn current_insertion_point(&self) -> usize {
670 self.editor.insertion_point()
671 }
672
673 pub fn current_buffer_contents(&self) -> &str {
675 self.editor.get_buffer()
676 }
677
678 fn print_line(&mut self, msg: &str) -> Result<()> {
680 self.painter.paint_line(msg)
681 }
682
683 pub fn clear_screen(&mut self) -> Result<()> {
686 self.painter.clear_screen()?;
687
688 Ok(())
689 }
690
691 pub fn clear_scrollback(&mut self) -> Result<()> {
693 self.painter.clear_scrollback()?;
694
695 Ok(())
696 }
697
698 fn read_line_helper(&mut self, prompt: &dyn Prompt) -> Result<Signal> {
701 self.painter
702 .initialize_prompt_position(self.suspended_state.as_ref())?;
703 if self.suspended_state.is_some() {
704 self.suspended_state = None;
707 }
708 self.hide_hints = false;
709
710 self.repaint(prompt)?;
711
712 loop {
713 #[cfg(feature = "external_printer")]
714 if let Some(ref external_printer) = self.external_printer {
715 let messages = Self::external_messages(external_printer)?;
717 if !messages.is_empty() {
718 self.painter.print_external_message(
720 messages,
721 self.editor.line_buffer(),
722 prompt,
723 )?;
724 self.repaint(prompt)?;
725 }
726 }
727
728 fn completed(events: &[Event]) -> bool {
731 if let Some(event) = events.last() {
732 matches!(
733 event,
734 Event::Key(KeyEvent {
735 code: KeyCode::Enter,
736 modifiers: KeyModifiers::NONE,
737 ..
738 })
739 )
740 } else {
741 false
742 }
743 }
744
745 let mut events: Vec<Event> = vec![];
746
747 if !self.immediately_accept {
748 #[cfg(feature = "external_printer")]
752 if event::poll(EXTERNAL_PRINTER_WAIT)? {
753 events.push(crossterm::event::read()?);
754 }
755 #[cfg(not(feature = "external_printer"))]
756 events.push(crossterm::event::read()?);
757
758 while !completed(&events) && event::poll(Duration::from_millis(0))? {
761 events.push(crossterm::event::read()?);
762 }
763
764 if events.len() > EVENTS_THRESHOLD
767 || events.iter().any(|e| matches!(e, Event::Resize(_, _)))
768 {
769 while !completed(&events) && event::poll(POLL_WAIT)? {
770 events.push(crossterm::event::read()?);
771 }
772 }
773 }
774
775 let mut reedline_events: Vec<ReedlineEvent> = vec![];
779 let mut edits = vec![];
780 let mut resize = None;
781 for event in events {
782 if let Ok(event) = ReedlineRawEvent::try_from(event) {
783 match self.edit_mode.parse_event(event) {
784 ReedlineEvent::Edit(edit) => edits.extend(edit),
785 ReedlineEvent::Resize(x, y) => resize = Some((x, y)),
786 event => {
787 if !edits.is_empty() {
788 reedline_events
789 .push(ReedlineEvent::Edit(std::mem::take(&mut edits)));
790 }
791 reedline_events.push(event);
792 }
793 }
794 }
795 }
796 if !edits.is_empty() {
797 reedline_events.push(ReedlineEvent::Edit(edits));
798 }
799 if let Some((x, y)) = resize {
800 reedline_events.push(ReedlineEvent::Resize(x, y));
801 }
802 if self.immediately_accept {
803 reedline_events.push(ReedlineEvent::Submit);
804 }
805
806 let mut need_repaint = false;
808 for event in reedline_events {
809 match self.handle_event(prompt, event)? {
810 EventStatus::Exits(signal) => {
811 if self.suspended_state.is_none() {
814 self.painter.move_cursor_to_end()?;
817 }
818 return Ok(signal);
819 }
820 EventStatus::Handled => {
821 need_repaint = true;
822 }
823 EventStatus::Inapplicable => {
824 }
826 }
827 }
828 if need_repaint {
829 self.repaint(prompt)?;
830 }
831 }
832 }
833
834 fn handle_event(&mut self, prompt: &dyn Prompt, event: ReedlineEvent) -> Result<EventStatus> {
835 if self.input_mode == InputMode::HistorySearch {
836 self.handle_history_search_event(event)
837 } else {
838 self.handle_editor_event(prompt, event)
839 }
840 }
841
842 fn handle_history_search_event(&mut self, event: ReedlineEvent) -> io::Result<EventStatus> {
843 match event {
844 ReedlineEvent::UntilFound(events) => {
845 for event in events {
846 match self.handle_history_search_event(event)? {
847 EventStatus::Inapplicable => {
848 }
850 success => {
851 return Ok(success);
852 }
853 }
854 }
855 Ok(EventStatus::Handled)
857 }
858 ReedlineEvent::CtrlD => {
859 if self.editor.is_empty() {
860 self.input_mode = InputMode::Regular;
861 self.editor.reset_undo_stack();
862 Ok(EventStatus::Exits(Signal::CtrlD))
863 } else {
864 self.run_history_commands(&[EditCommand::Delete]);
865 Ok(EventStatus::Handled)
866 }
867 }
868 ReedlineEvent::CtrlC => {
869 self.input_mode = InputMode::Regular;
870 Ok(EventStatus::Exits(Signal::CtrlC))
871 }
872 ReedlineEvent::ClearScreen => {
873 self.painter.clear_screen()?;
874 Ok(EventStatus::Handled)
875 }
876 ReedlineEvent::ClearScrollback => {
877 self.painter.clear_scrollback()?;
878 Ok(EventStatus::Handled)
879 }
880 ReedlineEvent::Enter
881 | ReedlineEvent::HistoryHintComplete
882 | ReedlineEvent::Submit
883 | ReedlineEvent::SubmitOrNewline => {
884 if let Some(string) = self.history_cursor.string_at_cursor() {
885 self.editor
886 .set_buffer(string, UndoBehavior::CreateUndoPoint);
887 }
888
889 self.input_mode = InputMode::Regular;
890 Ok(EventStatus::Handled)
891 }
892 ReedlineEvent::ExecuteHostCommand(host_command) => {
893 self.suspended_state = Some(self.painter.state_before_suspension());
894 Ok(EventStatus::Exits(Signal::Success(host_command)))
895 }
896 ReedlineEvent::Edit(commands) => {
897 self.run_history_commands(&commands);
898 Ok(EventStatus::Handled)
899 }
900 ReedlineEvent::Mouse => Ok(EventStatus::Handled),
901 ReedlineEvent::Resize(width, height) => {
902 self.painter.handle_resize(width, height);
903 Ok(EventStatus::Handled)
904 }
905 ReedlineEvent::Repaint => {
906 Ok(EventStatus::Handled)
908 }
909 ReedlineEvent::PreviousHistory | ReedlineEvent::Up | ReedlineEvent::SearchHistory => {
910 self.history_cursor
911 .back(self.history.as_ref())
912 .expect("todo: error handling");
913 Ok(EventStatus::Handled)
914 }
915 ReedlineEvent::NextHistory | ReedlineEvent::Down => {
916 self.history_cursor
917 .forward(self.history.as_ref())
918 .expect("todo: error handling");
919 if self.history_cursor.string_at_cursor().is_none() {
921 self.history_cursor
922 .back(self.history.as_ref())
923 .expect("todo: error handling");
924 }
925 Ok(EventStatus::Handled)
926 }
927 ReedlineEvent::Esc => {
928 self.input_mode = InputMode::Regular;
929 Ok(EventStatus::Handled)
930 }
931 ReedlineEvent::Right
933 | ReedlineEvent::Left
934 | ReedlineEvent::Multiple(_)
935 | ReedlineEvent::None
936 | ReedlineEvent::HistoryHintWordComplete
937 | ReedlineEvent::OpenEditor
938 | ReedlineEvent::Menu(_)
939 | ReedlineEvent::MenuNext
940 | ReedlineEvent::MenuPrevious
941 | ReedlineEvent::MenuUp
942 | ReedlineEvent::MenuDown
943 | ReedlineEvent::MenuLeft
944 | ReedlineEvent::MenuRight
945 | ReedlineEvent::MenuPageNext
946 | ReedlineEvent::MenuPagePrevious
947 | ReedlineEvent::ViChangeMode(_) => Ok(EventStatus::Inapplicable),
948 }
949 }
950
951 fn handle_editor_event(
952 &mut self,
953 prompt: &dyn Prompt,
954 event: ReedlineEvent,
955 ) -> io::Result<EventStatus> {
956 match event {
957 ReedlineEvent::Menu(name) => {
958 if self.active_menu().is_none() {
959 if let Some(menu) = self.menus.iter_mut().find(|menu| menu.name() == name) {
960 menu.menu_event(MenuEvent::Activate(self.quick_completions));
961
962 if self.quick_completions && menu.can_quick_complete() {
963 menu.update_values(
964 &mut self.editor,
965 self.completer.as_mut(),
966 self.history.as_ref(),
967 );
968
969 if menu.get_values().len() == 1 {
970 return self.handle_editor_event(prompt, ReedlineEvent::Enter);
971 }
972 }
973
974 if self.partial_completions
975 && menu.can_partially_complete(
976 self.quick_completions,
977 &mut self.editor,
978 self.completer.as_mut(),
979 self.history.as_ref(),
980 )
981 {
982 return Ok(EventStatus::Handled);
983 }
984
985 return Ok(EventStatus::Handled);
986 }
987 }
988 Ok(EventStatus::Inapplicable)
989 }
990 ReedlineEvent::MenuNext => {
991 if let Some(menu) = self.menus.iter_mut().find(|menu| menu.is_active()) {
992 if menu.get_values().len() == 1 && menu.can_quick_complete() {
993 self.handle_editor_event(prompt, ReedlineEvent::Enter)
994 } else {
995 if self.partial_completions {
996 menu.can_partially_complete(
997 self.quick_completions,
998 &mut self.editor,
999 self.completer.as_mut(),
1000 self.history.as_ref(),
1001 );
1002 }
1003 menu.menu_event(MenuEvent::NextElement);
1004 Ok(EventStatus::Handled)
1005 }
1006 } else {
1007 Ok(EventStatus::Inapplicable)
1008 }
1009 }
1010 ReedlineEvent::MenuPrevious => {
1011 self.active_menu()
1012 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1013 menu.menu_event(MenuEvent::PreviousElement);
1014 Ok(EventStatus::Handled)
1015 })
1016 }
1017 ReedlineEvent::MenuUp => {
1018 self.active_menu()
1019 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1020 menu.menu_event(MenuEvent::MoveUp);
1021 Ok(EventStatus::Handled)
1022 })
1023 }
1024 ReedlineEvent::MenuDown => {
1025 self.active_menu()
1026 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1027 menu.menu_event(MenuEvent::MoveDown);
1028 Ok(EventStatus::Handled)
1029 })
1030 }
1031 ReedlineEvent::MenuLeft => {
1032 self.active_menu()
1033 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1034 menu.menu_event(MenuEvent::MoveLeft);
1035 Ok(EventStatus::Handled)
1036 })
1037 }
1038 ReedlineEvent::MenuRight => {
1039 self.active_menu()
1040 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1041 menu.menu_event(MenuEvent::MoveRight);
1042 Ok(EventStatus::Handled)
1043 })
1044 }
1045 ReedlineEvent::MenuPageNext => {
1046 self.active_menu()
1047 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1048 menu.menu_event(MenuEvent::NextPage);
1049 Ok(EventStatus::Handled)
1050 })
1051 }
1052 ReedlineEvent::MenuPagePrevious => {
1053 self.active_menu()
1054 .map_or(Ok(EventStatus::Inapplicable), |menu| {
1055 menu.menu_event(MenuEvent::PreviousPage);
1056 Ok(EventStatus::Handled)
1057 })
1058 }
1059 ReedlineEvent::HistoryHintComplete => {
1060 if let Some(hinter) = self.hinter.as_mut() {
1061 let current_hint = hinter.complete_hint();
1062 if self.hints_active()
1063 && self.editor.is_cursor_at_buffer_end()
1064 && !current_hint.is_empty()
1065 && self.active_menu().is_none()
1066 {
1067 self.run_edit_commands(&[EditCommand::InsertString(current_hint)]);
1068 return Ok(EventStatus::Handled);
1069 }
1070 }
1071 Ok(EventStatus::Inapplicable)
1072 }
1073 ReedlineEvent::HistoryHintWordComplete => {
1074 if let Some(hinter) = self.hinter.as_mut() {
1075 let current_hint_part = hinter.next_hint_token();
1076 if self.hints_active()
1077 && self.editor.is_cursor_at_buffer_end()
1078 && !current_hint_part.is_empty()
1079 && self.active_menu().is_none()
1080 {
1081 self.run_edit_commands(&[EditCommand::InsertString(current_hint_part)]);
1082 return Ok(EventStatus::Handled);
1083 }
1084 }
1085 Ok(EventStatus::Inapplicable)
1086 }
1087 ReedlineEvent::Esc => {
1088 self.deactivate_menus();
1089 self.editor.reset_selection();
1090 Ok(EventStatus::Handled)
1091 }
1092 ReedlineEvent::CtrlD => {
1093 if self.editor.is_empty() {
1094 self.editor.reset_undo_stack();
1095 Ok(EventStatus::Exits(Signal::CtrlD))
1096 } else {
1097 self.run_edit_commands(&[EditCommand::Delete]);
1098 Ok(EventStatus::Handled)
1099 }
1100 }
1101 ReedlineEvent::CtrlC => {
1102 self.deactivate_menus();
1103 self.run_edit_commands(&[EditCommand::Clear]);
1104 self.editor.reset_undo_stack();
1105 Ok(EventStatus::Exits(Signal::CtrlC))
1106 }
1107 ReedlineEvent::ClearScreen => {
1108 self.deactivate_menus();
1109 self.painter.clear_screen()?;
1110 Ok(EventStatus::Handled)
1111 }
1112 ReedlineEvent::ClearScrollback => {
1113 self.deactivate_menus();
1114 self.painter.clear_scrollback()?;
1115 Ok(EventStatus::Handled)
1116 }
1117 ReedlineEvent::Enter | ReedlineEvent::Submit | ReedlineEvent::SubmitOrNewline
1118 if self.menus.iter().any(|menu| menu.is_active()) =>
1119 {
1120 for menu in self.menus.iter_mut() {
1121 if menu.is_active() {
1122 menu.replace_in_buffer(&mut self.editor);
1123 menu.menu_event(MenuEvent::Deactivate);
1124
1125 return Ok(EventStatus::Handled);
1126 }
1127 }
1128 unreachable!()
1129 }
1130 ReedlineEvent::Enter => {
1131 #[cfg(feature = "bashisms")]
1132 if let Some(event) = self.parse_bang_command() {
1133 return self.handle_editor_event(prompt, event);
1134 }
1135
1136 let buffer = self.editor.get_buffer().to_string();
1137 match self.validator.as_mut().map(|v| v.validate(&buffer)) {
1138 None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
1139 Some(ValidationResult::Incomplete) => {
1140 self.run_edit_commands(&[EditCommand::InsertNewline]);
1141
1142 Ok(EventStatus::Handled)
1143 }
1144 }
1145 }
1146 ReedlineEvent::Submit => {
1147 #[cfg(feature = "bashisms")]
1148 if let Some(event) = self.parse_bang_command() {
1149 return self.handle_editor_event(prompt, event);
1150 }
1151 Ok(self.submit_buffer(prompt)?)
1152 }
1153 ReedlineEvent::SubmitOrNewline => {
1154 #[cfg(feature = "bashisms")]
1155 if let Some(event) = self.parse_bang_command() {
1156 return self.handle_editor_event(prompt, event);
1157 }
1158 let cursor_position_in_buffer = self.editor.insertion_point();
1159 let buffer = self.editor.get_buffer().to_string();
1160 if cursor_position_in_buffer < buffer.len() {
1161 self.run_edit_commands(&[EditCommand::InsertNewline]);
1162 return Ok(EventStatus::Handled);
1163 }
1164 match self.validator.as_mut().map(|v| v.validate(&buffer)) {
1165 None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
1166 Some(ValidationResult::Incomplete) => {
1167 self.run_edit_commands(&[EditCommand::InsertNewline]);
1168
1169 Ok(EventStatus::Handled)
1170 }
1171 }
1172 }
1173 ReedlineEvent::ExecuteHostCommand(host_command) => {
1174 self.suspended_state = Some(self.painter.state_before_suspension());
1175 Ok(EventStatus::Exits(Signal::Success(host_command)))
1176 }
1177 ReedlineEvent::Edit(commands) => {
1178 self.run_edit_commands(&commands);
1179 if let Some(menu) = self.menus.iter_mut().find(|men| men.is_active()) {
1180 if self.quick_completions && menu.can_quick_complete() {
1181 match commands.first() {
1182 Some(&EditCommand::Backspace)
1183 | Some(&EditCommand::BackspaceWord)
1184 | Some(&EditCommand::MoveToLineStart { select: false }) => {
1185 menu.menu_event(MenuEvent::Deactivate)
1186 }
1187 _ => {
1188 menu.menu_event(MenuEvent::Edit(self.quick_completions));
1189 menu.update_values(
1190 &mut self.editor,
1191 self.completer.as_mut(),
1192 self.history.as_ref(),
1193 );
1194 if let Some(&EditCommand::Complete) = commands.first() {
1195 if menu.get_values().len() == 1 {
1196 return self
1197 .handle_editor_event(prompt, ReedlineEvent::Enter);
1198 } else if self.partial_completions
1199 && menu.can_partially_complete(
1200 self.quick_completions,
1201 &mut self.editor,
1202 self.completer.as_mut(),
1203 self.history.as_ref(),
1204 )
1205 {
1206 return Ok(EventStatus::Handled);
1207 }
1208 }
1209 }
1210 }
1211 }
1212 if self.editor.line_buffer().get_buffer().is_empty() {
1213 menu.menu_event(MenuEvent::Deactivate);
1214 } else {
1215 menu.menu_event(MenuEvent::Edit(self.quick_completions));
1216 }
1217 }
1218 Ok(EventStatus::Handled)
1219 }
1220 ReedlineEvent::OpenEditor => self.open_editor().map(|_| EventStatus::Handled),
1221 ReedlineEvent::Resize(width, height) => {
1222 self.painter.handle_resize(width, height);
1223 Ok(EventStatus::Handled)
1224 }
1225 ReedlineEvent::Repaint => {
1226 Ok(EventStatus::Handled)
1228 }
1229 ReedlineEvent::PreviousHistory => {
1230 self.previous_history();
1231 Ok(EventStatus::Handled)
1232 }
1233 ReedlineEvent::NextHistory => {
1234 self.next_history();
1235 Ok(EventStatus::Handled)
1236 }
1237 ReedlineEvent::Up => {
1238 self.up_command();
1239 Ok(EventStatus::Handled)
1240 }
1241 ReedlineEvent::Down => {
1242 self.down_command();
1243 Ok(EventStatus::Handled)
1244 }
1245 ReedlineEvent::Left => {
1246 self.run_edit_commands(&[EditCommand::MoveLeft { select: false }]);
1247 Ok(EventStatus::Handled)
1248 }
1249 ReedlineEvent::Right => {
1250 self.run_edit_commands(&[EditCommand::MoveRight { select: false }]);
1251 Ok(EventStatus::Handled)
1252 }
1253 ReedlineEvent::SearchHistory => {
1254 self.enter_history_search();
1255 Ok(EventStatus::Handled)
1256 }
1257 ReedlineEvent::Multiple(events) => {
1258 let mut latest_signal = EventStatus::Inapplicable;
1259 for event in events {
1260 match self.handle_editor_event(prompt, event)? {
1261 EventStatus::Handled => {
1262 latest_signal = EventStatus::Handled;
1263 }
1264 EventStatus::Inapplicable => {
1265 }
1267 EventStatus::Exits(signal) => {
1268 return Ok(EventStatus::Exits(signal));
1272 }
1273 }
1274 }
1275
1276 Ok(latest_signal)
1277 }
1278 ReedlineEvent::UntilFound(events) => {
1279 for event in events {
1280 match self.handle_editor_event(prompt, event)? {
1281 EventStatus::Inapplicable => {
1282 }
1284 success => {
1285 return Ok(success);
1286 }
1287 }
1288 }
1289 Ok(EventStatus::Inapplicable)
1291 }
1292 ReedlineEvent::ViChangeMode(_) => Ok(self.edit_mode.handle_mode_specific_event(event)),
1293 ReedlineEvent::None | ReedlineEvent::Mouse => Ok(EventStatus::Inapplicable),
1294 }
1295 }
1296
1297 fn active_menu(&mut self) -> Option<&mut ReedlineMenu> {
1298 self.menus.iter_mut().find(|menu| menu.is_active())
1299 }
1300
1301 fn deactivate_menus(&mut self) {
1302 self.menus
1303 .iter_mut()
1304 .for_each(|menu| menu.menu_event(MenuEvent::Deactivate));
1305 }
1306
1307 fn previous_history(&mut self) {
1308 self.history_cursor_on_excluded = false;
1309 if self.input_mode != InputMode::HistoryTraversal {
1310 self.input_mode = InputMode::HistoryTraversal;
1311 self.history_cursor = HistoryCursor::new(
1312 self.get_history_navigation_based_on_line_buffer(),
1313 self.get_history_session_id(),
1314 );
1315
1316 if self.history_excluded_item.is_some() {
1317 self.history_cursor_on_excluded = true;
1318 }
1319 }
1320
1321 if !self.history_cursor_on_excluded {
1322 self.history_cursor
1323 .back(self.history.as_ref())
1324 .expect("todo: error handling");
1325 }
1326 self.update_buffer_from_history();
1327 self.editor.move_to_start(false);
1328 self.editor.move_to_line_end(false);
1329 self.editor
1330 .update_undo_state(UndoBehavior::HistoryNavigation);
1331 }
1332
1333 fn next_history(&mut self) {
1334 if self.input_mode != InputMode::HistoryTraversal {
1335 self.input_mode = InputMode::HistoryTraversal;
1336 self.history_cursor = HistoryCursor::new(
1337 self.get_history_navigation_based_on_line_buffer(),
1338 self.get_history_session_id(),
1339 );
1340 }
1341
1342 if self.history_cursor_on_excluded {
1343 self.history_cursor_on_excluded = false;
1344 } else {
1345 let cursor_was_on_item = self.history_cursor.string_at_cursor().is_some();
1346 self.history_cursor
1347 .forward(self.history.as_ref())
1348 .expect("todo: error handling");
1349
1350 if cursor_was_on_item
1351 && self.history_cursor.string_at_cursor().is_none()
1352 && self.history_excluded_item.is_some()
1353 {
1354 self.history_cursor_on_excluded = true;
1355 }
1356 }
1357
1358 if self.history_cursor.string_at_cursor().is_none() && !self.history_cursor_on_excluded {
1359 self.input_mode = InputMode::Regular;
1360 }
1361 self.update_buffer_from_history();
1362 self.editor.move_to_end(false);
1363 self.editor
1364 .update_undo_state(UndoBehavior::HistoryNavigation)
1365 }
1366
1367 fn get_history_navigation_based_on_line_buffer(&self) -> HistoryNavigationQuery {
1371 if self.editor.is_empty() || !self.editor.is_cursor_at_buffer_end() {
1372 HistoryNavigationQuery::Normal(
1374 self.editor.line_buffer().clone(),
1376 )
1377 } else {
1378 let buffer = self.editor.get_buffer().to_string();
1384 HistoryNavigationQuery::PrefixSearch(buffer)
1385 }
1386 }
1387
1388 fn enter_history_search(&mut self) {
1392 self.history_cursor = HistoryCursor::new(
1393 HistoryNavigationQuery::SubstringSearch("".to_string()),
1394 self.get_history_session_id(),
1395 );
1396 self.input_mode = InputMode::HistorySearch;
1397 }
1398
1399 fn run_history_commands(&mut self, commands: &[EditCommand]) {
1403 for command in commands {
1404 match command {
1405 EditCommand::InsertChar(c) => {
1406 let navigation = self.history_cursor.get_navigation();
1407 if let HistoryNavigationQuery::SubstringSearch(mut substring) = navigation {
1408 substring.push(*c);
1409 self.history_cursor = HistoryCursor::new(
1410 HistoryNavigationQuery::SubstringSearch(substring),
1411 self.get_history_session_id(),
1412 );
1413 } else {
1414 self.history_cursor = HistoryCursor::new(
1415 HistoryNavigationQuery::SubstringSearch(String::from(*c)),
1416 self.get_history_session_id(),
1417 );
1418 }
1419 self.history_cursor
1420 .back(self.history.as_mut())
1421 .expect("todo: error handling");
1422 }
1423 EditCommand::Backspace => {
1424 let navigation = self.history_cursor.get_navigation();
1425
1426 if let HistoryNavigationQuery::SubstringSearch(substring) = navigation {
1427 let new_substring = text_manipulation::remove_last_grapheme(&substring);
1428
1429 self.history_cursor = HistoryCursor::new(
1430 HistoryNavigationQuery::SubstringSearch(new_substring.to_string()),
1431 self.get_history_session_id(),
1432 );
1433 self.history_cursor
1434 .back(self.history.as_mut())
1435 .expect("todo: error handling");
1436 }
1437 }
1438 _ => {
1439 self.input_mode = InputMode::Regular;
1440 }
1441 }
1442 }
1443 }
1444
1445 fn update_buffer_from_history(&mut self) {
1450 match self.history_cursor.get_navigation() {
1451 _ if self.history_cursor_on_excluded => self.editor.set_buffer(
1452 self.history_excluded_item
1453 .as_ref()
1454 .unwrap()
1455 .command_line
1456 .clone(),
1457 UndoBehavior::HistoryNavigation,
1458 ),
1459 HistoryNavigationQuery::Normal(original) => {
1460 if let Some(buffer_to_paint) = self.history_cursor.string_at_cursor() {
1461 self.editor
1462 .set_buffer(buffer_to_paint, UndoBehavior::HistoryNavigation);
1463 } else {
1464 self.editor
1466 .set_line_buffer(original, UndoBehavior::HistoryNavigation);
1467 }
1468 }
1469 HistoryNavigationQuery::PrefixSearch(prefix) => {
1470 if let Some(prefix_result) = self.history_cursor.string_at_cursor() {
1471 self.editor
1472 .set_buffer(prefix_result, UndoBehavior::HistoryNavigation);
1473 } else {
1474 self.editor
1475 .set_buffer(prefix, UndoBehavior::HistoryNavigation);
1476 }
1477 }
1478 HistoryNavigationQuery::SubstringSearch(_) => todo!(),
1479 }
1480 }
1481
1482 pub fn run_edit_commands(&mut self, commands: &[EditCommand]) {
1484 if self.input_mode == InputMode::HistoryTraversal {
1485 if matches!(
1486 self.history_cursor.get_navigation(),
1487 HistoryNavigationQuery::Normal(_)
1488 ) {
1489 if let Some(string) = self.history_cursor.string_at_cursor() {
1490 if string != self.editor.get_buffer() {
1494 self.editor
1495 .set_buffer(string, UndoBehavior::HistoryNavigation);
1496 }
1497 }
1498 }
1499 self.input_mode = InputMode::Regular;
1500 }
1501
1502 for command in commands {
1504 self.editor.run_edit_command(command);
1505 }
1506 }
1507
1508 fn up_command(&mut self) {
1509 if self.editor.is_cursor_at_first_line() {
1511 self.previous_history();
1513 } else {
1514 self.editor.move_line_up();
1515 }
1516 }
1517
1518 fn down_command(&mut self) {
1519 if self.editor.is_cursor_at_last_line() {
1521 self.next_history();
1523 } else {
1524 self.editor.move_line_down();
1525 }
1526 }
1527
1528 fn hints_active(&self) -> bool {
1530 !self.hide_hints && matches!(self.input_mode, InputMode::Regular)
1531 }
1532
1533 fn repaint(&mut self, prompt: &dyn Prompt) -> io::Result<()> {
1535 if self.input_mode == InputMode::HistorySearch {
1537 self.history_search_paint(prompt)
1538 } else {
1539 self.buffer_paint(prompt)
1540 }
1541 }
1542
1543 #[cfg(feature = "bashisms")]
1544 fn parse_bang_command(&mut self) -> Option<ReedlineEvent> {
1546 let buffer = self.editor.get_buffer();
1547 let parsed = parse_selection_char(buffer, '!');
1548 let parsed_prefix = parsed.prefix.unwrap_or_default().to_string();
1549 let parsed_marker = parsed.marker.unwrap_or_default().to_string();
1550
1551 if let Some(last) = parsed.remainder.chars().last() {
1552 if last != ' ' {
1553 return None;
1554 }
1555 }
1556
1557 let history_result = parsed
1558 .index
1559 .zip(parsed.marker)
1560 .and_then(|(index, indicator)| match parsed.action {
1561 ParseAction::LastCommand => self
1562 .history
1563 .search(SearchQuery {
1564 direction: SearchDirection::Backward,
1565 start_time: None,
1566 end_time: None,
1567 start_id: None,
1568 end_id: None,
1569 limit: Some(1), filter: SearchFilter::anything(self.get_history_session_id()),
1571 })
1572 .unwrap_or_else(|_| Vec::new())
1573 .get(index.saturating_sub(1))
1574 .map(|history| {
1575 (
1576 parsed.remainder.len(),
1577 indicator.len(),
1578 history.command_line.clone(),
1579 )
1580 }),
1581 ParseAction::BackwardSearch => self
1582 .history
1583 .search(SearchQuery {
1584 direction: SearchDirection::Backward,
1585 start_time: None,
1586 end_time: None,
1587 start_id: None,
1588 end_id: None,
1589 limit: Some(index as i64), filter: SearchFilter::anything(self.get_history_session_id()),
1591 })
1592 .unwrap_or_else(|_| Vec::new())
1593 .get(index.saturating_sub(1))
1594 .map(|history| {
1595 (
1596 parsed.remainder.len(),
1597 indicator.len(),
1598 history.command_line.clone(),
1599 )
1600 }),
1601 ParseAction::BackwardPrefixSearch => {
1602 let history_search_by_session = self
1603 .history
1604 .search(SearchQuery::last_with_prefix_and_cwd(
1605 parsed.prefix.unwrap().to_string(),
1606 self.cwd.clone().unwrap_or_else(|| {
1607 std::env::current_dir()
1608 .unwrap_or_default()
1609 .to_string_lossy()
1610 .to_string()
1611 }),
1612 self.get_history_session_id(),
1613 ))
1614 .unwrap_or_else(|_| Vec::new())
1615 .get(index.saturating_sub(1))
1616 .map(|history| {
1617 (
1618 parsed.remainder.len(),
1619 parsed_prefix.len() + parsed_marker.len(),
1620 history.command_line.clone(),
1621 )
1622 });
1623 if history_search_by_session.is_none() {
1626 self.history
1627 .search(SearchQuery::last_with_prefix(
1628 parsed_prefix.clone(),
1629 self.get_history_session_id(),
1630 ))
1631 .unwrap_or_else(|_| Vec::new())
1632 .get(index.saturating_sub(1))
1633 .map(|history| {
1634 (
1635 parsed.remainder.len(),
1636 parsed_prefix.len() + parsed_marker.len(),
1637 history.command_line.clone(),
1638 )
1639 })
1640 } else {
1641 history_search_by_session
1642 }
1643 }
1644 ParseAction::ForwardSearch => self
1645 .history
1646 .search(SearchQuery {
1647 direction: SearchDirection::Forward,
1648 start_time: None,
1649 end_time: None,
1650 start_id: None,
1651 end_id: None,
1652 limit: Some((index + 1) as i64), filter: SearchFilter::anything(self.get_history_session_id()),
1654 })
1655 .unwrap_or_else(|_| Vec::new())
1656 .get(index)
1657 .map(|history| {
1658 (
1659 parsed.remainder.len(),
1660 indicator.len(),
1661 history.command_line.clone(),
1662 )
1663 }),
1664 ParseAction::LastToken => self
1665 .history
1666 .search(SearchQuery::last_with_search(SearchFilter::anything(
1667 self.get_history_session_id(),
1668 )))
1669 .unwrap_or_else(|_| Vec::new())
1670 .first()
1671 .and_then(|history| history.command_line.split_whitespace().next_back())
1673 .map(|token| (parsed.remainder.len(), indicator.len(), token.to_string())),
1674 });
1675
1676 if let Some((start, size, history)) = history_result {
1677 let edits = vec![
1678 EditCommand::MoveToPosition {
1679 position: start,
1680 select: false,
1681 },
1682 EditCommand::ReplaceChars(size, history),
1683 ];
1684
1685 Some(ReedlineEvent::Edit(edits))
1686 } else {
1687 None
1688 }
1689 }
1690
1691 fn open_editor(&mut self) -> Result<()> {
1692 match &mut self.buffer_editor {
1693 Some(BufferEditor {
1694 ref mut command,
1695 ref temp_file,
1696 }) => {
1697 {
1698 let mut file = File::create(temp_file)?;
1699 write!(file, "{}", self.editor.get_buffer())?;
1700 }
1701 {
1702 let mut child = command.spawn()?;
1703 child.wait()?;
1704 }
1705
1706 let res = std::fs::read_to_string(temp_file)?;
1707 let res = res.trim_end().to_string();
1708
1709 self.editor.set_buffer(res, UndoBehavior::CreateUndoPoint);
1710
1711 Ok(())
1712 }
1713 _ => Ok(()),
1714 }
1715 }
1716
1717 fn history_search_paint(&mut self, prompt: &dyn Prompt) -> Result<()> {
1722 let navigation = self.history_cursor.get_navigation();
1723
1724 if let HistoryNavigationQuery::SubstringSearch(substring) = navigation {
1725 let status =
1726 if !substring.is_empty() && self.history_cursor.string_at_cursor().is_none() {
1727 PromptHistorySearchStatus::Failing
1728 } else {
1729 PromptHistorySearchStatus::Passing
1730 };
1731
1732 let prompt_history_search = PromptHistorySearch::new(status, substring.clone());
1733
1734 let res_string = self.history_cursor.string_at_cursor().unwrap_or_default();
1735
1736 let res_string = if self.use_ansi_coloring {
1738 let match_highlighter = SimpleMatchHighlighter::new(substring);
1739 let styled = match_highlighter.highlight(&res_string, 0);
1740 styled.render_simple()
1741 } else {
1742 res_string
1743 };
1744
1745 let lines = PromptLines::new(
1746 prompt,
1747 self.prompt_edit_mode(),
1748 Some(prompt_history_search),
1749 &res_string,
1750 "",
1751 "",
1752 );
1753
1754 self.painter.repaint_buffer(
1755 prompt,
1756 &lines,
1757 self.prompt_edit_mode(),
1758 None,
1759 self.use_ansi_coloring,
1760 &self.cursor_shapes,
1761 )?;
1762 }
1763
1764 Ok(())
1765 }
1766
1767 fn buffer_paint(&mut self, prompt: &dyn Prompt) -> Result<()> {
1771 let cursor_position_in_buffer = self.editor.insertion_point();
1772 let buffer_to_paint = self.editor.get_buffer();
1773
1774 let mut styled_text = self
1775 .highlighter
1776 .highlight(buffer_to_paint, cursor_position_in_buffer);
1777 if let Some((from, to)) = self.editor.get_selection() {
1778 styled_text.style_range(from, to, self.visual_selection_style);
1779 }
1780
1781 let (before_cursor, after_cursor) = styled_text.render_around_insertion_point(
1782 cursor_position_in_buffer,
1783 prompt,
1784 self.use_ansi_coloring,
1785 );
1786
1787 let hint: String = if self.hints_active() {
1788 self.hinter.as_mut().map_or_else(String::new, |hinter| {
1789 hinter.handle(
1790 buffer_to_paint,
1791 cursor_position_in_buffer,
1792 self.history.as_ref(),
1793 self.use_ansi_coloring,
1794 &self.cwd.clone().unwrap_or_else(|| {
1795 std::env::current_dir()
1796 .unwrap_or_default()
1797 .to_string_lossy()
1798 .to_string()
1799 }),
1800 )
1801 })
1802 } else {
1803 String::new()
1804 };
1805
1806 let mut lines = PromptLines::new(
1810 prompt,
1811 self.prompt_edit_mode(),
1812 None,
1813 &before_cursor,
1814 &after_cursor,
1815 &hint,
1816 );
1817
1818 for menu in self.menus.iter_mut() {
1820 if menu.is_active() {
1821 lines.prompt_indicator = menu.indicator().to_owned().into();
1822 let cursor_pos = lines.cursor_pos(self.painter.screen_width());
1824 menu.set_cursor_pos(cursor_pos);
1825
1826 menu.update_working_details(
1827 &mut self.editor,
1828 self.completer.as_mut(),
1829 self.history.as_ref(),
1830 &self.painter,
1831 );
1832 }
1833 }
1834
1835 let menu = self.menus.iter().find(|menu| menu.is_active());
1836
1837 self.painter.repaint_buffer(
1838 prompt,
1839 &lines,
1840 self.prompt_edit_mode(),
1841 menu,
1842 self.use_ansi_coloring,
1843 &self.cursor_shapes,
1844 )
1845 }
1846
1847 #[cfg(feature = "external_printer")]
1852 pub fn with_external_printer(mut self, printer: ExternalPrinter<String>) -> Self {
1853 self.external_printer = Some(printer);
1854 self
1855 }
1856
1857 #[cfg(feature = "external_printer")]
1858 fn external_messages(external_printer: &ExternalPrinter<String>) -> Result<Vec<String>> {
1859 let mut messages = Vec::new();
1860 loop {
1861 let result = external_printer.receiver().try_recv();
1862 match result {
1863 Ok(line) => {
1864 let lines = line.lines().map(String::from).collect::<Vec<_>>();
1865 messages.extend(lines);
1866 }
1867 Err(TryRecvError::Empty) => {
1868 break;
1869 }
1870 Err(TryRecvError::Disconnected) => {
1871 return Err(Error::new(
1872 ErrorKind::NotConnected,
1873 TryRecvError::Disconnected,
1874 ));
1875 }
1876 }
1877 }
1878 Ok(messages)
1879 }
1880
1881 fn submit_buffer(&mut self, prompt: &dyn Prompt) -> io::Result<EventStatus> {
1882 let buffer = self.editor.get_buffer().to_string();
1883 self.hide_hints = true;
1884 if let Some(transient_prompt) = self.transient_prompt.take() {
1886 self.repaint(transient_prompt.as_ref())?;
1887 self.transient_prompt = Some(transient_prompt);
1888 } else {
1889 self.repaint(prompt)?;
1890 }
1891 if !buffer.is_empty() {
1892 let mut entry = HistoryItem::from_command_line(&buffer);
1893 entry.session_id = self.get_history_session_id();
1894
1895 if self
1896 .history_exclusion_prefix
1897 .as_ref()
1898 .map(|prefix| buffer.starts_with(prefix))
1899 .unwrap_or(false)
1900 {
1901 entry.id = Some(Self::FILTERED_ITEM_ID);
1902 self.history_last_run_id = entry.id;
1903 self.history_excluded_item = Some(entry);
1904 } else {
1905 entry = self.history.save(entry).expect("todo: error handling");
1906 self.history_last_run_id = entry.id;
1907 self.history_excluded_item = None;
1908 }
1909 }
1910 self.run_edit_commands(&[EditCommand::Clear]);
1911 self.editor.reset_undo_stack();
1912
1913 Ok(EventStatus::Exits(Signal::Success(buffer)))
1914 }
1915}
1916
1917#[cfg(test)]
1918mod tests {
1919 use super::*;
1920
1921 #[test]
1922 fn test_cursor_position_after_multiline_history_navigation() {
1923 let mut reedline = Reedline::create();
1930
1931 let multiline_command = "echo 'line 1'\necho 'line 2'\necho 'line 3'";
1933 let history_item = HistoryItem::from_command_line(multiline_command);
1934 reedline
1935 .history
1936 .save(history_item)
1937 .expect("Failed to save history");
1938
1939 reedline.previous_history();
1941
1942 let initial_insertion_point = reedline.current_insertion_point();
1944
1945 assert_eq!(reedline.current_buffer_contents(), multiline_command);
1947
1948 let first_line_end = multiline_command.find('\n').unwrap();
1951 assert_eq!(initial_insertion_point, first_line_end);
1952
1953 reedline.run_edit_commands(&[EditCommand::MoveRight { select: false }]);
1958
1959 let after_move_insertion_point = reedline.current_insertion_point();
1960
1961 assert_eq!(after_move_insertion_point, initial_insertion_point + 1);
1963
1964 assert_eq!(reedline.current_buffer_contents(), multiline_command);
1966 }
1967
1968 #[test]
1969 fn thread_safe() {
1970 fn f<S: Send>(_: S) {}
1971 f(Reedline::create());
1972 }
1973}