fix: restore connection progress dialog and support Enter to retry

This commit is contained in:
TomZz
2026-06-27 00:37:33 +08:00
parent 8cc64acdc1
commit 8854ee9afb
4 changed files with 252 additions and 0 deletions
+128
View File
@@ -260,6 +260,8 @@ pub(crate) struct Ashell {
pub(crate) tabs_scroll_handle: gpui::ScrollHandle,
pub(crate) selector_scroll_handle: gpui::ScrollHandle,
pub(crate) saved_scroll_handle: gpui::ScrollHandle,
pub(crate) connection_scroll_handle: gpui::ScrollHandle,
pub(crate) connection_progress: Option<ConnectionProgress>,
pub(crate) pending_sftp_path_sync: Option<String>,
pub(crate) sftp_context_menu: Option<SftpContextMenuState>,
pub(crate) sftp_creating_folder: bool,
@@ -326,6 +328,14 @@ pub(crate) enum SelectorEntry {
Saved(String),
}
#[derive(Clone)]
pub(crate) struct ConnectionProgress {
pub(crate) tab_id: String,
pub(crate) title: SharedString,
pub(crate) lines: Vec<SharedString>,
pub(crate) failed: bool,
}
#[derive(Clone)]
pub(crate) struct SftpContextMenuState {
pub(crate) remote_path: String,
@@ -607,6 +617,8 @@ impl Ashell {
tabs_scroll_handle: gpui::ScrollHandle::new(),
selector_scroll_handle: gpui::ScrollHandle::new(),
saved_scroll_handle: gpui::ScrollHandle::new(),
connection_scroll_handle: gpui::ScrollHandle::new(),
connection_progress: None,
pending_sftp_path_sync: Some("/".into()),
sftp_context_menu: None,
sftp_creating_folder: false,
@@ -799,6 +811,14 @@ impl Ashell {
tab.backend_initialized = true;
tab.status = text.clone();
}
if let Some(progress) = self.connection_progress.as_mut() {
if progress.tab_id == tab_id {
progress.lines.push(text.clone().into());
let _idx = progress.lines.len().saturating_sub(1);
self.connection_scroll_handle
.set_offset(gpui::point(px(0.), px(-99999.0)));
}
}
self.status = text.into();
}
BackendEvent::Connected { tab_id } => {
@@ -809,6 +829,13 @@ impl Ashell {
}
self.sync_system_tab_to_active_group();
self.request_active_system_snapshot();
if self
.connection_progress
.as_ref()
.is_some_and(|progress| progress.tab_id == tab_id && !progress.failed)
{
self.connection_progress = None;
}
}
BackendEvent::PromptRequest {
tab_id,
@@ -914,6 +941,16 @@ impl Ashell {
if self.system_tab_id.as_deref() == Some(tab_id.as_str()) {
self.system_status = Some(reason.clone().into());
}
if let Some(progress) = self.connection_progress.as_mut() {
if progress.tab_id == tab_id {
progress.lines.push(reason.clone().into());
let _idx = progress.lines.len().saturating_sub(1);
self.connection_scroll_handle
.set_offset(gpui::point(px(0.), px(-99999.0)));
progress.title = t!("connection_failed").into();
progress.failed = true;
}
}
self.status = reason.into();
}
BackendEvent::TransferProgress {
@@ -1086,6 +1123,97 @@ impl Ashell {
cx.notify();
}
pub(crate) fn retry_connection_progress(&mut self, cx: &mut Context<Self>) {
let Some(progress) = self.connection_progress.clone() else {
return;
};
self.connection_progress = None;
let mut retry_tabs = Vec::new();
for (ix, tab) in self.tabs.iter().enumerate() {
if !tab.connected && tab.session.is_some() && tab.id == progress.tab_id {
retry_tabs.push((ix, tab.id.clone(), tab.session.clone().unwrap()));
}
}
if retry_tabs.is_empty() {
cx.notify();
return;
}
for (ix, tab_id, session) in retry_tabs {
// Close old backend
self.tabs[ix].send_backend(crate::terminal::BackendCommand::Close);
// Spawn new backend
let backend = crate::backend::ssh::spawn_ssh_terminal(
self.runtime.handle(),
tab_id.clone(),
session.clone(),
self.tabs[ix].cols,
self.tabs[ix].rows,
self.events_tx.clone(),
);
// Replace tab state
self.tabs[ix].set_backend(backend);
self.tabs[ix].connected = false;
self.tabs[ix].status = "connecting".into();
self.tabs[ix].disconnected_reason = None;
self.tabs[ix].backend_initialized = false;
// Restart SFTP for the group containing this tab
if let Some(group) = self
.tab_groups
.iter()
.find(|g| g.pane_root.contains(&tab_id))
{
let group_id = group.id.clone();
let group_session = self
.tabs
.iter()
.find(|t| group.pane_root.contains(&t.id) && t.session.is_some())
.and_then(|t| t.session.clone());
if let Some(session) = group_session {
if let Some(old_handle) = self.sftp_handles.remove(&group_id) {
old_handle.close();
}
let sftp_handle = crate::sftp::spawn_sftp(
self.runtime.handle(),
group_id.clone(),
session,
self.events_tx.clone(),
);
self.sftp_handles.insert(group_id.clone(), sftp_handle);
if let Some(group) = self.tab_groups.iter_mut().find(|g| g.id == group_id) {
if let Some(sftp) = group.sftp.as_mut() {
sftp.status = rust_i18n::t!("sftp_connecting").to_string();
}
}
}
}
}
self.connection_progress = Some(ConnectionProgress {
tab_id: progress.tab_id.clone(),
title: t!("connecting").into(),
lines: vec![t!("starting_connection").into()],
failed: false,
});
self.status = "ssh tabs retrying".into();
cx.notify();
}
pub(crate) fn cancel_connection_progress(&mut self, cx: &mut Context<Self>) {
if let Some(progress) = &self.connection_progress {
let tab_id = progress.tab_id.clone();
self.connection_progress = None;
self.handle_tab_close(tab_id);
}
cx.notify();
}
pub(crate) fn save_layout_state(&self, window: &mut gpui::Window, cx: &gpui::App) {
if self.is_layout_reset {
tracing::info!("[ui] layout was reset, skipping save layout state.");
+109
View File
@@ -2903,6 +2903,115 @@ impl Render for Ashell {
),
)
})
.when_some(self.connection_progress.clone(), |this, progress| {
this.child(
div()
.absolute()
.top_0()
.left_0()
.right_0()
.bottom_0()
.bg(gpui::Hsla {
h: 0.0,
s: 0.0,
l: 0.0,
a: 0.48,
})
.flex()
.items_center()
.justify_center()
.child(
div()
.w(px(420.))
.p_5()
.rounded_lg()
.border_1()
.border_color(cx.theme().border)
.bg(cx.theme().popover)
.shadow_lg()
.child(
v_flex()
.gap_4()
.child(
Button::new("ssh-connect-progress")
.primary()
.loading(!progress.failed)
.label(progress.title.clone()),
)
.child(
div()
.relative()
.min_h(px(0.))
.max_h(px(220.))
.child(
div()
.id("connection-progress-scroll")
.max_h(px(220.))
.overflow_hidden()
.overflow_y_scroll()
.track_scroll(&self.connection_scroll_handle)
.child(
v_flex().gap_2().children(
progress.lines.iter().cloned().map(|line| {
div()
.text_size(rems(1.0))
.text_color(if progress.failed {
cx.theme().danger
} else {
cx.theme().muted_foreground
})
.child(line)
}),
),
)
)
.child(
div()
.absolute()
.top_0()
.right_0()
.bottom_0()
.w(px(16.))
.child(
Scrollbar::vertical(&self.connection_scroll_handle)
.scrollbar_show(ScrollbarShow::Scrolling)
)
)
)
.when(progress.failed, |this| {
this.child(
h_flex()
.justify_end()
.gap_2()
.child(
Button::new("ssh-connect-progress-retry")
.primary()
.label(t!("retry").to_string())
.on_click(cx.listener(
|this, _, _, cx| {
this.retry_connection_progress(
cx,
)
},
)),
)
.child(
Button::new("ssh-connect-progress-close")
.label(t!("cancel").to_string())
.on_click(cx.listener(
|this, _, _, cx| {
this.cancel_connection_progress(
cx,
)
},
)),
),
)
}),
),
),
)
})
.on_prepaint({
let view = cx.entity().clone();
move |_, window, cx| {
+6
View File
@@ -494,6 +494,12 @@ impl Ashell {
self.events_tx.clone(),
));
self.active_tab = Some(id.clone());
self.connection_progress = Some(crate::app::ConnectionProgress {
tab_id: id.clone(),
title: rust_i18n::t!("connecting").into(),
lines: vec![rust_i18n::t!("starting_connection").into()],
failed: false,
});
self.pane_root = PaneLayout::Single(id.clone());
self.focused_pane_path = vec![];
let group_id = Uuid::new_v4().to_string();
+9
View File
@@ -119,6 +119,15 @@ impl Ashell {
&& !event.keystroke.modifiers.alt
&& !event.keystroke.modifiers.platform
{
if let Some(progress) = &self.connection_progress {
if progress.failed {
self.retry_connection_progress(cx);
window.prevent_default();
cx.stop_propagation();
return;
}
}
let active_id = self.active_tab.clone();
if let Some(active_id) = active_id {
let is_disconnected = self