feat: enhance split pane focus UI and fix graceful exit tab cleanup

- Implement 4-sided inner absolute border for active pane focus

- Fix 'exit' gracefully closing tabs not removing empty tab groups

- Sync system monitoring automatically on active group switches
This commit is contained in:
TomZz
2026-06-13 23:25:54 +08:00
parent 8193223dc2
commit 0ccbf9573b
3 changed files with 32 additions and 60 deletions
+1 -39
View File
@@ -639,45 +639,7 @@ impl Ashell {
|| reason == "ssh session closed";
// Auto-close the pane on graceful exit (e.g. user typed exit)
if is_graceful_exit {
if let Some(ix) = self.tabs.iter().position(|t| t.id == tab_id) {
self.tabs[ix].backend.send(BackendCommand::Close);
self.tabs.remove(ix);
}
if let Some(g) = self.tab_groups.iter_mut().find(|g| g.pane_root.contains(&tab_id)) {
g.pane_root.remove_tab(&tab_id);
}
self.pane_root.remove_tab(&tab_id);
self.sync_pane_root_to_group();
if self.tabs.is_empty() || self.tab_groups.is_empty() {
self.pane_root = PaneLayout::Single(String::new());
self.focused_pane_path = vec![];
self.active_tab = None;
self.active_group = None;
self.tab_groups.clear();
self.tabs.clear();
self.system_tab_id = None;
self.cpu_history.clear();
self.net_rx_history.clear();
self.net_tx_history.clear();
self.system_status = None;
if let Some(handle) = self.sftp_handles.remove(&tab_id) {
handle.close();
}
return changed;
}
let was_active = self.active_tab.as_deref() == Some(tab_id.as_str());
if was_active
|| self.active_tab.as_ref().is_some_and(|active_id| !self.tabs.iter().any(|tab| &tab.id == active_id))
{
let first_id = self.pane_root.tab_ids().first().copied().map(String::from)
.or_else(|| self.tabs.first().map(|t| t.id.clone()));
if let Some(new_id) = first_id {
self.active_tab = Some(new_id.clone());
self.focus_pane_with_id(new_id);
}
}
self.sync_system_tab_to_active_group();
self.handle_tab_close(tab_id.clone());
self.status = reason.into();
self.remote_sample_in_flight = false;
return changed;
+24 -16
View File
@@ -1639,23 +1639,31 @@ impl Ashell {
}
})
.unwrap_or(cx.theme().success);
if is_focused {
el = h_flex()
.size_full()
.child(
div()
.w(px(2.))
.h_full()
.flex_none()
.bg(indicator_color),
)
.child(el.flex_1().min_w(px(0.)));
} else {
el = h_flex()
.size_full()
.child(div().w(px(2.)).h_full().flex_none())
.child(el.opacity(0.85).flex_1().min_w(px(0.)));
let has_multiple_panes = this.pane_root.tab_ids().len() > 1;
if !is_focused {
el = el.opacity(0.85);
}
if has_multiple_panes {
if is_focused {
el = div()
.size_full()
.relative()
.child(div().absolute().top(px(1.)).left(px(1.)).right(px(1.)).h(px(1.)).bg(indicator_color))
.child(div().absolute().bottom(px(1.)).left(px(1.)).right(px(1.)).h(px(1.)).bg(indicator_color))
.child(div().absolute().left(px(1.)).top(px(1.)).bottom(px(1.)).w(px(1.)).bg(indicator_color))
.child(div().absolute().right(px(1.)).top(px(1.)).bottom(px(1.)).w(px(1.)).bg(indicator_color))
.p(px(4.))
.child(el);
} else {
el = div()
.size_full()
.p(px(4.))
.child(el);
}
}
el.into_any_element()
}
PaneLayout::Horizontal(children, ratio) => {
+7 -5
View File
@@ -488,15 +488,19 @@ impl Ashell {
}
pub(crate) fn close_tab(&mut self, id: String, cx: &mut Context<Self>) {
self.handle_tab_close(id);
cx.notify();
}
pub(crate) fn handle_tab_close(&mut self, id: String) {
let group_ix = self.tab_groups.iter().position(|g| g.pane_root.contains(&id));
let Some(ref group) = group_ix.map(|i| self.tab_groups[i].clone()) else {
// Fallback: find and close individual tab
eprintln!("[close_tab] no group found for tab '{}', closing individually", id);
eprintln!("[handle_tab_close] no group found for tab '{}', closing individually", id);
if let Some(ix) = self.tabs.iter().position(|tab| tab.id == id) {
self.tabs[ix].backend.send(BackendCommand::Close);
self.tabs.remove(ix);
}
cx.notify();
return;
};
@@ -504,7 +508,7 @@ impl Ashell {
let pane_ids_str: Vec<&str> = pane_ids.iter().map(|s| *s).collect();
let is_group_close = pane_ids.len() <= 1;
eprintln!(
"[close_tab] id='{}' group_panes={:?} is_group_close={}",
"[handle_tab_close] id='{}' group_panes={:?} is_group_close={}",
id, pane_ids_str, is_group_close
);
@@ -573,7 +577,6 @@ impl Ashell {
for (_, handle) in self.sftp_handles.drain() {
handle.close();
}
cx.notify();
return;
}
@@ -606,7 +609,6 @@ impl Ashell {
}
}
self.sync_system_tab_to_active_group();
cx.notify();
}
pub(crate) fn focus_terminal(