reedline/
engine.rs

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
49// The POLL_WAIT is used to specify for how long the POLL should wait for
50// events, to accelerate the handling of paste or compound resize events. Having
51// a POLL_WAIT of zero means that every single event is treated as soon as it
52// arrives. This doesn't allow for the possibility of more than 1 event
53// happening at the same time.
54const POLL_WAIT: Duration = Duration::from_millis(100);
55// Since a paste event is multiple `Event::Key` events happening at the same
56// time, we specify how many events should be in the `crossterm_events` vector
57// before it is considered a paste. 10 events is conservative enough.
58const EVENTS_THRESHOLD: usize = 10;
59
60/// Maximum time Reedline will block on input before yielding control to
61/// external printers.
62#[cfg(feature = "external_printer")]
63const EXTERNAL_PRINTER_WAIT: Duration = Duration::from_millis(100);
64
65/// Determines if inputs should be used to extend the regular line buffer,
66/// traverse the history in the standard prompt or edit the search string in the
67/// reverse search
68#[derive(Debug, PartialEq, Eq)]
69enum InputMode {
70    /// Regular input by user typing or previous insertion.
71    /// Undo tracking is active
72    Regular,
73    /// Full reverse search mode with different prompt,
74    /// editing affects the search string,
75    /// suggestions are provided to be inserted in the line buffer
76    HistorySearch,
77    /// Hybrid mode indicating that history is walked through in the standard prompt
78    /// Either bash style up/down history or fish style prefix search,
79    /// Edits directly switch to [`InputMode::Regular`]
80    HistoryTraversal,
81}
82
83/// Line editor engine
84///
85/// ## Example usage
86/// ```no_run
87/// use reedline::{Reedline, Signal, DefaultPrompt};
88/// let mut line_editor = Reedline::create();
89/// let prompt = DefaultPrompt::default();
90///
91/// let out = line_editor.read_line(&prompt).unwrap();
92/// match out {
93///    Signal::Success(content) => {
94///        // process content
95///    }
96///    _ => {
97///        eprintln!("Entry aborted!");
98///
99///    }
100/// }
101/// ```
102pub struct Reedline {
103    editor: Editor,
104
105    // History
106    history: Box<dyn History>,
107    history_cursor: HistoryCursor,
108    history_session_id: Option<HistorySessionId>,
109    // none if history doesn't support this
110    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    // State of the painter after a `ReedlineEvent::ExecuteHostCommand` was requested, used after
117    // execution to decide if we can re-use the previous prompt or paint a new one.
118    suspended_state: Option<PainterSuspendedState>,
119
120    // Validator
121    validator: Option<Box<dyn Validator>>,
122
123    // Stdout
124    painter: Painter,
125
126    transient_prompt: Option<Box<dyn Prompt>>,
127
128    // Edit Mode: Vi, Emacs
129    edit_mode: Box<dyn EditMode>,
130
131    // Provides the tab completions
132    completer: Box<dyn Completer>,
133    quick_completions: bool,
134    partial_completions: bool,
135
136    // Highlight the edit buffer
137    highlighter: Box<dyn Highlighter>,
138
139    // Style used for visual selection
140    visual_selection_style: Style,
141
142    // Showcase hints based on various strategies (history, language-completion, spellcheck, etc)
143    hinter: Option<Box<dyn Hinter>>,
144    hide_hints: bool,
145
146    // Use ansi coloring or not
147    use_ansi_coloring: bool,
148
149    // Current working directory as defined by the application. If set, it will
150    // override the actual working directory of the process.
151    cwd: Option<String>,
152
153    // Engine Menus
154    menus: Vec<ReedlineMenu>,
155
156    // Text editor used to open the line buffer for editing
157    buffer_editor: Option<BufferEditor>,
158
159    // Use different cursors depending on the current edit mode
160    cursor_shapes: Option<CursorConfig>,
161
162    // Manage bracketed paste mode
163    bracketed_paste: BracketedPasteGuard,
164
165    // Manage optional kitty protocol
166    kitty_protocol: KittyProtocolGuard,
167
168    // Whether lines should be accepted immediately
169    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        // Ensures that the terminal is in a good state if we panic semigracefully
191        // Calling `disable_raw_mode()` twice is fine with Linux
192        let _ignore = terminal::disable_raw_mode();
193    }
194}
195
196impl Reedline {
197    const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX);
198
199    /// Create a new [`Reedline`] engine with a local [`History`] that is not synchronized to a file.
200    #[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    /// Get a new history session id based on the current time and the first commit datetime of reedline
251    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    /// Toggle whether reedline enables bracketed paste to reed copied content
261    ///
262    /// This currently alters the behavior for multiline pastes as pasting of regular text will
263    /// execute after every complete new line as determined by the [`Validator`]. With enabled
264    /// bracketed paste all lines will appear in the buffer and can then be submitted with a
265    /// separate enter.
266    ///
267    /// At this point most terminals should support it or ignore the setting of the necessary
268    /// flags. For full compatibility, keep it disabled.
269    pub fn use_bracketed_paste(mut self, enable: bool) -> Self {
270        self.bracketed_paste.set(enable);
271        self
272    }
273
274    /// Toggle whether reedline uses the kitty keyboard enhancement protocol
275    ///
276    /// This allows us to disambiguate more events than the traditional standard
277    /// Only available with a few terminal emulators.
278    /// You can check for that with [`crate::kitty_protocol_available`]
279    /// `Reedline` will perform this check internally
280    ///
281    /// Read more: <https://sw.kovidgoyal.net/kitty/keyboard-protocol/>
282    pub fn use_kitty_keyboard_enhancement(mut self, enable: bool) -> Self {
283        self.kitty_protocol.set(enable);
284        self
285    }
286
287    /// Return the previously generated history session id
288    pub fn get_history_session_id(&self) -> Option<HistorySessionId> {
289        self.history_session_id
290    }
291
292    /// Set a new history session id
293    /// This should be used in situations where the user initially did not have a history_session_id
294    /// and then later realized they want to have one without restarting the application.
295    pub fn set_history_session_id(&mut self, session: Option<HistorySessionId>) -> Result<()> {
296        self.history_session_id = session;
297        Ok(())
298    }
299
300    /// A builder to include a [`Hinter`] in your instance of the Reedline engine
301    /// # Example
302    /// ```rust
303    /// //Cargo.toml
304    /// //[dependencies]
305    /// //nu-ansi-term = "*"
306    /// use {
307    ///     nu_ansi_term::{Color, Style},
308    ///     reedline::{DefaultHinter, Reedline},
309    /// };
310    ///
311    /// let mut line_editor = Reedline::create().with_hinter(Box::new(
312    ///     DefaultHinter::default()
313    ///     .with_style(Style::new().italic().fg(Color::LightGray)),
314    /// ));
315    /// ```
316    #[must_use]
317    pub fn with_hinter(mut self, hinter: Box<dyn Hinter>) -> Self {
318        self.hinter = Some(hinter);
319        self
320    }
321
322    /// Remove current [`Hinter`]
323    #[must_use]
324    pub fn disable_hints(mut self) -> Self {
325        self.hinter = None;
326        self
327    }
328
329    /// A builder to configure the tab completion
330    /// # Example
331    /// ```rust
332    /// // Create a reedline object with tab completions support
333    ///
334    /// use reedline::{DefaultCompleter, Reedline};
335    ///
336    /// let commands = vec![
337    ///   "test".into(),
338    ///   "hello world".into(),
339    ///   "hello world reedline".into(),
340    ///   "this is the reedline crate".into(),
341    /// ];
342    /// let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
343    ///
344    /// let mut line_editor = Reedline::create().with_completer(completer);
345    /// ```
346    #[must_use]
347    pub fn with_completer(mut self, completer: Box<dyn Completer>) -> Self {
348        self.completer = completer;
349        self
350    }
351
352    /// Turn on quick completions. These completions will auto-select if the completer
353    /// ever narrows down to a single entry.
354    #[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    /// Turn on partial completions. These completions will fill the buffer with the
361    /// smallest common string from all the options
362    #[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    /// A builder which enables or disables the use of ansi coloring in the prompt
369    /// and in the command line syntax highlighting.
370    #[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    /// Update current working directory.
377    #[must_use]
378    pub fn with_cwd(mut self, cwd: Option<String>) -> Self {
379        self.cwd = cwd;
380        self
381    }
382
383    /// A builder that configures the highlighter for your instance of the Reedline engine
384    /// # Example
385    /// ```rust
386    /// // Create a reedline object with highlighter support
387    ///
388    /// use reedline::{ExampleHighlighter, Reedline};
389    ///
390    /// let commands = vec![
391    ///   "test".into(),
392    ///   "hello world".into(),
393    ///   "hello world reedline".into(),
394    ///   "this is the reedline crate".into(),
395    /// ];
396    /// let mut line_editor =
397    /// Reedline::create().with_highlighter(Box::new(ExampleHighlighter::new(commands)));
398    /// ```
399    #[must_use]
400    pub fn with_highlighter(mut self, highlighter: Box<dyn Highlighter>) -> Self {
401        self.highlighter = highlighter;
402        self
403    }
404
405    /// A builder that configures the style used for visual selection
406    #[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    /// A builder which configures the history for your instance of the Reedline engine
413    /// # Example
414    /// ```rust,no_run
415    /// // Create a reedline object with history support, including history size limits
416    ///
417    /// use reedline::{FileBackedHistory, Reedline};
418    ///
419    /// let history = Box::new(
420    /// FileBackedHistory::with_file(5, "history.txt".into())
421    ///     .expect("Error configuring history with file"),
422    /// );
423    /// let mut line_editor = Reedline::create()
424    ///     .with_history(history);
425    /// ```
426    #[must_use]
427    pub fn with_history(mut self, history: Box<dyn History>) -> Self {
428        self.history = history;
429        self
430    }
431
432    /// A builder which configures history exclusion for your instance of the Reedline engine
433    /// # Example
434    /// ```rust,no_run
435    /// // Create a reedline instance with history that will *not* include commands starting with a space
436    ///
437    /// use reedline::{FileBackedHistory, Reedline};
438    ///
439    /// let history = Box::new(
440    /// FileBackedHistory::with_file(5, "history.txt".into())
441    ///     .expect("Error configuring history with file"),
442    /// );
443    /// let mut line_editor = Reedline::create()
444    ///     .with_history(history)
445    ///     .with_history_exclusion_prefix(Some(" ".into()));
446    /// ```
447    #[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    /// A builder that configures the validator for your instance of the Reedline engine
454    /// # Example
455    /// ```rust
456    /// // Create a reedline object with validator support
457    ///
458    /// use reedline::{DefaultValidator, Reedline};
459    ///
460    /// let mut line_editor =
461    /// Reedline::create().with_validator(Box::new(DefaultValidator));
462    /// ```
463    #[must_use]
464    pub fn with_validator(mut self, validator: Box<dyn Validator>) -> Self {
465        self.validator = Some(validator);
466        self
467    }
468
469    /// A builder that configures the alternate text editor used to edit the line buffer
470    ///
471    /// You are responsible for providing a file path that is unique to this reedline session
472    ///
473    /// # Example
474    /// ```rust,no_run
475    /// // Create a reedline object with vim as editor
476    ///
477    /// use reedline::Reedline;
478    /// use std::env::temp_dir;
479    /// use std::process::Command;
480    ///
481    /// let temp_file = std::env::temp_dir().join("my-random-unique.file");
482    /// let mut command = Command::new("vim");
483    /// // you can provide additional flags:
484    /// command.arg("-p"); // open in a vim tab (just for demonstration)
485    /// // you don't have to pass the filename to the command
486    /// let mut line_editor =
487    /// Reedline::create().with_buffer_editor(command, temp_file);
488    /// ```
489    #[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    /// Remove the current [`Validator`]
503    #[must_use]
504    pub fn disable_validator(mut self) -> Self {
505        self.validator = None;
506        self
507    }
508
509    /// Set a different prompt to be used after submitting each line
510    #[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    /// A builder which configures the edit mode for your instance of the Reedline engine
517    #[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    /// A builder that appends a menu to the engine
524    #[must_use]
525    pub fn with_menu(mut self, menu: ReedlineMenu) -> Self {
526        self.menus.push(menu);
527        self
528    }
529
530    /// A builder that clears the list of menus added to the engine
531    #[must_use]
532    pub fn clear_menus(mut self) -> Self {
533        self.menus = Vec::new();
534        self
535    }
536
537    /// A builder that adds the history item id
538    #[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    /// A builder that enables reedline changing the cursor shape based on the current edit mode.
545    /// The current implementation sets the cursor shape when drawing the prompt.
546    /// Do not use this if the cursor shape is set elsewhere, e.g. in the terminal settings or by ansi escape sequences.
547    pub fn with_cursor_config(mut self, cursor_shapes: CursorConfig) -> Self {
548        self.cursor_shapes = Some(cursor_shapes);
549        self
550    }
551
552    /// A builder that configures whether reedline should immediately accept the input.
553    pub fn with_immediately_accept(mut self, immediately_accept: bool) -> Self {
554        self.immediately_accept = immediately_accept;
555        self
556    }
557
558    /// Returns the corresponding expected prompt style for the given edit mode
559    pub fn prompt_edit_mode(&self) -> PromptEditMode {
560        self.edit_mode.edit_mode()
561    }
562
563    /// Output the complete [`History`] chronologically with numbering to the terminal
564    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    /// Output the complete [`History`] for this session, chronologically with numbering to the terminal
577    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    /// Print the history session id
593    pub fn print_history_session_id(&mut self) -> Result<()> {
594        println!("History Session Id: {:?}", self.get_history_session_id());
595        Ok(())
596    }
597
598    /// Toggle between having a history that uses the history session id and one that does not
599    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    /// Read-only view of the history
611    pub fn history(&self) -> &dyn History {
612        &*self.history
613    }
614
615    /// Mutable view of the history
616    pub fn history_mut(&mut self) -> &mut dyn History {
617        &mut *self.history
618    }
619
620    /// Update the underlying [`History`] to/from disk
621    pub fn sync_history(&mut self) -> std::io::Result<()> {
622        // TODO: check for interactions in the non-submitting events
623        self.history.sync()
624    }
625
626    /// Check if any commands have been run.
627    ///
628    /// When no commands have been run, calling [`Self::update_last_command_context`]
629    /// does not make sense and is guaranteed to fail with a "No command run" error.
630    pub fn has_last_command_context(&self) -> bool {
631        self.history_last_run_id.is_some()
632    }
633
634    /// update the last history item with more information
635    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    /// Wait for input and provide the user with a specified [`Prompt`].
652    ///
653    /// Returns a [`std::io::Result`] in which the `Err` type is [`std::io::Result`]
654    /// and the `Ok` variant wraps a [`Signal`] which handles user inputs.
655    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    /// Returns the current insertion point of the input buffer.
669    pub fn current_insertion_point(&self) -> usize {
670        self.editor.insertion_point()
671    }
672
673    /// Returns the current contents of the input buffer.
674    pub fn current_buffer_contents(&self) -> &str {
675        self.editor.get_buffer()
676    }
677
678    /// Writes `msg` to the terminal with a following carriage return and newline
679    fn print_line(&mut self, msg: &str) -> Result<()> {
680        self.painter.paint_line(msg)
681    }
682
683    /// Clear the screen by printing enough whitespace to start the prompt or
684    /// other output back at the first line of the terminal.
685    pub fn clear_screen(&mut self) -> Result<()> {
686        self.painter.clear_screen()?;
687
688        Ok(())
689    }
690
691    /// Clear the screen and the scrollback buffer of the terminal
692    pub fn clear_scrollback(&mut self) -> Result<()> {
693        self.painter.clear_scrollback()?;
694
695        Ok(())
696    }
697
698    /// Helper implementing the logic for [`Reedline::read_line()`] to be wrapped
699    /// in a `raw_mode` context.
700    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            // Last editor was suspended to run a ExecuteHostCommand event,
705            // we are resuming operation now.
706            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                // get messages from printer as crlf separated "lines"
716                let messages = Self::external_messages(external_printer)?;
717                if !messages.is_empty() {
718                    // print the message(s)
719                    self.painter.print_external_message(
720                        messages,
721                        self.editor.line_buffer(),
722                        prompt,
723                    )?;
724                    self.repaint(prompt)?;
725                }
726            }
727
728            // Helper function that returns true if the input is complete and
729            // can be sent to the hosting application.
730            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                // If the `external_printer` feature is enabled, we need to
749                // periodically yield so that external printers get a chance to
750                // print. Otherwise, we can just block until we receive an event.
751                #[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                // Receive all events in the queue without blocking. Will stop when
759                // a line of input is completed.
760                while !completed(&events) && event::poll(Duration::from_millis(0))? {
761                    events.push(crossterm::event::read()?);
762                }
763
764                // If we believe there's text pasting or resizing going on, batch
765                // more events at the cost of a slight delay.
766                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            // Convert `Event` into `ReedlineEvent`. Also, fuse consecutive
776            // `ReedlineEvent::EditCommand` into one. Also, if there're multiple
777            // `ReedlineEvent::Resize`, only keep the last one.
778            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            // Handle reedline events.
807            let mut need_repaint = false;
808            for event in reedline_events {
809                match self.handle_event(prompt, event)? {
810                    EventStatus::Exits(signal) => {
811                        // Check if we are merely suspended (to process an ExecuteHostCommand event)
812                        // or if we're about to quit the editor.
813                        if self.suspended_state.is_none() {
814                            // We are about to quit the editor, move the cursor below the input
815                            // area, for external commands or new read_line call
816                            self.painter.move_cursor_to_end()?;
817                        }
818                        return Ok(signal);
819                    }
820                    EventStatus::Handled => {
821                        need_repaint = true;
822                    }
823                    EventStatus::Inapplicable => {
824                        // Nothing changed, no need to repaint
825                    }
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                            // Try again with the next event handler
849                        }
850                        success => {
851                            return Ok(success);
852                        }
853                    }
854                }
855                // Exhausting the event handlers is still considered handled
856                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                // A handled Event causes a repaint
907                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                // Hacky way to ensure that we don't fall of into failed search going forward
920                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            // TODO: Check if events should be handled
932            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                // A handled Event causes a repaint
1227                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                            // NO OP
1266                        }
1267                        EventStatus::Exits(signal) => {
1268                            // TODO: Check if we want to allow execution to
1269                            // proceed if there are more events after the
1270                            // terminating
1271                            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                            // Try again with the next event handler
1283                        }
1284                        success => {
1285                            return Ok(success);
1286                        }
1287                    }
1288                }
1289                // Exhausting the event handlers is still considered handled
1290                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    /// Enable the search and navigation through the history from the line buffer prompt
1368    ///
1369    /// Enables either prefix search with output in the line buffer or simple traversal
1370    fn get_history_navigation_based_on_line_buffer(&self) -> HistoryNavigationQuery {
1371        if self.editor.is_empty() || !self.editor.is_cursor_at_buffer_end() {
1372            // Perform bash-style basic up/down entry walking
1373            HistoryNavigationQuery::Normal(
1374                // Hack: Tight coupling point to be able to restore previously typed input
1375                self.editor.line_buffer().clone(),
1376            )
1377        } else {
1378            // Prefix search like found in fish, zsh, etc.
1379            // Search string is set once from the current buffer
1380            // Current setup (code in other methods)
1381            // Continuing with typing will leave the search
1382            // but next invocation of this method will start the next search
1383            let buffer = self.editor.get_buffer().to_string();
1384            HistoryNavigationQuery::PrefixSearch(buffer)
1385        }
1386    }
1387
1388    /// Switch into reverse history search mode
1389    ///
1390    /// This mode uses a separate prompt and handles keybindings slightly differently!
1391    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    /// Dispatches the applicable [`EditCommand`] actions for editing the history search string.
1400    ///
1401    /// Only modifies internal state, does not perform regular output!
1402    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    /// Set the buffer contents for history traversal/search in the standard prompt
1446    ///
1447    /// When using the up/down traversal or fish/zsh style prefix search update the main line buffer accordingly.
1448    /// Not used for the separate modal reverse search!
1449    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                    // Hack
1465                    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    /// Executes [`EditCommand`] actions by modifying the internal state appropriately. Does not output itself.
1483    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                    // NOTE: `set_buffer` resets the insertion point,
1491                    // which we should avoid during history navigation through the same buffer
1492                    // https://github.com/nushell/reedline/pull/899
1493                    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        // Run the commands over the edit buffer
1503        for command in commands {
1504            self.editor.run_edit_command(command);
1505        }
1506    }
1507
1508    fn up_command(&mut self) {
1509        // If we're at the top, then:
1510        if self.editor.is_cursor_at_first_line() {
1511            // If we're at the top, move to previous history
1512            self.previous_history();
1513        } else {
1514            self.editor.move_line_up();
1515        }
1516    }
1517
1518    fn down_command(&mut self) {
1519        // If we're at the top, then:
1520        if self.editor.is_cursor_at_last_line() {
1521            // If we're at the top, move to previous history
1522            self.next_history();
1523        } else {
1524            self.editor.move_line_down();
1525        }
1526    }
1527
1528    /// Checks if hints should be displayed and are able to be completed
1529    fn hints_active(&self) -> bool {
1530        !self.hide_hints && matches!(self.input_mode, InputMode::Regular)
1531    }
1532
1533    /// Repaint of either the buffer or the parts for reverse history search
1534    fn repaint(&mut self, prompt: &dyn Prompt) -> io::Result<()> {
1535        // Repainting
1536        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    /// Parses the ! command to replace entries from the history
1545    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), // fetch the latest one entries
1570                        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), // fetch the latest n entries
1590                        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 we don't find any history searching by session id, then let's
1624                    // search everything, otherwise use the result from the session search
1625                    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), // fetch the oldest n entries
1653                        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                    //BUGBUG: This returns the wrong results with paths with spaces in them
1672                    .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    /// Repaint logic for the history reverse search
1718    ///
1719    /// Overwrites the prompt indicator and highlights the search string
1720    /// separately from the result buffer.
1721    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            // Highlight matches
1737            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    /// Triggers a full repaint including the prompt parts
1768    ///
1769    /// Includes the highlighting and hinting calls.
1770    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        // Needs to add return carriage to newlines because when not in raw mode
1807        // some OS don't fully return the carriage
1808
1809        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        // Updating the working details of the active menu
1819        for menu in self.menus.iter_mut() {
1820            if menu.is_active() {
1821                lines.prompt_indicator = menu.indicator().to_owned().into();
1822                // If the menu requires the cursor position, update it (ide menu)
1823                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    /// Adds an external printer
1848    ///
1849    /// ## Required feature:
1850    /// `external_printer`
1851    #[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        // Additional repaint to show the content without hints etc.
1885        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        // Test for https://github.com/nushell/reedline/pull/899
1924        // Ensure that after navigating to a multiline history entry and then
1925        // running edit commands, the cursor doesn't jump unexpectedly.
1926        // The fix prevents set_buffer() from being called unnecessarily,
1927        // which would reset the insertion point.
1928
1929        let mut reedline = Reedline::create();
1930
1931        // Add a multiline entry to history
1932        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        // Navigate to previous history
1940        reedline.previous_history();
1941
1942        // Get the initial insertion point after history navigation
1943        let initial_insertion_point = reedline.current_insertion_point();
1944
1945        // The buffer should contain our multiline command
1946        assert_eq!(reedline.current_buffer_contents(), multiline_command);
1947
1948        // After the fix, previous_history() positions cursor at end of first line
1949        // (after move_to_start + move_to_line_end)
1950        let first_line_end = multiline_command.find('\n').unwrap();
1951        assert_eq!(initial_insertion_point, first_line_end);
1952
1953        // Now simulate pressing the right arrow key, which should move cursor right
1954        // Without the fix, set_buffer() would be called and reset the insertion point,
1955        // causing the cursor to jump unexpectedly. With the fix, it stays where it is
1956        // and moves correctly.
1957        reedline.run_edit_commands(&[EditCommand::MoveRight { select: false }]);
1958
1959        let after_move_insertion_point = reedline.current_insertion_point();
1960
1961        // The cursor should have moved right by 1 from where it was
1962        assert_eq!(after_move_insertion_point, initial_insertion_point + 1);
1963
1964        // The buffer should still be unchanged
1965        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}