mirror of
https://github.com/wez/wezterm.git
synced 2024-10-28 09:22:19 +03:00
connui: add a sleep_with_reason method
This is used to indicate timeout/retries during connection establishment and also to count down to the automatic window close. The UI will render a progress bar underneath the reason text to show the passage of time, as well as counting down the provided duration.
This commit is contained in:
parent
73c0c02ffb
commit
e7d8068ad9
124
src/connui.rs
124
src/connui.rs
@ -2,11 +2,12 @@ use crate::termwiztermtab;
|
|||||||
use anyhow::{anyhow, bail, Context as _};
|
use anyhow::{anyhow, bail, Context as _};
|
||||||
use crossbeam::channel::{bounded, Receiver, Sender};
|
use crossbeam::channel::{bounded, Receiver, Sender};
|
||||||
use promise::Promise;
|
use promise::Promise;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
use termwiz::cell::unicode_column_width;
|
use termwiz::cell::{unicode_column_width, CellAttributes};
|
||||||
use termwiz::lineedit::*;
|
use termwiz::lineedit::*;
|
||||||
use termwiz::surface::Change;
|
use termwiz::surface::{Change, Position};
|
||||||
use termwiz::terminal::*;
|
use termwiz::terminal::*;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct PasswordPromptHost {
|
struct PasswordPromptHost {
|
||||||
@ -39,6 +40,12 @@ pub enum UIRequest {
|
|||||||
echo: bool,
|
echo: bool,
|
||||||
respond: Promise<String>,
|
respond: Promise<String>,
|
||||||
},
|
},
|
||||||
|
/// Sleep with a progress bar
|
||||||
|
Sleep {
|
||||||
|
reason: String,
|
||||||
|
duration: Duration,
|
||||||
|
respond: Promise<()>,
|
||||||
|
},
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +74,13 @@ impl ConnectionUIImpl {
|
|||||||
}) => {
|
}) => {
|
||||||
respond.result(self.password_prompt(&prompt));
|
respond.result(self.password_prompt(&prompt));
|
||||||
}
|
}
|
||||||
|
Ok(UIRequest::Sleep {
|
||||||
|
reason,
|
||||||
|
duration,
|
||||||
|
mut respond,
|
||||||
|
}) => {
|
||||||
|
respond.result(self.sleep(&reason, duration));
|
||||||
|
}
|
||||||
Err(err) if err.is_timeout() => {}
|
Err(err) if err.is_timeout() => {}
|
||||||
Err(err) => bail!("recv_timeout: {}", err),
|
Err(err) => bail!("recv_timeout: {}", err),
|
||||||
}
|
}
|
||||||
@ -97,6 +111,78 @@ impl ConnectionUIImpl {
|
|||||||
bail!("prompt cancelled");
|
bail!("prompt cancelled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, reason: &str, duration: Duration) -> anyhow::Result<()> {
|
||||||
|
let start = Instant::now();
|
||||||
|
let deadline = start + duration;
|
||||||
|
loop {
|
||||||
|
let now = Instant::now();
|
||||||
|
if now >= deadline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a progress bar underneath the countdown text by reversing
|
||||||
|
// out the text for the elapsed portion of time.
|
||||||
|
let remain = deadline - now;
|
||||||
|
let term_width = self.term.get_screen_size().map(|s| s.cols).unwrap_or(80);
|
||||||
|
let prog_width = term_width as u128 * (duration.as_millis() - remain.as_millis())
|
||||||
|
/ duration.as_millis();
|
||||||
|
let prog_width = prog_width as usize;
|
||||||
|
let message = format!("{} ({:.0?})", reason, remain);
|
||||||
|
|
||||||
|
let mut reversed_string = String::new();
|
||||||
|
let mut default_string = String::new();
|
||||||
|
let mut col = 0;
|
||||||
|
for grapheme in message.graphemes(true) {
|
||||||
|
// Once we've passed the elapsed column, full up the string
|
||||||
|
// that we'll render with default attributes instead.
|
||||||
|
if col > prog_width {
|
||||||
|
default_string.push_str(grapheme);
|
||||||
|
} else {
|
||||||
|
reversed_string.push_str(grapheme);
|
||||||
|
}
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't reach the elapsed column yet (really short text!),
|
||||||
|
// we need to pad out the reversed string.
|
||||||
|
while col < prog_width {
|
||||||
|
reversed_string.push(' ');
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.term.render(&[
|
||||||
|
Change::CursorPosition {
|
||||||
|
x: Position::Absolute(0),
|
||||||
|
y: Position::NoChange,
|
||||||
|
},
|
||||||
|
Change::AllAttributes(CellAttributes::default().set_reverse(true).clone()),
|
||||||
|
Change::Text(reversed_string),
|
||||||
|
Change::AllAttributes(CellAttributes::default()),
|
||||||
|
Change::Text(default_string),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// We use poll_input rather than a raw sleep here so that
|
||||||
|
// eg: resize events can be processed and reflected in the
|
||||||
|
// dimensions reported at the top of the loop.
|
||||||
|
// We're using a sub-second value for the delay here for a
|
||||||
|
// slightly smoother progress bar.
|
||||||
|
self.term
|
||||||
|
.poll_input(Some(remain.min(Duration::from_millis(50))))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = format!("{} (done)\r\n", reason);
|
||||||
|
self.term.render(&[
|
||||||
|
Change::CursorPosition {
|
||||||
|
x: Position::Absolute(0),
|
||||||
|
y: Position::NoChange,
|
||||||
|
},
|
||||||
|
Change::Text(message),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HeadlessImpl {
|
struct HeadlessImpl {
|
||||||
@ -114,6 +200,15 @@ impl HeadlessImpl {
|
|||||||
Ok(UIRequest::Input { mut respond, .. }) => {
|
Ok(UIRequest::Input { mut respond, .. }) => {
|
||||||
respond.result(Err(anyhow!("Input requested from headless context")));
|
respond.result(Err(anyhow!("Input requested from headless context")));
|
||||||
}
|
}
|
||||||
|
Ok(UIRequest::Sleep {
|
||||||
|
mut respond,
|
||||||
|
reason,
|
||||||
|
duration,
|
||||||
|
}) => {
|
||||||
|
log::error!("{} (sleeping for {:?})", reason, duration);
|
||||||
|
std::thread::sleep(duration);
|
||||||
|
respond.result(Ok(()));
|
||||||
|
}
|
||||||
Err(err) if err.is_timeout() => {}
|
Err(err) if err.is_timeout() => {}
|
||||||
Err(err) => bail!("recv_timeout: {}", err),
|
Err(err) => bail!("recv_timeout: {}", err),
|
||||||
}
|
}
|
||||||
@ -136,7 +231,11 @@ impl ConnectionUI {
|
|||||||
if let Err(e) = ui.run() {
|
if let Err(e) = ui.run() {
|
||||||
log::error!("while running ConnectionUI loop: {:?}", e);
|
log::error!("while running ConnectionUI loop: {:?}", e);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::new(10, 0));
|
ui.sleep(
|
||||||
|
"(this window will close automatically)",
|
||||||
|
Duration::new(10, 0),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
Self { tx }
|
Self { tx }
|
||||||
@ -164,6 +263,23 @@ impl ConnectionUI {
|
|||||||
self.output(vec![Change::Text(s)]);
|
self.output(vec![Change::Text(s)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sleep (blocking!) for the specified duration, but updates
|
||||||
|
/// the UI with the reason and a count down during that time.
|
||||||
|
pub fn sleep_with_reason(&self, reason: &str, duration: Duration) -> anyhow::Result<()> {
|
||||||
|
let mut promise = Promise::new();
|
||||||
|
let future = promise.get_future().unwrap();
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.send(UIRequest::Sleep {
|
||||||
|
reason: reason.to_string(),
|
||||||
|
duration,
|
||||||
|
respond: promise,
|
||||||
|
})
|
||||||
|
.context("send to ConnectionUI failed")?;
|
||||||
|
|
||||||
|
future.wait()
|
||||||
|
}
|
||||||
|
|
||||||
/// Crack a multi-line prompt into an optional preamble and the prompt
|
/// Crack a multi-line prompt into an optional preamble and the prompt
|
||||||
/// text on the final line. This is needed because the line editor
|
/// text on the final line. This is needed because the line editor
|
||||||
/// is only designed for a single line prompt; a multi-line prompt
|
/// is only designed for a single line prompt; a multi-line prompt
|
||||||
|
@ -699,13 +699,12 @@ impl Client {
|
|||||||
let mut ui = ConnectionUI::new();
|
let mut ui = ConnectionUI::new();
|
||||||
ui.title("wezterm: Reconnecting...");
|
ui.title("wezterm: Reconnecting...");
|
||||||
|
|
||||||
ui.output_str(&format!(
|
|
||||||
"client disconnected {}; will reconnect in {:?}\n",
|
|
||||||
e, backoff
|
|
||||||
));
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(backoff);
|
ui.sleep_with_reason(
|
||||||
|
&format!("client disconnected {}; will reconnect", e),
|
||||||
|
backoff,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
match reconnectable.connect(false, &mut ui) {
|
match reconnectable.connect(false, &mut ui) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
backoff = BASE_INTERVAL;
|
backoff = BASE_INTERVAL;
|
||||||
|
@ -263,12 +263,14 @@ impl Tab for TermWizTerminalTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
|
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
|
||||||
self.renderable
|
let renderable = self.renderable.borrow();
|
||||||
.borrow()
|
let mut inner = renderable.inner.borrow_mut();
|
||||||
.inner
|
|
||||||
.borrow_mut()
|
inner.surface.resize(size.cols as usize, size.rows as usize);
|
||||||
.surface
|
inner.input_tx.send(InputEvent::Resized {
|
||||||
.resize(size.cols as usize, size.rows as usize);
|
rows: size.rows as usize,
|
||||||
|
cols: size.cols as usize,
|
||||||
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user