1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-02 23:55:11 +03:00
wezterm/termwiz/examples/line_editor.rs
Wez Furlong 79007d9c33 termwiz: line editor: allow custom editor actions
This restructures the LineEditor to allow the hosting application to
override key presses and apply custom edits to the editor buffer.

Methods for performing predefined actions and for accessing the line
buffer and cursor position have been provided/exposed to support this.

One consequence of this change is that the editor instance needs to be
passed through to the host trait impl and that means that the LineEditor
can no longer be generic over `Terminal`.  Instead we now take `&mut dyn
Terminal` which was how the majority of non-example code was using it in
any case.  This simplifies a bit of boilerplate in wezterm but adds an
extra line to the most basic examples.
2020-04-09 07:37:23 -07:00

112 lines
3.6 KiB
Rust

use termwiz::cell::AttributeChange;
use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
use termwiz::lineedit::*;
#[derive(Default)]
struct Host {
history: BasicHistory,
}
impl LineEditorHost for Host {
// Render the prompt with a darkslateblue background color if
// the terminal supports true color, otherwise render it with
// a navy blue ansi color.
fn render_prompt(&self, prompt: &str) -> Vec<OutputElement> {
vec![
OutputElement::Attribute(AttributeChange::Background(
ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::from_named("darkslateblue").unwrap(),
AnsiColor::Navy.into(),
),
)),
OutputElement::Text(prompt.to_owned()),
]
}
fn history(&mut self) -> &mut dyn History {
&mut self.history
}
/// Demo of the completion API for words starting with "h" or "he"
fn complete(&self, line: &str, cursor_position: usize) -> Vec<CompletionCandidate> {
let mut candidates = vec![];
if let Some((range, word)) = word_at_cursor(line, cursor_position) {
let words = &["hello", "help", "he-man"];
for w in words {
if w.starts_with(word) {
candidates.push(CompletionCandidate {
range: range.clone(),
text: w.to_string(),
});
}
}
}
candidates
}
}
/// This is a conceptually simple function that computes the bounds
/// of the whitespace delimited word at the specified cursor position
/// in the supplied line string.
/// It returns the range and the corresponding slice out of the line.
/// This function is sufficient for example purposes; in a real application
/// the equivalent function would need to be aware of quoting and other
/// application specific context.
fn word_at_cursor(line: &str, cursor_position: usize) -> Option<(std::ops::Range<usize>, &str)> {
let char_indices: Vec<(usize, char)> = line.char_indices().collect();
if char_indices.is_empty() {
return None;
}
let char_position = char_indices
.iter()
.position(|(idx, _)| *idx == cursor_position)
.unwrap_or(char_indices.len());
// Look back until we find whitespace
let mut start_position = char_position;
while start_position > 0
&& start_position <= char_indices.len()
&& !char_indices[start_position - 1].1.is_whitespace()
{
start_position -= 1;
}
// Look forwards until we find whitespace
let mut end_position = char_position;
while end_position < char_indices.len() && !char_indices[end_position].1.is_whitespace() {
end_position += 1;
}
if end_position > start_position {
let range = char_indices[start_position].0
..char_indices
.get(end_position)
.map(|c| c.0 + 1)
.unwrap_or(line.len());
Some((range.clone(), &line[range]))
} else {
None
}
}
fn main() -> anyhow::Result<()> {
println!("Type `exit` to quit this example, or start a word with `h` and press Tab.");
let mut terminal = line_editor_terminal()?;
let mut editor = LineEditor::new(&mut terminal);
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(())
}