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

lineedit: add history

This commit is contained in:
Wez Furlong 2019-05-27 13:35:24 -07:00
parent 2b85d5dca4
commit a94c802a74
5 changed files with 152 additions and 12 deletions

View File

@ -1,9 +1,12 @@
use failure::Fallible;
use termwiz::cell::AttributeChange;
use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
use termwiz::lineedit::{line_editor, LineEditorHost, OutputElement};
use termwiz::lineedit::*;
struct Host {}
#[derive(Default)]
struct Host {
history: BasicHistory,
}
impl LineEditorHost for Host {
// Render the prompt with a darkslateblue background color if
@ -20,14 +23,27 @@ impl LineEditorHost for Host {
OutputElement::Text(prompt.to_owned()),
]
}
fn history(&mut self) -> &mut History {
&mut self.history
}
}
fn main() -> Fallible<()> {
println!("Type `exit` to quit this example");
let mut editor = line_editor()?;
let mut host = Host {};
let line = editor.read_line(&mut host)?;
println!("read line: {:?}", line);
let mut host = Host::default();
loop {
if let Some(line) = editor.read_line(&mut host)? {
println!("read line: {:?}", line);
if line == "exit" {
break;
}
host.history().add(&line);
}
}
Ok(())
}

View File

@ -19,4 +19,6 @@ pub enum Action {
Repaint,
Move(Movement),
Kill(Movement),
HistoryPrevious,
HistoryNext,
}

View File

@ -0,0 +1,46 @@
use std::collections::VecDeque;
/// Represents a position within the history.
/// Smaller numbers are assumed to be before larger numbers,
/// and the indices are assumed to be contiguous.
pub type HistoryIndex = usize;
/// Defines the history interface for the line editor.
pub trait History {
/// Lookup the line corresponding to an index.
fn get(&self, idx: HistoryIndex) -> Option<&str>;
/// Return the index for the most recently added entry.
fn last(&self) -> Option<HistoryIndex>;
/// Add an entry.
/// Note that the LineEditor will not automatically call
/// the add method.
fn add(&mut self, line: &str);
}
/// A simple history implementation that holds entries in memory.
#[derive(Default)]
pub struct BasicHistory {
entries: VecDeque<String>,
}
impl History for BasicHistory {
fn get(&self, idx: HistoryIndex) -> Option<&str> {
self.entries.get(idx).map(String::as_str)
}
fn last(&self) -> Option<HistoryIndex> {
if self.entries.is_empty() {
None
} else {
Some(self.entries.len() - 1)
}
}
fn add(&mut self, line: &str) {
if self.entries.back().map(String::as_str) == Some(line) {
// Ignore duplicates
return;
}
self.entries.push_back(line.to_owned());
}
}

View File

@ -1,4 +1,5 @@
use crate::cell::{AttributeChange, CellAttributes};
use crate::lineedit::{BasicHistory, History};
use crate::surface::Change;
/// The `OutputElement` type allows returning graphic attribute changes
@ -59,8 +60,18 @@ pub trait LineEditorHost {
fn highlight_line(&self, line: &str, _cursor_position: usize) -> Vec<OutputElement> {
vec![OutputElement::Text(line.to_owned())]
}
/// Returns the history implementation
fn history(&mut self) -> &mut History;
}
/// A concrete implementation of `LineEditorHost` that uses the default behaviors.
pub struct NopLineEditorHost {}
impl LineEditorHost for NopLineEditorHost {}
#[derive(Default)]
pub struct NopLineEditorHost {
history: BasicHistory,
}
impl LineEditorHost for NopLineEditorHost {
fn history(&mut self) -> &mut History {
&mut self.history
}
}

View File

@ -3,11 +3,11 @@
//!
//! ```no_run
//! use failure::Fallible;
//! use termwiz::lineedit::{line_editor,NopLineEditorHost};
//! use termwiz::lineedit::{line_editor, NopLineEditorHost};
//!
//! fn main() -> Fallible<()> {
//! let mut editor = line_editor()?;
//! let mut host = NopLineEditorHost{};
//! let mut host = NopLineEditorHost::default();
//!
//! let line = editor.read_line(&mut host)?;
//! println!("read line: {:?}", line);
@ -43,19 +43,21 @@ use unicode_segmentation::GraphemeCursor;
use unicode_width::UnicodeWidthStr;
mod actions;
mod history;
mod host;
pub use actions::{Action, Movement, RepeatCount};
pub use history::*;
pub use host::*;
/// The `LineEditor` struct provides line editing facilities similar
/// to those in the unix shell.
/// ```no_run
/// use failure::Fallible;
/// use termwiz::lineedit::{line_editor,NopLineEditorHost};
/// use termwiz::lineedit::{line_editor, NopLineEditorHost};
///
/// fn main() -> Fallible<()> {
/// let mut editor = line_editor()?;
/// let mut host = NopLineEditorHost{};
/// let mut host = NopLineEditorHost::default();
///
/// let line = editor.read_line(&mut host)?;
/// println!("read line: {:?}", line);
@ -70,6 +72,9 @@ pub struct LineEditor<T: Terminal> {
/// byte index into the UTF-8 string data of the insertion
/// point. This is NOT the number of graphemes!
cursor: usize,
history_pos: Option<usize>,
bottom_line: Option<String>,
}
impl<T: Terminal> LineEditor<T> {
@ -99,6 +104,8 @@ impl<T: Terminal> LineEditor<T> {
prompt: "> ".to_owned(),
line: String::new(),
cursor: 0,
history_pos: None,
bottom_line: None,
}
}
@ -184,6 +191,25 @@ impl<T: Terminal> LineEditor<T> {
key: KeyCode::Backspace,
modifiers: Modifiers::NONE,
}) => Some(Action::Kill(Movement::BackwardChar(1))),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('P'),
modifiers: Modifiers::CTRL,
})
| InputEvent::Key(KeyEvent {
key: KeyCode::UpArrow,
modifiers: Modifiers::NONE,
}) => Some(Action::HistoryPrevious),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('N'),
modifiers: Modifiers::CTRL,
})
| InputEvent::Key(KeyEvent {
key: KeyCode::DownArrow,
modifiers: Modifiers::NONE,
}) => Some(Action::HistoryNext),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('B'),
modifiers: Modifiers::CTRL,
@ -375,6 +401,8 @@ impl<T: Terminal> LineEditor<T> {
fn read_line_impl(&mut self, host: &mut LineEditorHost) -> Fallible<Option<String>> {
self.line.clear();
self.cursor = 0;
self.history_pos = None;
self.bottom_line = None;
self.render(host)?;
while let Some(event) = self.terminal.poll_input(None)? {
@ -402,7 +430,44 @@ impl<T: Terminal> LineEditor<T> {
self.terminal
.render(&[Change::ClearScreen(Default::default())])?;
}
_ => {}
Some(Action::HistoryPrevious) => {
if let Some(cur_pos) = self.history_pos.as_ref() {
let prior_idx = cur_pos.saturating_sub(1);
if let Some(prior) = host.history().get(prior_idx) {
self.history_pos = Some(prior_idx);
self.line = prior.to_string();
self.cursor = self.line.len();
}
} else {
if let Some(last) = host.history().last() {
self.bottom_line = Some(self.line.clone());
self.history_pos = Some(last);
self.line = host
.history()
.get(last)
.expect("History::last and History::get to be consistent")
.to_string();
self.cursor = self.line.len();
}
}
}
Some(Action::HistoryNext) => {
if let Some(cur_pos) = self.history_pos.as_ref() {
let next_idx = cur_pos.saturating_add(1);
if let Some(next) = host.history().get(next_idx) {
self.history_pos = Some(next_idx);
self.line = next.to_string();
self.cursor = self.line.len();
} else if let Some(bottom) = self.bottom_line.take() {
self.line = bottom;
self.cursor = self.line.len();
} else {
self.line.clear();
self.cursor = 0;
}
}
}
None => {}
}
self.render(host)?;
}