1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

termwiz: improve line editor

Move the cursor to the correct column when emoji are input.
Add some docs.
This commit is contained in:
Wez Furlong 2019-05-26 22:39:50 -07:00
parent 4fe790dd0c
commit 5dd0e39b05

View File

@ -1,16 +1,71 @@
//! The `LineEditor` struct provides line editing facilities similar
//! to those in the unix shell.
//! It is recommended that a direct `Terminal` instance be used to
//! construct the `LineEditor` (rather than a `BufferedTerminal`),
//! and to disable mouse input:
//!
//! ```
//! use failure::{err_msg, Fallible};
//! use termwiz::caps::{Capabilities, ProbeHintsBuilder};
//! use termwiz::lineedit::LineEditor;
//! use termwiz::terminal::new_terminal;
//!
//! fn main() -> Fallible<()> {
//! // Disable mouse input in the line editor
//! let hints = ProbeHintsBuilder::new_from_env()
//! .mouse_reporting(Some(false))
//! .build()
//! .map_err(err_msg)?;
//! let caps = Capabilities::new_with_hints(hints)?;
//! let terminal = new_terminal(caps)?;
//! let mut editor = LineEditor::new(terminal);
//!
//! let line = editor.read_line()?;
//! println!("read line: {}", line);
//!
//! Ok(())
//! }
//! ```
//!
//! ## Key Bindings
//!
//! The following key bindings are supported:
//!
//! Keystroke | Action
//! --------- | ------
//! Ctrl-A, Home | Move cursor to the beginning of the line
//! Ctrl-E, End | Move cursor to the end of the line
//! Ctrl-B, Left | Move cursor one grapheme to the left
//! Ctrl-F, Right | Move cursor one grapheme to the right
//! Ctrl-H, Backspace | Delete the grapheme to the left of the cursor
//! Ctrl-J, Ctrl-M, Enter | Finish line editing and accept the current line
use crate::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
use crate::surface::{Change, Position};
use crate::terminal::Terminal;
use failure::Fallible;
use unicode_segmentation::GraphemeCursor;
use unicode_width::UnicodeWidthStr;
pub struct LineEditor<T: Terminal> {
terminal: T,
line: String,
/// byte index into the UTF-8 string data of the insertion
/// point. This is NOT the number of graphemes!
cursor: usize,
}
impl<T: Terminal> LineEditor<T> {
/// Create a new line editor.
/// It is recommended that the terminal be created this way:
/// ```
/// // Disable mouse input in the line editor
/// let hints = ProbeHintsBuilder::new_from_env()
/// .mouse_reporting(Some(false))
/// .build()
/// .map_err(err_msg)?;
/// let caps = Capabilities::new_with_hints(hints)?;
/// let terminal = new_terminal(caps)?;
/// ```
pub fn new(terminal: T) -> Self {
Self {
terminal,
@ -20,6 +75,14 @@ impl<T: Terminal> LineEditor<T> {
}
fn render(&mut self) -> Fallible<()> {
// In order to position the terminal cursor at the right spot,
// we need to compute how many graphemes away from the start of
// the line the current insertion point is. We can do this by
// slicing into the string and requesting its unicode width.
// It might feel more right to count the number of graphemes in
// the string, but this doesn't render correctly for glyphs that
// are double-width. Nothing about unicode is easy :-/
let grapheme_count = UnicodeWidthStr::width(&self.line[0..self.cursor]);
self.terminal.render(&[
Change::CursorPosition {
x: Position::Absolute(0),
@ -28,13 +91,16 @@ impl<T: Terminal> LineEditor<T> {
Change::ClearToEndOfScreen(Default::default()),
Change::Text(self.line.clone()),
Change::CursorPosition {
x: Position::Absolute(self.cursor),
x: Position::Absolute(grapheme_count),
y: Position::NoChange,
},
])?;
Ok(())
}
/// Enter line editing mode.
/// Control is not returned to the caller until a line has been
/// accepted, or until an error is detected.
pub fn read_line(&mut self) -> Fallible<String> {
self.terminal.set_raw_mode()?;
let res = self.read_line_impl();
@ -65,6 +131,10 @@ impl<T: Terminal> LineEditor<T> {
break;
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('H'),
modifiers: Modifiers::CTRL,
})
| InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::NONE,
}) => {
@ -75,6 +145,10 @@ impl<T: Terminal> LineEditor<T> {
}
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('B'),
modifiers: Modifiers::CTRL,
})
| InputEvent::Key(KeyEvent {
key: KeyCode::LeftArrow,
modifiers: Modifiers::NONE,
}) => {
@ -108,6 +182,10 @@ impl<T: Terminal> LineEditor<T> {
}
}
InputEvent::Key(KeyEvent {
key: KeyCode::Char('F'),
modifiers: Modifiers::CTRL,
})
| InputEvent::Key(KeyEvent {
key: KeyCode::RightArrow,
modifiers: Modifiers::NONE,
}) => {