1
1
mirror of https://github.com/wez/wezterm.git synced 2024-10-28 09:22:19 +03:00

mux: improve predictive local echo

Extends the "predictions" to allow for some basic cursor movement
via cursor keys and text deletion.

In addition, show predictions for pasted text.

refs: https://github.com/wez/wezterm/issues/127
This commit is contained in:
Wez Furlong 2020-03-08 08:36:52 -07:00
parent 061d53bcfa
commit 446b3f7fd5
3 changed files with 127 additions and 24 deletions

View File

@ -27,7 +27,11 @@ struct PerTab {
}
impl PerTab {
fn compute_changes(&mut self, tab: &Rc<dyn Tab>) -> Option<GetTabRenderChangesResponse> {
fn compute_changes(
&mut self,
tab: &Rc<dyn Tab>,
force: bool,
) -> Option<GetTabRenderChangesResponse> {
let mut changed = false;
let mouse_grabbed = tab.is_mouse_grabbed();
if mouse_grabbed != self.mouse_grabbed {
@ -62,7 +66,7 @@ impl PerTab {
changed = true;
}
if !changed {
if !changed && !force {
return None;
}
@ -120,7 +124,7 @@ fn maybe_push_tab_changes(
per_tab: Arc<Mutex<PerTab>>,
) -> anyhow::Result<()> {
let mut per_tab = per_tab.lock().unwrap();
if let Some(resp) = per_tab.compute_changes(tab) {
if let Some(resp) = per_tab.compute_changes(tab, false) {
sender.send(DecodedPdu {
pdu: Pdu::GetTabRenderChangesResponse(resp),
serial: 0,
@ -295,7 +299,17 @@ impl SessionHandler {
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {}", tab_id))?;
tab.key_down(event.key, event.modifiers)?;
maybe_push_tab_changes(&tab, sender, per_tab)?;
// For a key press, we want to always send back the
// cursor position so that the predictive echo doesn't
// leave the cursor in the wrong place
let mut per_tab = per_tab.lock().unwrap();
if let Some(resp) = per_tab.compute_changes(&tab, true) {
sender.send(DecodedPdu {
pdu: Pdu::GetTabRenderChangesResponse(resp),
serial: 0,
})?;
}
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,

View File

@ -136,6 +136,12 @@ impl Tab for ClientTab {
fn send_paste(&self, text: &str) -> anyhow::Result<()> {
let client = Arc::clone(&self.client);
let remote_tab_id = self.remote_tab_id;
self.renderable
.borrow()
.inner
.borrow_mut()
.predict_from_paste(text);
let data = text.to_owned();
promise::spawn::spawn(async move {
client

View File

@ -135,14 +135,7 @@ impl RenderableInner {
/// other editor commands
/// There are bound to be a number of other edge cases that we should
/// handle.
fn apply_prediction(&mut self, c: char, line: &mut Line) {
let cell = Cell::new(
c,
CellAttributes::default()
.set_underline(Underline::Double)
.clone(),
);
fn apply_prediction(&mut self, c: KeyCode, line: &mut Line) {
let text = line.as_str();
if text.contains("sword:") {
// This line might be a password prompt. Don't force
@ -151,9 +144,46 @@ impl RenderableInner {
return;
}
let cell = line.set_cell(self.cursor_position.x, cell);
// Adjust the cursor to reflect the width of this new cell
self.cursor_position.x += cell.width();
match c {
KeyCode::Enter => {
self.cursor_position.x = 0;
self.cursor_position.y += 1;
}
KeyCode::UpArrow => {
self.cursor_position.y = self.cursor_position.y.saturating_sub(1);
}
KeyCode::DownArrow => {
self.cursor_position.y += 1;
}
KeyCode::RightArrow => {
self.cursor_position.x += 1;
}
KeyCode::LeftArrow => {
self.cursor_position.x = self.cursor_position.x.saturating_sub(1);
}
KeyCode::Delete => {
line.erase_cell(self.cursor_position.x);
}
KeyCode::Backspace => {
if self.cursor_position.x > 0 {
line.erase_cell(self.cursor_position.x - 1);
self.cursor_position.x -= 1;
}
}
KeyCode::Char(c) => {
let cell = Cell::new(
c,
CellAttributes::default()
.set_underline(Underline::Double)
.clone(),
);
let cell = line.set_cell(self.cursor_position.x, cell);
// Adjust the cursor to reflect the width of this new cell
self.cursor_position.x += cell.width();
}
_ => {}
}
}
/// Based on a keypress, apply a "prediction" of what the terminal
@ -162,35 +192,88 @@ impl RenderableInner {
/// when a user is typing at any reasonable velocity.
pub fn predict_from_key_event(&mut self, key: KeyCode, mods: KeyModifiers) {
let c = match key {
KeyCode::Char(c) => c,
KeyCode::LeftArrow
| KeyCode::RightArrow
| KeyCode::UpArrow
| KeyCode::DownArrow
| KeyCode::Delete
| KeyCode::Backspace
| KeyCode::Enter
| KeyCode::Char(_) => key,
_ => return,
};
if mods != KeyModifiers::NONE && mods != KeyModifiers::SHIFT {
return;
}
match self.lines.pop(&self.cursor_position.y) {
let row = self.cursor_position.y;
match self.lines.pop(&row) {
Some(LineEntry::Stale(mut line))
| Some(LineEntry::Line(mut line))
| Some(LineEntry::Dirty(mut line)) => {
self.apply_prediction(c, &mut line);
self.lines
.put(self.cursor_position.y, LineEntry::Dirty(line));
self.lines.put(row, LineEntry::Dirty(line));
}
Some(LineEntry::DirtyAndFetching(mut line, instant)) => {
self.apply_prediction(c, &mut line);
self.lines.put(
self.cursor_position.y,
LineEntry::DirtyAndFetching(line, instant),
);
self.lines
.put(row, LineEntry::DirtyAndFetching(line, instant));
}
Some(entry) => {
self.lines.put(self.cursor_position.y, entry);
self.lines.put(row, entry);
}
None => {}
}
}
fn apply_paste_prediction(&mut self, row: usize, text: &str, line: &mut Line) {
let attrs = CellAttributes::default()
.set_underline(Underline::Double)
.clone();
let text_line = Line::from_text(text, &attrs);
if row == 0 {
for cell in text_line.cells() {
line.set_cell(self.cursor_position.x, cell.clone());
self.cursor_position.x += cell.width();
}
} else {
// The pasted line replaces the data for the existing line
line.resize_and_clear(0);
line.append_line(text_line);
self.cursor_position.x = line.cells().len();
}
}
pub fn predict_from_paste(&mut self, text: &str) {
let text = textwrap::fill(text, self.dimensions.cols);
let lines: Vec<&str> = text.split("\n").collect();
for (idx, paste_line) in lines.iter().enumerate() {
let row = self.cursor_position.y + idx as StableRowIndex;
match self.lines.pop(&row) {
Some(LineEntry::Stale(mut line))
| Some(LineEntry::Line(mut line))
| Some(LineEntry::Dirty(mut line)) => {
self.apply_paste_prediction(idx, paste_line, &mut line);
self.lines.put(row, LineEntry::Dirty(line));
}
Some(LineEntry::DirtyAndFetching(mut line, instant)) => {
self.apply_paste_prediction(idx, paste_line, &mut line);
self.lines
.put(row, LineEntry::DirtyAndFetching(line, instant));
}
Some(entry) => {
self.lines.put(row, entry);
}
None => {}
}
}
self.cursor_position.y += lines.len().saturating_sub(1) as StableRowIndex;
}
pub fn update_last_send(&mut self) {
self.last_send_time = Instant::now();
self.poll_interval = BASE_POLL_INTERVAL;