termwiz/lineedit/
mod.rs

1//! The `LineEditor` struct provides line editing facilities similar
2//! to those in the unix shell.
3//!
4//! ```no_run
5//! use termwiz::lineedit::{line_editor_terminal, NopLineEditorHost, LineEditor};
6//!
7//! fn main() -> termwiz::Result<()> {
8//!     let mut terminal = line_editor_terminal()?;
9//!     let mut editor = LineEditor::new(&mut terminal);
10//!     let mut host = NopLineEditorHost::default();
11//!
12//!     let line = editor.read_line(&mut host)?;
13//!     println!("read line: {:?}", line);
14//!
15//!     Ok(())
16//! }
17//! ```
18//!
19//! ## Key Bindings
20//!
21//! The following key bindings are supported:
22//!
23//! Keystroke     | Action
24//! ---------     | ------
25//! Ctrl-A, Home  | Move cursor to the beginning of the line
26//! Ctrl-E, End   | Move cursor to the end of the line
27//! Ctrl-B, Left  | Move cursor one grapheme to the left
28//! Ctrl-C        | Cancel the line editor
29//! Ctrl-D        | Cancel the line editor with an End-of-File result
30//! Ctrl-F, Right | Move cursor one grapheme to the right
31//! Ctrl-H, Backspace | Delete the grapheme to the left of the cursor
32//! Delete        | Delete the grapheme to the right of the cursor
33//! Ctrl-J, Ctrl-M, Enter | Finish line editing and accept the current line
34//! Ctrl-K        | Delete from cursor to end of line
35//! Ctrl-L        | Move the cursor to the top left, clear screen and repaint
36//! Ctrl-R        | Incremental history search mode
37//! Ctrl-W        | Delete word leading up to cursor
38//! Alt-b, Alt-Left | Move the cursor backwards one word
39//! Alt-f, Alt-Right | Move the cursor forwards one word
40use crate::caps::{Capabilities, ProbeHints};
41use crate::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
42use crate::surface::change::ChangeSequence;
43use crate::surface::{Change, Position};
44use crate::terminal::{new_terminal, Terminal};
45use crate::{bail, ensure, Result};
46
47mod actions;
48mod buffer;
49mod history;
50mod host;
51pub use actions::{Action, Movement, RepeatCount};
52pub use buffer::LineEditBuffer;
53pub use history::*;
54pub use host::*;
55
56/// The `LineEditor` struct provides line editing facilities similar
57/// to those in the unix shell.
58/// ```no_run
59/// use termwiz::lineedit::{line_editor_terminal, NopLineEditorHost, LineEditor};
60///
61/// fn main() -> termwiz::Result<()> {
62///     let mut terminal = line_editor_terminal()?;
63///     let mut editor = LineEditor::new(&mut terminal);
64///     let mut host = NopLineEditorHost::default();
65///
66///     let line = editor.read_line(&mut host)?;
67///     println!("read line: {:?}", line);
68///
69///     Ok(())
70/// }
71/// ```
72pub struct LineEditor<'term> {
73    terminal: &'term mut dyn Terminal,
74    prompt: String,
75    line: LineEditBuffer,
76
77    history_pos: Option<usize>,
78    bottom_line: Option<String>,
79
80    completion: Option<CompletionState>,
81
82    move_to_editor_start: Option<Change>,
83    move_to_editor_end: Option<Change>,
84
85    state: EditorState,
86}
87
88#[derive(Clone, Eq, PartialEq, Debug)]
89enum EditorState {
90    Inactive,
91    Editing,
92    Cancelled,
93    Accepted,
94    Searching {
95        style: SearchStyle,
96        direction: SearchDirection,
97        matching_line: String,
98        cursor: usize,
99    },
100}
101
102struct CompletionState {
103    candidates: Vec<CompletionCandidate>,
104    index: usize,
105    original_line: String,
106    original_cursor: usize,
107}
108
109impl CompletionState {
110    fn next(&mut self) {
111        self.index += 1;
112        if self.index >= self.candidates.len() {
113            self.index = 0;
114        }
115    }
116
117    fn current(&self) -> (usize, String) {
118        let mut line = self.original_line.clone();
119        let candidate = &self.candidates[self.index];
120        line.replace_range(candidate.range.clone(), &candidate.text);
121
122        // To figure the new cursor position do a little math:
123        // "he<TAB>" when the completion is "hello" will set the completion
124        // candidate to replace "he" with "hello", so the difference in the
125        // lengths of these two is how far the cursor needs to move.
126        let range_len = candidate.range.end - candidate.range.start;
127        let new_cursor = self.original_cursor + candidate.text.len() - range_len;
128
129        (new_cursor, line)
130    }
131}
132
133impl<'term> LineEditor<'term> {
134    /// Create a new line editor.
135    /// In most cases, you'll want to use the `line_editor` function,
136    /// because it creates a `Terminal` instance with the recommended
137    /// settings, but if you need to decompose that for some reason,
138    /// this snippet shows the recommended way to create a line
139    /// editor:
140    ///
141    /// ```no_run
142    /// use termwiz::caps::{Capabilities, ProbeHints};
143    /// use termwiz::terminal::new_terminal;
144    /// use termwiz::Error;
145    /// // Disable mouse input in the line editor
146    /// let hints = ProbeHints::new_from_env()
147    ///     .mouse_reporting(Some(false));
148    /// let caps = Capabilities::new_with_hints(hints)?;
149    /// let terminal = new_terminal(caps)?;
150    /// # Ok::<(), Error>(())
151    /// ```
152    pub fn new(terminal: &'term mut dyn Terminal) -> Self {
153        Self {
154            terminal,
155            prompt: "> ".to_owned(),
156            line: LineEditBuffer::default(),
157            history_pos: None,
158            bottom_line: None,
159            completion: None,
160            move_to_editor_start: None,
161            move_to_editor_end: None,
162            state: EditorState::Inactive,
163        }
164    }
165
166    fn render(&mut self, host: &mut dyn LineEditorHost) -> Result<()> {
167        let screen_size = self.terminal.get_screen_size()?;
168
169        let mut changes = ChangeSequence::new(screen_size.rows, screen_size.cols);
170
171        changes.add(Change::ClearToEndOfScreen(Default::default()));
172        changes.add(Change::AllAttributes(Default::default()));
173        for ele in host.render_prompt(&self.prompt) {
174            changes.add(ele);
175        }
176        changes.add(Change::AllAttributes(Default::default()));
177
178        // If we're searching, the input area shows the match rather than the input,
179        // and the cursor moves to the first matching character
180        let (line_to_display, cursor) = match &self.state {
181            EditorState::Searching {
182                matching_line,
183                cursor,
184                ..
185            } => (matching_line.as_str(), *cursor),
186            _ => (self.line.get_line(), self.line.get_cursor()),
187        };
188
189        let cursor_position_after_printing_prompt = changes.current_cursor_position();
190
191        let (elements, cursor_x_pos) = host.highlight_line(line_to_display, cursor);
192
193        // Calculate what the cursor position would be after printing X columns
194        // of text from the specified location.
195        // Returns (x, y) of the resultant cursor position.
196        fn compute_cursor_after_printing_x_columns(
197            cursor_x: usize,
198            cursor_y: isize,
199            delta: usize,
200            screen_cols: usize,
201        ) -> (usize, isize) {
202            let y = (cursor_x + delta) / screen_cols;
203            let x = (cursor_x + delta) % screen_cols;
204
205            let row = cursor_y + y as isize;
206            let col = x.max(0) as usize;
207
208            (col, row)
209        }
210        let cursor_position = compute_cursor_after_printing_x_columns(
211            cursor_position_after_printing_prompt.0,
212            cursor_position_after_printing_prompt.1,
213            cursor_x_pos,
214            screen_size.cols,
215        );
216
217        for ele in elements {
218            changes.add(ele);
219        }
220
221        let cursor_after_line_render = changes.current_cursor_position();
222        if cursor_after_line_render.0 == screen_size.cols {
223            // If the cursor position remains in the first column
224            // then the renderer may still consider itself to be on
225            // the prior line; force out an additional character to force
226            // it to apply wrapping/flush.
227            changes.add(" ");
228        }
229
230        if let EditorState::Editing = &self.state {
231            let preview_elements = host.render_preview(line_to_display);
232            if !preview_elements.is_empty() {
233                // Preview starts from a new line.
234                changes.add("\r\n");
235                // Do not be affected by attributes set by highlight_line.
236                changes.add(Change::AllAttributes(Default::default()));
237                for ele in preview_elements {
238                    changes.add(ele);
239                }
240            }
241        }
242
243        if let EditorState::Searching {
244            style, direction, ..
245        } = &self.state
246        {
247            // We want to draw the search state below the input area
248            let label = match (style, direction) {
249                (SearchStyle::Substring, SearchDirection::Backwards) => "bck-i-search",
250                (SearchStyle::Substring, SearchDirection::Forwards) => "fwd-i-search",
251            };
252            // Do not be affected by attributes set by previous lines.
253            changes.add(Change::AllAttributes(Default::default()));
254            // We position the actual cursor on the matching portion of
255            // the text in the line editing area, but since the input
256            // is drawn here, we render an `_` to indicate where the input
257            // position really is.
258            changes.add(format!("\r\n{}: {}_", label, self.line.get_line()));
259        }
260
261        // Add some debugging status at the bottom
262        /*
263        changes.add(format!(
264            "\r\n{:?} {:?}",
265            cursor_position,
266            (changes.cursor_x, changes.cursor_y)
267        ));
268        */
269
270        let render_height = changes.render_height();
271
272        changes.move_to(cursor_position);
273
274        let mut changes = changes.consume();
275        if let Some(start) = self.move_to_editor_start.take() {
276            changes.insert(0, start);
277        }
278        self.terminal.render(&changes)?;
279
280        self.move_to_editor_start.replace(Change::CursorPosition {
281            x: Position::Absolute(0),
282            y: Position::Relative(-1 * cursor_position.1),
283        });
284
285        self.move_to_editor_end.replace(Change::CursorPosition {
286            x: Position::Absolute(0),
287            y: Position::Relative(1 + render_height as isize - cursor_position.1),
288        });
289
290        Ok(())
291    }
292
293    pub fn set_prompt(&mut self, prompt: &str) {
294        self.prompt = prompt.to_owned();
295    }
296
297    /// Enter line editing mode.
298    /// Control is not returned to the caller until a line has been
299    /// accepted, or until an error is detected.
300    /// Returns Ok(None) if the editor was cancelled eg: via CTRL-C.
301    pub fn read_line(&mut self, host: &mut dyn LineEditorHost) -> Result<Option<String>> {
302        self.read_line_with_optional_initial_value(host, None)
303    }
304
305    pub fn read_line_with_optional_initial_value(
306        &mut self,
307        host: &mut dyn LineEditorHost,
308        initial_value: Option<&str>,
309    ) -> Result<Option<String>> {
310        ensure!(
311            self.state == EditorState::Inactive,
312            "recursive call to read_line!"
313        );
314
315        // Clear out the last render info so that we don't over-compensate
316        // on the first call to render().
317        self.move_to_editor_end.take();
318        self.move_to_editor_start.take();
319
320        self.terminal.set_raw_mode()?;
321        self.state = EditorState::Editing;
322        let res = self.read_line_impl(host, initial_value);
323        self.state = EditorState::Inactive;
324
325        if let Some(move_end) = self.move_to_editor_end.take() {
326            self.terminal
327                .render(&[move_end, Change::ClearToEndOfScreen(Default::default())])?;
328        }
329
330        self.terminal.flush()?;
331        self.terminal.set_cooked_mode()?;
332        res
333    }
334
335    fn resolve_action(
336        &mut self,
337        event: &InputEvent,
338        host: &mut dyn LineEditorHost,
339    ) -> Option<Action> {
340        if let Some(action) = host.resolve_action(event, self) {
341            return Some(action);
342        }
343
344        match event {
345            InputEvent::Key(KeyEvent {
346                key: KeyCode::Char('C'),
347                modifiers: Modifiers::CTRL,
348            }) => Some(Action::Cancel),
349
350            InputEvent::Key(KeyEvent {
351                key: KeyCode::Tab,
352                modifiers: Modifiers::NONE,
353            }) => Some(Action::Complete),
354
355            InputEvent::Key(KeyEvent {
356                key: KeyCode::Char('D'),
357                modifiers: Modifiers::CTRL,
358            }) => Some(Action::EndOfFile),
359
360            InputEvent::Key(KeyEvent {
361                key: KeyCode::Char('J'),
362                modifiers: Modifiers::CTRL,
363            })
364            | InputEvent::Key(KeyEvent {
365                key: KeyCode::Char('M'),
366                modifiers: Modifiers::CTRL,
367            })
368            | InputEvent::Key(KeyEvent {
369                key: KeyCode::Enter,
370                modifiers: Modifiers::NONE,
371            }) => Some(Action::AcceptLine),
372            InputEvent::Key(KeyEvent {
373                key: KeyCode::Char('H'),
374                modifiers: Modifiers::CTRL,
375            })
376            | InputEvent::Key(KeyEvent {
377                key: KeyCode::Backspace,
378                modifiers: Modifiers::NONE,
379            }) => Some(Action::Kill(Movement::BackwardChar(1))),
380            InputEvent::Key(KeyEvent {
381                key: KeyCode::Delete,
382                modifiers: Modifiers::NONE,
383            }) => Some(Action::KillAndMove(
384                Movement::ForwardChar(1),
385                Movement::None,
386            )),
387
388            InputEvent::Key(KeyEvent {
389                key: KeyCode::Char('P'),
390                modifiers: Modifiers::CTRL,
391            })
392            | InputEvent::Key(KeyEvent {
393                key: KeyCode::UpArrow,
394                modifiers: Modifiers::NONE,
395            })
396            | InputEvent::Key(KeyEvent {
397                key: KeyCode::ApplicationUpArrow,
398                modifiers: Modifiers::NONE,
399            }) => Some(Action::HistoryPrevious),
400
401            InputEvent::Key(KeyEvent {
402                key: KeyCode::Char('N'),
403                modifiers: Modifiers::CTRL,
404            })
405            | InputEvent::Key(KeyEvent {
406                key: KeyCode::DownArrow,
407                modifiers: Modifiers::NONE,
408            })
409            | InputEvent::Key(KeyEvent {
410                key: KeyCode::ApplicationDownArrow,
411                modifiers: Modifiers::NONE,
412            }) => Some(Action::HistoryNext),
413
414            InputEvent::Key(KeyEvent {
415                key: KeyCode::Char('B'),
416                modifiers: Modifiers::CTRL,
417            })
418            | InputEvent::Key(KeyEvent {
419                key: KeyCode::ApplicationLeftArrow,
420                modifiers: Modifiers::NONE,
421            })
422            | InputEvent::Key(KeyEvent {
423                key: KeyCode::LeftArrow,
424                modifiers: Modifiers::NONE,
425            }) => Some(Action::Move(Movement::BackwardChar(1))),
426
427            InputEvent::Key(KeyEvent {
428                key: KeyCode::Char('W'),
429                modifiers: Modifiers::CTRL,
430            }) => Some(Action::Kill(Movement::BackwardWord(1))),
431
432            InputEvent::Key(KeyEvent {
433                key: KeyCode::Char('b'),
434                modifiers: Modifiers::ALT,
435            })
436            | InputEvent::Key(KeyEvent {
437                key: KeyCode::LeftArrow,
438                modifiers: Modifiers::ALT,
439            })
440            | InputEvent::Key(KeyEvent {
441                key: KeyCode::ApplicationLeftArrow,
442                modifiers: Modifiers::ALT,
443            }) => Some(Action::Move(Movement::BackwardWord(1))),
444
445            InputEvent::Key(KeyEvent {
446                key: KeyCode::Char('f'),
447                modifiers: Modifiers::ALT,
448            })
449            | InputEvent::Key(KeyEvent {
450                key: KeyCode::RightArrow,
451                modifiers: Modifiers::ALT,
452            })
453            | InputEvent::Key(KeyEvent {
454                key: KeyCode::ApplicationRightArrow,
455                modifiers: Modifiers::ALT,
456            }) => Some(Action::Move(Movement::ForwardWord(1))),
457
458            InputEvent::Key(KeyEvent {
459                key: KeyCode::Char('A'),
460                modifiers: Modifiers::CTRL,
461            })
462            | InputEvent::Key(KeyEvent {
463                key: KeyCode::Home,
464                modifiers: Modifiers::NONE,
465            }) => Some(Action::Move(Movement::StartOfLine)),
466            InputEvent::Key(KeyEvent {
467                key: KeyCode::Char('E'),
468                modifiers: Modifiers::CTRL,
469            })
470            | InputEvent::Key(KeyEvent {
471                key: KeyCode::End,
472                modifiers: Modifiers::NONE,
473            }) => Some(Action::Move(Movement::EndOfLine)),
474            InputEvent::Key(KeyEvent {
475                key: KeyCode::Char('F'),
476                modifiers: Modifiers::CTRL,
477            })
478            | InputEvent::Key(KeyEvent {
479                key: KeyCode::RightArrow,
480                modifiers: Modifiers::NONE,
481            })
482            | InputEvent::Key(KeyEvent {
483                key: KeyCode::ApplicationRightArrow,
484                modifiers: Modifiers::NONE,
485            }) => Some(Action::Move(Movement::ForwardChar(1))),
486            InputEvent::Key(KeyEvent {
487                key: KeyCode::Char(c),
488                modifiers: Modifiers::SHIFT,
489            })
490            | InputEvent::Key(KeyEvent {
491                key: KeyCode::Char(c),
492                modifiers: Modifiers::NONE,
493            }) => Some(Action::InsertChar(1, *c)),
494            InputEvent::Paste(text) => Some(Action::InsertText(1, text.clone())),
495            InputEvent::Key(KeyEvent {
496                key: KeyCode::Char('L'),
497                modifiers: Modifiers::CTRL,
498            }) => Some(Action::Repaint),
499            InputEvent::Key(KeyEvent {
500                key: KeyCode::Char('K'),
501                modifiers: Modifiers::CTRL,
502            }) => Some(Action::Kill(Movement::EndOfLine)),
503
504            InputEvent::Key(KeyEvent {
505                key: KeyCode::Char('R'),
506                modifiers: Modifiers::CTRL,
507            }) => Some(Action::HistoryIncSearchBackwards),
508
509            // This is the common binding for forwards, but it is usually
510            // masked by the stty stop setting
511            InputEvent::Key(KeyEvent {
512                key: KeyCode::Char('S'),
513                modifiers: Modifiers::CTRL,
514            }) => Some(Action::HistoryIncSearchForwards),
515
516            _ => None,
517        }
518    }
519
520    fn kill_text(&mut self, kill_movement: Movement, move_movement: Movement) {
521        self.clear_completion();
522        self.line.kill_text(kill_movement, move_movement);
523    }
524
525    fn clear_completion(&mut self) {
526        self.completion = None;
527    }
528
529    fn cancel_search_state(&mut self) {
530        if let EditorState::Searching {
531            matching_line,
532            cursor,
533            ..
534        } = &self.state
535        {
536            self.line.set_line_and_cursor(matching_line, *cursor);
537            self.state = EditorState::Editing;
538        }
539    }
540
541    /// Returns the current line and cursor position.
542    /// You don't normally need to call this unless you are defining
543    /// a custom editor operation on the line buffer contents.
544    /// The cursor position is the byte index into the line UTF-8 bytes.
545    pub fn get_line_and_cursor(&mut self) -> (&str, usize) {
546        (self.line.get_line(), self.line.get_cursor())
547    }
548
549    /// Sets the current line and cursor position.
550    /// You don't normally need to call this unless you are defining
551    /// a custom editor operation on the line buffer contents.
552    /// The cursor position is the byte index into the line UTF-8 bytes.
553    /// Panics: the cursor must be the first byte in a UTF-8 code point
554    /// sequence or the end of the provided line.
555    pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) {
556        self.line.set_line_and_cursor(line, cursor);
557    }
558
559    /// Call this after changing modifying the line buffer.
560    /// If the editor is in search mode this will update the search
561    /// results, otherwise it will be a NOP.
562    fn reapply_search_pattern(&mut self, host: &mut dyn LineEditorHost) {
563        if let EditorState::Searching {
564            style,
565            direction,
566            matching_line,
567            cursor,
568        } = &self.state
569        {
570            // We always start again from the bottom
571            self.history_pos.take();
572
573            let history_pos = match host.history().last() {
574                Some(p) => p,
575                None => {
576                    // TODO: there's no way we can match anything.
577                    // Generate a failed match result?
578                    return;
579                }
580            };
581
582            let last_matching_line;
583            let last_cursor;
584
585            if let Some(result) =
586                host.history()
587                    .search(history_pos, *style, *direction, self.line.get_line())
588            {
589                self.history_pos.replace(result.idx);
590                last_matching_line = result.line.to_string();
591                last_cursor = result.cursor;
592            } else {
593                last_matching_line = matching_line.clone();
594                last_cursor = *cursor;
595            }
596
597            self.state = EditorState::Searching {
598                style: *style,
599                direction: *direction,
600                matching_line: last_matching_line,
601                cursor: last_cursor,
602            };
603        }
604    }
605
606    fn trigger_search(
607        &mut self,
608        style: SearchStyle,
609        direction: SearchDirection,
610        host: &mut dyn LineEditorHost,
611    ) {
612        self.clear_completion();
613
614        if let EditorState::Searching { .. } = &self.state {
615            // Already searching
616        } else {
617            // Not yet searching, so we start a new search
618            // with an empty pattern
619            self.line.clear();
620            self.history_pos.take();
621        }
622
623        let history_pos = match self.history_pos {
624            Some(p) => match direction.next(p) {
625                Some(p) => p,
626                None => return,
627            },
628            None => match host.history().last() {
629                Some(p) => p,
630                None => {
631                    // TODO: there's no way we can match anything.
632                    // Generate a failed match result?
633                    return;
634                }
635            },
636        };
637
638        let search_result =
639            host.history()
640                .search(history_pos, style, direction, self.line.get_line());
641
642        let last_matching_line;
643        let last_cursor;
644
645        if let Some(result) = search_result {
646            self.history_pos.replace(result.idx);
647            last_matching_line = result.line.to_string();
648            last_cursor = result.cursor;
649        } else if let EditorState::Searching {
650            matching_line,
651            cursor,
652            ..
653        } = &self.state
654        {
655            last_matching_line = matching_line.clone();
656            last_cursor = *cursor;
657        } else {
658            last_matching_line = String::new();
659            last_cursor = 0;
660        }
661
662        self.state = EditorState::Searching {
663            style,
664            direction,
665            matching_line: last_matching_line,
666            cursor: last_cursor,
667        };
668    }
669
670    /// Applies the effect of the specified action to the line editor.
671    /// You don't normally need to call this unless you are defining
672    /// custom key mapping or custom actions in your embedding application.
673    pub fn apply_action(&mut self, host: &mut dyn LineEditorHost, action: Action) -> Result<()> {
674        // When searching, reinterpret history next/prev as repeated
675        // search actions in the appropriate direction
676        let action = match (action, &self.state) {
677            (
678                Action::HistoryPrevious,
679                EditorState::Searching {
680                    style: SearchStyle::Substring,
681                    ..
682                },
683            ) => Action::HistoryIncSearchBackwards,
684            (
685                Action::HistoryNext,
686                EditorState::Searching {
687                    style: SearchStyle::Substring,
688                    ..
689                },
690            ) => Action::HistoryIncSearchForwards,
691            (action, _) => action,
692        };
693
694        match action {
695            Action::Cancel => self.state = EditorState::Cancelled,
696            Action::NoAction => {}
697            Action::AcceptLine => {
698                // Make sure that hitting Enter for a line that
699                // shows in the incremental search causes that
700                // line to be accepted, rather than the search pattern!
701                self.cancel_search_state();
702
703                self.state = EditorState::Accepted;
704            }
705            Action::EndOfFile => {
706                return Err(
707                    std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "End Of File").into(),
708                )
709            }
710            Action::Kill(movement) => {
711                self.kill_text(movement, movement);
712                self.reapply_search_pattern(host);
713            }
714            Action::KillAndMove(kill_movement, move_movement) => {
715                self.kill_text(kill_movement, move_movement);
716                self.reapply_search_pattern(host);
717            }
718
719            Action::Move(movement) => {
720                self.clear_completion();
721                self.cancel_search_state();
722                self.line.exec_movement(movement);
723            }
724
725            Action::InsertChar(rep, c) => {
726                self.clear_completion();
727                for _ in 0..rep {
728                    self.line.insert_char(c);
729                }
730                self.reapply_search_pattern(host);
731            }
732            Action::InsertText(rep, text) => {
733                self.clear_completion();
734                for _ in 0..rep {
735                    self.line.insert_text(&text);
736                }
737                self.reapply_search_pattern(host);
738            }
739            Action::Repaint => {
740                self.terminal
741                    .render(&[Change::ClearScreen(Default::default())])?;
742            }
743            Action::HistoryPrevious => {
744                self.clear_completion();
745                self.cancel_search_state();
746
747                if let Some(cur_pos) = self.history_pos.as_ref() {
748                    let prior_idx = cur_pos.saturating_sub(1);
749                    if let Some(prior) = host.history().get(prior_idx) {
750                        self.history_pos = Some(prior_idx);
751                        self.line.set_line_and_cursor(&prior, prior.len());
752                    }
753                } else if let Some(last) = host.history().last() {
754                    self.bottom_line = Some(self.line.get_line().to_string());
755                    self.history_pos = Some(last);
756                    let line = host
757                        .history()
758                        .get(last)
759                        .expect("History::last and History::get to be consistent");
760                    self.line.set_line_and_cursor(&line, line.len())
761                }
762            }
763            Action::HistoryNext => {
764                self.clear_completion();
765                self.cancel_search_state();
766
767                if let Some(cur_pos) = self.history_pos.as_ref() {
768                    let next_idx = cur_pos.saturating_add(1);
769                    if let Some(next) = host.history().get(next_idx) {
770                        self.history_pos = Some(next_idx);
771                        self.line.set_line_and_cursor(&next, next.len());
772                    } else if let Some(bottom) = self.bottom_line.take() {
773                        self.line.set_line_and_cursor(&bottom, bottom.len());
774                    } else {
775                        self.line.clear();
776                    }
777                }
778            }
779
780            Action::HistoryIncSearchBackwards => {
781                self.trigger_search(SearchStyle::Substring, SearchDirection::Backwards, host);
782            }
783            Action::HistoryIncSearchForwards => {
784                self.trigger_search(SearchStyle::Substring, SearchDirection::Forwards, host);
785            }
786
787            Action::Complete => {
788                self.cancel_search_state();
789
790                if self.completion.is_none() {
791                    let candidates = host.complete(self.line.get_line(), self.line.get_cursor());
792                    if !candidates.is_empty() {
793                        let state = CompletionState {
794                            candidates,
795                            index: 0,
796                            original_line: self.line.get_line().to_string(),
797                            original_cursor: self.line.get_cursor(),
798                        };
799
800                        let (cursor, line) = state.current();
801                        self.line.set_line_and_cursor(&line, cursor);
802
803                        // If there is only a single completion then don't
804                        // leave us in a state where we just cycle on the
805                        // same completion over and over.
806                        if state.candidates.len() > 1 {
807                            self.completion = Some(state);
808                        }
809                    }
810                } else if let Some(state) = self.completion.as_mut() {
811                    state.next();
812                    let (cursor, line) = state.current();
813                    self.line.set_line_and_cursor(&line, cursor);
814                }
815            }
816        }
817
818        Ok(())
819    }
820
821    fn read_line_impl(
822        &mut self,
823        host: &mut dyn LineEditorHost,
824        initial_value: Option<&str>,
825    ) -> Result<Option<String>> {
826        self.line.clear();
827        if let Some(value) = initial_value {
828            self.line.set_line_and_cursor(value, value.len());
829        }
830        self.history_pos = None;
831        self.bottom_line = None;
832        self.clear_completion();
833
834        self.render(host)?;
835        while let Some(event) = self.terminal.poll_input(None)? {
836            if let Some(action) = self.resolve_action(&event, host) {
837                self.apply_action(host, action)?;
838                // Editor state might have changed. Re-render to clear
839                // preview or highlight lines differently.
840                self.render(host)?;
841                match self.state {
842                    EditorState::Searching { .. } | EditorState::Editing => {}
843                    EditorState::Cancelled => return Ok(None),
844                    EditorState::Accepted => return Ok(Some(self.line.get_line().to_string())),
845                    EditorState::Inactive => bail!("editor is inactive during read line!?"),
846                }
847            } else {
848                self.render(host)?;
849            }
850        }
851        Ok(Some(self.line.get_line().to_string()))
852    }
853}
854
855/// Create a `Terminal` with the recommended settings for use with
856/// a `LineEditor`.
857pub fn line_editor_terminal() -> Result<impl Terminal> {
858    let hints = ProbeHints::new_from_env().mouse_reporting(Some(false));
859    let caps = Capabilities::new_with_hints(hints)?;
860    new_terminal(caps)
861}