mirror of
https://github.com/wez/wezterm.git
synced 2024-11-27 12:23:46 +03:00
lineedit: add history
This commit is contained in:
parent
2b85d5dca4
commit
a94c802a74
@ -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(())
|
||||
}
|
||||
|
@ -19,4 +19,6 @@ pub enum Action {
|
||||
Repaint,
|
||||
Move(Movement),
|
||||
Kill(Movement),
|
||||
HistoryPrevious,
|
||||
HistoryNext,
|
||||
}
|
||||
|
46
termwiz/src/lineedit/history.rs
Normal file
46
termwiz/src/lineedit/history.rs
Normal 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());
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user