reedline/
engine.rs

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
48// The POLL_WAIT is used to specify for how long the POLL should wait for
49// events, to accelerate the handling of paste or compound resize events. Having
50// a POLL_WAIT of zero means that every single event is treated as soon as it
51// arrives. This doesn't allow for the possibility of more than 1 event
52// happening at the same time.
53const POLL_WAIT: u64 = 10;
54// Since a paste event is multiple Event::Key events happening at the same time, we specify
55// how many events should be in the crossterm_events vector before it is considered
56// a paste. 10 events in 10 milliseconds is conservative enough (unlikely somebody
57// will type more than 10 characters in 10 milliseconds)
58const EVENTS_THRESHOLD: usize = 10;
59
60/// Determines if inputs should be used to extend the regular line buffer,
61/// traverse the history in the standard prompt or edit the search string in the
62/// reverse search
63#[derive(Debug, PartialEq, Eq)]
64enum InputMode {
65    /// Regular input by user typing or previous insertion.
66    /// Undo tracking is active
67    Regular,
68    /// Full reverse search mode with different prompt,
69    /// editing affects the search string,
70    /// suggestions are provided to be inserted in the line buffer
71    HistorySearch,
72    /// Hybrid mode indicating that history is walked through in the standard prompt
73    /// Either bash style up/down history or fish style prefix search,
74    /// Edits directly switch to [`InputMode::Regular`]
75    HistoryTraversal,
76}
77
78/// Line editor engine
79///
80/// ## Example usage
81/// ```no_run
82/// use reedline::{Reedline, Signal, DefaultPrompt};
83/// let mut line_editor = Reedline::create();
84/// let prompt = DefaultPrompt::default();
85///
86/// let out = line_editor.read_line(&prompt).unwrap();
87/// match out {
88///    Signal::Success(content) => {
89///        // process content
90///    }
91///    _ => {
92///        eprintln!("Entry aborted!");
93///
94///    }
95/// }
96/// ```
97pub struct Reedline {
98    editor: Editor,
99
100    // History
101    history: Box<dyn History>,
102    history_cursor: HistoryCursor,
103    history_session_id: Option<HistorySessionId>,
104    // none if history doesn't support this
105    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
112    validator: Option<Box<dyn Validator>>,
113
114    // Stdout
115    painter: Painter,
116
117    transient_prompt: Option<Box<dyn Prompt>>,
118
119    // Edit Mode: Vi, Emacs
120    edit_mode: Box<dyn EditMode>,
121
122    // Provides the tab completions
123    completer: Box<dyn Completer>,
124    quick_completions: bool,
125    partial_completions: bool,
126
127    // Highlight the edit buffer
128    highlighter: Box<dyn Highlighter>,
129
130    // Showcase hints based on various strategies (history, language-completion, spellcheck, etc)
131    hinter: Option<Box<dyn Hinter>>,
132    hide_hints: bool,
133
134    // Use ansi coloring or not
135    use_ansi_coloring: bool,
136
137    // Engine Menus
138    menus: Vec<ReedlineMenu>,
139
140    // Text editor used to open the line buffer for editing
141    buffer_editor: Option<BufferEditor>,
142
143    // Use different cursors depending on the current edit mode
144    cursor_shapes: Option<CursorConfig>,
145
146    // Manage bracketed paste mode
147    bracketed_paste: BracketedPasteGuard,
148
149    // Manage optional kitty protocol
150    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        // Ensures that the terminal is in a good state if we panic semigracefully
172        // Calling `disable_raw_mode()` twice is fine with Linux
173        let _ignore = terminal::disable_raw_mode();
174    }
175}
176
177impl Reedline {
178    const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX);
179
180    /// Create a new [`Reedline`] engine with a local [`History`] that is not synchronized to a file.
181    #[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    /// Get a new history session id based on the current time and the first commit datetime of reedline
227    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    /// Toggle whether reedline enables bracketed paste to reed copied content
237    ///
238    /// This currently alters the behavior for multiline pastes as pasting of regular text will
239    /// execute after every complete new line as determined by the [`Validator`]. With enabled
240    /// bracketed paste all lines will appear in the buffer and can then be submitted with a
241    /// separate enter.
242    ///
243    /// At this point most terminals should support it or ignore the setting of the necessary
244    /// flags. For full compatibility, keep it disabled.
245    pub fn use_bracketed_paste(mut self, enable: bool) -> Self {
246        self.bracketed_paste.set(enable);
247        self
248    }
249
250    /// Toggle whether reedline uses the kitty keyboard enhancement protocol
251    ///
252    /// This allows us to disambiguate more events than the traditional standard
253    /// Only available with a few terminal emulators.
254    /// You can check for that with [`crate::kitty_protocol_available`]
255    /// `Reedline` will perform this check internally
256    ///
257    /// Read more: <https://sw.kovidgoyal.net/kitty/keyboard-protocol/>
258    pub fn use_kitty_keyboard_enhancement(mut self, enable: bool) -> Self {
259        self.kitty_protocol.set(enable);
260        self
261    }
262
263    /// Return the previously generated history session id
264    pub fn get_history_session_id(&self) -> Option<HistorySessionId> {
265        self.history_session_id
266    }
267
268    /// Set a new history session id
269    /// This should be used in situations where the user initially did not have a history_session_id
270    /// and then later realized they want to have one without restarting the application.
271    pub fn set_history_session_id(&mut self, session: Option<HistorySessionId>) -> Result<()> {
272        self.history_session_id = session;
273        Ok(())
274    }
275
276    /// A builder to include a [`Hinter`] in your instance of the Reedline engine
277    /// # Example
278    /// ```rust
279    /// //Cargo.toml
280    /// //[dependencies]
281    /// //nu-ansi-term = "*"
282    /// use {
283    ///     nu_ansi_term::{Color, Style},
284    ///     reedline::{DefaultHinter, Reedline},
285    /// };
286    ///
287    /// let mut line_editor = Reedline::create().with_hinter(Box::new(
288    ///     DefaultHinter::default()
289    ///     .with_style(Style::new().italic().fg(Color::LightGray)),
290    /// ));
291    /// ```
292    #[must_use]
293    pub fn with_hinter(mut self, hinter: Box<dyn Hinter>) -> Self {
294        self.hinter = Some(hinter);
295        self
296    }
297
298    /// Remove current [`Hinter`]
299    #[must_use]
300    pub fn disable_hints(mut self) -> Self {
301        self.hinter = None;
302        self
303    }
304
305    /// A builder to configure the tab completion
306    /// # Example
307    /// ```rust
308    /// // Create a reedline object with tab completions support
309    ///
310    /// use reedline::{DefaultCompleter, Reedline};
311    ///
312    /// let commands = vec![
313    ///   "test".into(),
314    ///   "hello world".into(),
315    ///   "hello world reedline".into(),
316    ///   "this is the reedline crate".into(),
317    /// ];
318    /// let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
319    ///
320    /// let mut line_editor = Reedline::create().with_completer(completer);
321    /// ```
322    #[must_use]
323    pub fn with_completer(mut self, completer: Box<dyn Completer>) -> Self {
324        self.completer = completer;
325        self
326    }
327
328    /// Turn on quick completions. These completions will auto-select if the completer
329    /// ever narrows down to a single entry.
330    #[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    /// Turn on partial completions. These completions will fill the buffer with the
337    /// smallest common string from all the options
338    #[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    /// A builder which enables or disables the use of ansi coloring in the prompt
345    /// and in the command line syntax highlighting.
346    #[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    /// A builder that configures the highlighter for your instance of the Reedline engine
353    /// # Example
354    /// ```rust
355    /// // Create a reedline object with highlighter support
356    ///
357    /// use reedline::{ExampleHighlighter, Reedline};
358    ///
359    /// let commands = vec![
360    ///   "test".into(),
361    ///   "hello world".into(),
362    ///   "hello world reedline".into(),
363    ///   "this is the reedline crate".into(),
364    /// ];
365    /// let mut line_editor =
366    /// Reedline::create().with_highlighter(Box::new(ExampleHighlighter::new(commands)));
367    /// ```
368    #[must_use]
369    pub fn with_highlighter(mut self, highlighter: Box<dyn Highlighter>) -> Self {
370        self.highlighter = highlighter;
371        self
372    }
373
374    /// A builder which configures the history for your instance of the Reedline engine
375    /// # Example
376    /// ```rust,no_run
377    /// // Create a reedline object with history support, including history size limits
378    ///
379    /// use reedline::{FileBackedHistory, Reedline};
380    ///
381    /// let history = Box::new(
382    /// FileBackedHistory::with_file(5, "history.txt".into())
383    ///     .expect("Error configuring history with file"),
384    /// );
385    /// let mut line_editor = Reedline::create()
386    ///     .with_history(history);
387    /// ```
388    #[must_use]
389    pub fn with_history(mut self, history: Box<dyn History>) -> Self {
390        self.history = history;
391        self
392    }
393
394    /// A builder which configures history exclusion for your instance of the Reedline engine
395    /// # Example
396    /// ```rust,no_run
397    /// // Create a reedline instance with history that will *not* include commands starting with a space
398    ///
399    /// use reedline::{FileBackedHistory, Reedline};
400    ///
401    /// let history = Box::new(
402    /// FileBackedHistory::with_file(5, "history.txt".into())
403    ///     .expect("Error configuring history with file"),
404    /// );
405    /// let mut line_editor = Reedline::create()
406    ///     .with_history(history)
407    ///     .with_history_exclusion_prefix(Some(" ".into()));
408    /// ```
409    #[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    /// A builder that configures the validator for your instance of the Reedline engine
416    /// # Example
417    /// ```rust
418    /// // Create a reedline object with validator support
419    ///
420    /// use reedline::{DefaultValidator, Reedline};
421    ///
422    /// let mut line_editor =
423    /// Reedline::create().with_validator(Box::new(DefaultValidator));
424    /// ```
425    #[must_use]
426    pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
427        self.validator = Some(validator);
428        self
429    }
430
431    /// A builder that configures the alternate text editor used to edit the line buffer
432    ///
433    /// You are responsible for providing a file path that is unique to this reedline session
434    ///
435    /// # Example
436    /// ```rust,no_run
437    /// // Create a reedline object with vim as editor
438    ///
439    /// use reedline::Reedline;
440    /// use std::env::temp_dir;
441    /// use std::process::Command;
442    ///
443    /// let temp_file = std::env::temp_dir().join("my-random-unique.file");
444    /// let mut command = Command::new("vim");
445    /// // you can provide additional flags:
446    /// command.arg("-p"); // open in a vim tab (just for demonstration)
447    /// // you don't have to pass the filename to the command
448    /// let mut line_editor =
449    /// Reedline::create().with_buffer_editor(command, temp_file);
450    /// ```
451    #[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    /// Remove the current [`Validator`]
465    #[must_use]
466    pub fn disable_validator(mut self) -> Self {
467        self.validator = None;
468        self
469    }
470
471    /// Set a different prompt to be used after submitting each line
472    #[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    /// A builder which configures the edit mode for your instance of the Reedline engine
479    #[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    /// A builder that appends a menu to the engine
486    #[must_use]
487    pub fn with_menu(mut self, menu: ReedlineMenu) -> Self {
488        self.menus.push(menu);
489        self
490    }
491
492    /// A builder that clears the list of menus added to the engine
493    #[must_use]
494    pub fn clear_menus(mut self) -> Self {
495        self.menus = Vec::new();
496        self
497    }
498
499    /// A builder that adds the history item id
500    #[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    /// A builder that enables reedline changing the cursor shape based on the current edit mode.
507    /// The current implementation sets the cursor shape when drawing the prompt.
508    /// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences.
509    pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self {
510        self.cursor_shapes = Some(cursor_shapes);
511        self
512    }
513
514    /// Returns the corresponding expected prompt style for the given edit mode
515    pub fn prompt_edit_mode(&self) -> PromptEditMode {
516        self.edit_mode.edit_mode()
517    }
518
519    /// Output the complete [`History`] chronologically with numbering to the terminal
520    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    /// Output the complete [`History`] for this session, chronologically with numbering to the terminal
533    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    /// Print the history session id
549    pub fn print_history_session_id(&mut self) -> Result<()> {
550        println!("History Session Id: {:?}", self.get_history_session_id());
551        Ok(())
552    }
553
554    /// Toggle between having a history that uses the history session id and one that does not
555    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    /// Read-only view of the history
567    pub fn history(&self) -> &dyn History {
568        &*self.history
569    }
570
571    /// Mutable view of the history
572    pub fn history_mut(&mut self) -> &mut dyn History {
573        &mut *self.history
574    }
575
576    /// Update the underlying [`History`] to/from disk
577    pub fn sync_history(&mut self) -> std::io::Result<()> {
578        // TODO: check for interactions in the non-submitting events
579        self.history.sync()
580    }
581
582    /// Check if any commands have been run.
583    ///
584    /// When no commands have been run, calling [`Self::update_last_command_context`]
585    /// does not make sense and is guaranteed to fail with a "No command run" error.
586    pub fn has_last_command_context(&self) -> bool {
587        self.history_last_run_id.is_some()
588    }
589
590    /// update the last history item with more information
591    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    /// Wait for input and provide the user with a specified [`Prompt`].
608    ///
609    /// Returns a [`std::io::Result`] in which the `Err` type is [`std::io::Result`]
610    /// and the `Ok` variant wraps a [`Signal`] which handles user inputs.
611    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    /// Returns the current insertion point of the input buffer.
625    pub fn current_insertion_point(&self) -> usize {
626        self.editor.insertion_point()
627    }
628
629    /// Returns the current contents of the input buffer.
630    pub fn current_buffer_contents(&self) -> &str {
631        self.editor.get_buffer()
632    }
633
634    /// Writes `msg` to the terminal with a following carriage return and newline
635    fn print_line(&mut self, msg: &str) -> Result<()> {
636        self.painter.paint_line(msg)
637    }
638
639    /// Clear the screen by printing enough whitespace to start the prompt or
640    /// other output back at the first line of the terminal.
641    pub fn clear_screen(&mut self) -> Result<()> {
642        self.painter.clear_screen()?;
643
644        Ok(())
645    }
646
647    /// Clear the screen and the scollback buffer of the terminal
648    pub fn clear_scrollback(&mut self) -> Result<()> {
649        self.painter.clear_scrollback()?;
650
651        Ok(())
652    }
653
654    /// Helper implementing the logic for [`Reedline::read_line()`] to be wrapped
655    /// in a `raw_mode` context.
656    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                // get messages from printer as crlf separated "lines"
671                let messages = Self::external_messages(external_printer)?;
672                if !messages.is_empty() {
673                    // print the message(s)
674                    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                            // Break early to check if the input is complete and
698                            // can be send to the hosting application. If
699                            // multiple complete entries are submitted, events
700                            // are still in the crossterm queue for us to
701                            // process.
702                            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                // There could be multiple events queued up!
715                // pasting text, resizes, blocking this thread (e.g. during debugging)
716                // We should be able to handle all of them as quickly as possible without causing unnecessary output steps.
717                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            // Accelerate pasted text by fusing `EditCommand`s
727            //
728            // (Text should only be `EditCommand::InsertChar`s)
729            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                        // Move the cursor below the input area, for external commands or new read_line call
756                        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                        // Nothing changed, no need to repaint
766                    }
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                            // Try again with the next event handler
787                        }
788                        success => {
789                            return Ok(success);
790                        }
791                    }
792                }
793                // Exhausting the event handlers is still considered handled
794                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                // TODO: Decide if we need to do something special to have a nicer painter state on the next go
832                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                // A handled Event causes a repaint
845                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                // Hacky way to ensure that we don't fall of into failed search going forward
858                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            // TODO: Check if events should be handled
870            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                // TODO: Decide if we need to do something special to have a nicer painter state on the next go
1098                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                // A handled Event causes a repaint
1150                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                            // NO OP
1189                        }
1190                        EventStatus::Exits(signal) => {
1191                            // TODO: Check if we want to allow execution to
1192                            // proceed if there are more events after the
1193                            // terminating
1194                            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                            // Try again with the next event handler
1206                        }
1207                        success => {
1208                            return Ok(success);
1209                        }
1210                    }
1211                }
1212                // Exhausting the event handlers is still considered handled
1213                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    /// Enable the search and navigation through the history from the line buffer prompt
1289    ///
1290    /// Enables either prefix search with output in the line buffer or simple traversal
1291    fn get_history_navigation_based_on_line_buffer(&self) -> HistoryNavigationQuery {
1292        if self.editor.is_empty() || !self.editor.is_cursor_at_buffer_end() {
1293            // Perform bash-style basic up/down entry walking
1294            HistoryNavigationQuery::Normal(
1295                // Hack: Tight coupling point to be able to restore previously typed input
1296                self.editor.line_buffer().clone(),
1297            )
1298        } else {
1299            // Prefix search like found in fish, zsh, etc.
1300            // Search string is set once from the current buffer
1301            // Current setup (code in other methods)
1302            // Continuing with typing will leave the search
1303            // but next invocation of this method will start the next search
1304            let buffer = self.editor.get_buffer().to_string();
1305            HistoryNavigationQuery::PrefixSearch(buffer)
1306        }
1307    }
1308
1309    /// Switch into reverse history search mode
1310    ///
1311    /// This mode uses a separate prompt and handles keybindings slightly differently!
1312    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    /// Dispatches the applicable [`EditCommand`] actions for editing the history search string.
1321    ///
1322    /// Only modifies internal state, does not perform regular output!
1323    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    /// Set the buffer contents for history traversal/search in the standard prompt
1367    ///
1368    /// When using the up/down traversal or fish/zsh style prefix search update the main line buffer accordingly.
1369    /// Not used for the separate modal reverse search!
1370    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                    // Hack
1386                    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    /// Executes [`EditCommand`] actions by modifying the internal state appropriately. Does not output itself.
1404    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        // Run the commands over the edit buffer
1419        for command in commands {
1420            self.editor.run_edit_command(command);
1421        }
1422    }
1423
1424    fn up_command(&mut self) {
1425        // If we're at the top, then:
1426        if self.editor.is_cursor_at_first_line() {
1427            // If we're at the top, move to previous history
1428            self.previous_history();
1429        } else {
1430            self.editor.move_line_up();
1431        }
1432    }
1433
1434    fn down_command(&mut self) {
1435        // If we're at the top, then:
1436        if self.editor.is_cursor_at_last_line() {
1437            // If we're at the top, move to previous history
1438            self.next_history();
1439        } else {
1440            self.editor.move_line_down();
1441        }
1442    }
1443
1444    /// Checks if hints should be displayed and are able to be completed
1445    fn hints_active(&self) -> bool {
1446        !self.hide_hints && matches!(self.input_mode, InputMode::Regular)
1447    }
1448
1449    /// Repaint of either the buffer or the parts for reverse history search
1450    fn repaint(&mut self, prompt: &dyn Prompt) -> io::Result<()> {
1451        // Repainting
1452        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    /// Parses the ! command to replace entries from the history
1461    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), // fetch the latest one entries
1484                        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), // fetch the latest n entries
1504                        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), // fetch the oldest n entries
1524                        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    /// Repaint logic for the history reverse search
1585    ///
1586    /// Overwrites the prompt indicator and highlights the search string
1587    /// separately from the result buffer.
1588    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            // Highlight matches
1604            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    /// Triggers a full repaint including the prompt parts
1635    ///
1636    /// Includes the highlighting and hinting calls.
1637    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        // Needs to add return carriage to newlines because when not in raw mode
1664        // some OS don't fully return the carriage
1665
1666        let lines = PromptLines::new(
1667            prompt,
1668            self.prompt_edit_mode(),
1669            None,
1670            &before_cursor,
1671            &after_cursor,
1672            &hint,
1673        );
1674
1675        // Updating the working details of the active menu
1676        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    /// Adds an external printer
1700    ///
1701    /// ## Required feature:
1702    /// `external_printer`
1703    #[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        // Additional repaint to show the content without hints etc.
1737        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}