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:
parent
061d53bcfa
commit
446b3f7fd5
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user