1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 15:04:36 +03:00

avoid deadlock while pasting large chunks of text

A big paste could saturate the input/output of the pty and
lead to an effective deadlock; the application wants to send
output to us but we are busy trying to write the paste to it.

Break up large pastes into chunks and send them piece by piece.
This does mean that a large bracketed paste is not an atomic
unit any longer, but it seems to work out ok when pasting into
vim.
This commit is contained in:
Wez Furlong 2019-03-23 15:25:49 -07:00
parent c93f967bc4
commit acc895f1cd
3 changed files with 80 additions and 5 deletions

View File

@ -1,9 +1,12 @@
use super::window::TerminalWindow;
use crate::mux::tab::Tab;
use crate::frontend::gui_executor;
use crate::mux::tab::{Tab, TabId};
use crate::mux::Mux;
use clipboard::{ClipboardContext, ClipboardProvider};
use failure::Error;
use promise::Future;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use term::{KeyCode, KeyModifiers};
use termwiz::hyperlink::Hyperlink;
@ -22,6 +25,45 @@ pub struct HostImpl<H: HostHelper> {
clipboard: Option<ClipboardContext>,
}
const PASTE_CHUNK_SIZE: usize = 1024;
struct Paste {
tab_id: TabId,
text: String,
offset: usize,
}
fn schedule_next_paste(paste: &Arc<Mutex<Paste>>) {
let paste = Arc::clone(paste);
Future::with_executor(gui_executor().unwrap(), move || {
let mut locked = paste.lock().unwrap();
let mux = Mux::get().unwrap();
let tab = mux.get_tab(locked.tab_id).unwrap();
let remain = locked.text.len() - locked.offset;
let chunk = remain.min(PASTE_CHUNK_SIZE);
let text_slice = &locked.text[locked.offset..locked.offset + chunk];
tab.send_paste(text_slice).unwrap();
if chunk < remain {
// There is more to send
locked.offset += chunk;
schedule_next_paste(&paste);
}
Ok(())
});
}
fn trickle_paste(tab_id: TabId, text: String) {
let paste = Arc::new(Mutex::new(Paste {
tab_id,
text,
offset: PASTE_CHUNK_SIZE,
}));
schedule_next_paste(&paste);
}
impl<H: HostHelper> HostImpl<H> {
pub fn new(helper: H) -> Self {
Self {
@ -78,7 +120,15 @@ impl<H: HostHelper> HostImpl<H> {
if (cfg!(target_os = "macos") && mods == KeyModifiers::SUPER && key == KeyCode::Char('v'))
|| (mods == KeyModifiers::SHIFT && key == KeyCode::Insert)
{
tab.send_paste(&self.get_clipboard()?)?;
let text = self.get_clipboard()?;
if text.len() <= PASTE_CHUNK_SIZE {
// Send it all now
tab.send_paste(&text)?;
return Ok(true);
}
// It's pretty heavy, so we trickle it into the pty
tab.send_paste(&text[0..PASTE_CHUNK_SIZE])?;
trickle_paste(tab.tab_id(), text);
return Ok(true);
}
if mods == (KeyModifiers::SUPER | KeyModifiers::SHIFT)

View File

@ -5,6 +5,7 @@ use crate::mux::Mux;
use failure::Error;
use promise::Executor;
use serde_derive::*;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
@ -34,9 +35,23 @@ impl Default for FrontEndSelection {
}
}
thread_local! {
static EXECUTOR: RefCell<Option<Box<Executor>>> = RefCell::new(None);
}
pub fn gui_executor() -> Option<Box<Executor>> {
let mut res = None;
EXECUTOR.with(|exec| {
if let Some(exec) = &*exec.borrow() {
res = Some(exec.clone_executor());
}
});
res
}
impl FrontEndSelection {
pub fn try_new(self, mux: &Rc<Mux>) -> Result<Rc<FrontEnd>, Error> {
match self {
let front_end = match self {
FrontEndSelection::Glutin => glium::glutinloop::GlutinFrontEnd::try_new(mux),
#[cfg(all(unix, not(target_os = "macos")))]
FrontEndSelection::X11 => xwindows::x11loop::X11FrontEnd::try_new(mux),
@ -44,7 +59,13 @@ impl FrontEndSelection {
FrontEndSelection::X11 => bail!("X11 not compiled in"),
FrontEndSelection::MuxServer => muxserver::MuxServerFrontEnd::try_new(mux),
FrontEndSelection::Null => muxserver::MuxServerFrontEnd::new_null(mux),
}
}?;
EXECUTOR.with(|exec| {
*exec.borrow_mut() = Some(front_end.gui_executor());
});
Ok(front_end)
}
// TODO: find or build a proc macro for this

View File

@ -740,6 +740,10 @@ impl TerminalState {
}
}
pub fn bracketed_paste_enabled(&self) -> bool {
self.bracketed_paste
}
/// Send text to the terminal that is the result of pasting.
/// If bracketed paste mode is enabled, the paste is enclosed
/// in the bracketing, otherwise it is fed to the pty as-is.