Fix reconnect stale keys and TUI status rendering
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
This commit is contained in:
+46
-6
@@ -3480,7 +3480,11 @@ async fn run_terminal(
|
||||
// on how long the link has been silent (recomputed after any
|
||||
// reconnect attempt above may have reset `last_packet_at`).
|
||||
if !forward_only {
|
||||
let action = disconnect_status.on_tick(last_packet_at.elapsed());
|
||||
let action = if predictor.alternate_screen {
|
||||
disconnect_status.on_suppressed()
|
||||
} else {
|
||||
disconnect_status.on_tick(last_packet_at.elapsed())
|
||||
};
|
||||
disconnect_status.apply(action)?;
|
||||
}
|
||||
}
|
||||
@@ -4854,8 +4858,10 @@ const DISCONNECT_STATUS_THRESHOLD_SECS: u64 = 2;
|
||||
/// When server packets stop arriving we paint one blue line on the terminal's
|
||||
/// top row — e.g. `[dosh] reconnecting — last contact 3s ago` — using
|
||||
/// save/restore cursor (ESC 7 / ESC 8) so the real application cursor never
|
||||
/// moves and full-screen TUIs are not corrupted. The bar is refreshed on the
|
||||
/// status timer tick and cleared the instant a packet resumes.
|
||||
/// moves. The run loop suppresses the bar while the server is in the alternate
|
||||
/// screen because a saved cursor does not make writing row 1 non-destructive for
|
||||
/// full-screen TUIs. The bar is refreshed on the status timer tick and cleared
|
||||
/// the instant a packet resumes.
|
||||
///
|
||||
/// The decision logic and the rendered text are pure and unit-tested; only
|
||||
/// [`DisconnectStatus::emit`] / [`DisconnectStatus::emit_clear`] touch stdout.
|
||||
@@ -4878,6 +4884,8 @@ enum StatusAction {
|
||||
Show(String),
|
||||
/// Erase the status line (link recovered or feature disabled).
|
||||
Clear,
|
||||
/// Drop tracked status state without touching the terminal.
|
||||
Forget,
|
||||
}
|
||||
|
||||
impl DisconnectStatus {
|
||||
@@ -4936,6 +4944,18 @@ impl DisconnectStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure decision for periods where drawing the top-row status is unsafe
|
||||
/// (alternate-screen TUIs). Forget any tracked bar without writing a clear
|
||||
/// sequence, because clearing row 1 would corrupt the TUI just like showing
|
||||
/// the bar would.
|
||||
fn on_suppressed(&self) -> StatusAction {
|
||||
if self.shown {
|
||||
StatusAction::Forget
|
||||
} else {
|
||||
StatusAction::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Record that an action was applied to the screen, updating internal state.
|
||||
fn applied(&mut self, action: &StatusAction) {
|
||||
match action {
|
||||
@@ -4947,6 +4967,10 @@ impl DisconnectStatus {
|
||||
self.shown = false;
|
||||
self.drawn_text.clear();
|
||||
}
|
||||
StatusAction::Forget => {
|
||||
self.shown = false;
|
||||
self.drawn_text.clear();
|
||||
}
|
||||
StatusAction::None => {}
|
||||
}
|
||||
}
|
||||
@@ -4956,6 +4980,7 @@ impl DisconnectStatus {
|
||||
match &action {
|
||||
StatusAction::Show(text) => self.emit(text)?,
|
||||
StatusAction::Clear => self.emit_clear()?,
|
||||
StatusAction::Forget => {}
|
||||
StatusAction::None => {}
|
||||
}
|
||||
self.applied(&action);
|
||||
@@ -6143,9 +6168,24 @@ mod tests {
|
||||
assert_eq!(status.on_tick(Duration::from_secs(10)), StatusAction::Clear);
|
||||
}
|
||||
|
||||
/// The rendered overlay must be non-destructive: save cursor (ESC 7), park on
|
||||
/// the top row, paint the blue reconnect bar, then restore cursor (ESC 8) so
|
||||
/// the application's cursor and full-screen TUIs are never disturbed.
|
||||
#[test]
|
||||
fn disconnect_status_suppresses_without_touching_alternate_screen() {
|
||||
let mut status = DisconnectStatus::new(true);
|
||||
let show = status.on_tick(Duration::from_secs(3));
|
||||
status.applied(&show);
|
||||
assert!(status.shown);
|
||||
|
||||
let suppressed = status.on_suppressed();
|
||||
assert_eq!(suppressed, StatusAction::Forget);
|
||||
status.applied(&suppressed);
|
||||
assert!(!status.shown);
|
||||
assert_eq!(status.drawn_text, "");
|
||||
}
|
||||
|
||||
/// The rendered overlay must preserve cursor position: save cursor (ESC 7),
|
||||
/// park on the top row, paint the blue reconnect bar, then restore cursor
|
||||
/// (ESC 8). The run loop suppresses this renderer entirely in full-screen
|
||||
/// alternate-screen TUIs.
|
||||
#[test]
|
||||
fn disconnect_status_overlay_is_save_restore_top_blue_bar() {
|
||||
let bytes = render_status_overlay("[dosh] reconnecting — last contact 3s ago", 24);
|
||||
|
||||
Reference in New Issue
Block a user