fix: reveal newly focused spaces in the sidebar (#3554)

This commit is contained in:
Can Celik
2026-09-03 02:47:48 +03:00
committed by GitHub
parent 2ae8b91ca5
commit 94f6d9c0d9
6 changed files with 104 additions and 1 deletions
+1
View File
@@ -57,6 +57,7 @@ impl ClientShellState {
workspace_scroll: &mut self.workspace_scroll,
agent_scroll: &mut self.agent_scroll,
tab_scroll: &mut self.tab_scroll,
reveal_focused_workspace: &mut self.reveal_focused_workspace,
reveal_focused_tab: &mut self.reveal_focused_tab,
sidebar_collapsed: self.sidebar_collapsed,
sidebar_section_split: self.sidebar_section_split,
+1
View File
@@ -200,6 +200,7 @@ pub(super) struct ShellRenderState<'a> {
pub(super) workspace_scroll: &'a mut usize,
pub(super) agent_scroll: &'a mut usize,
pub(super) tab_scroll: &'a mut usize,
pub(super) reveal_focused_workspace: &'a mut bool,
pub(super) reveal_focused_tab: &'a mut bool,
pub(super) sidebar_collapsed: bool,
pub(super) sidebar_section_split: f32,
+23
View File
@@ -53,6 +53,29 @@ pub(super) fn list_scroll_metrics(
}
}
pub(super) fn list_scroll_start_to_reveal(
row_heights: &[u16],
gaps_after: &[u16],
body_height: u16,
requested_start: usize,
target: usize,
) -> usize {
let mut metrics = list_scroll_metrics(row_heights, gaps_after, body_height, requested_start);
let mut start = metrics
.max_offset_from_bottom
.saturating_sub(metrics.offset_from_bottom);
if target < start {
return target;
}
while target >= start.saturating_add(metrics.viewport_rows)
&& start < metrics.max_offset_from_bottom
{
start = start.saturating_add(1);
metrics = list_scroll_metrics(row_heights, gaps_after, body_height, start);
}
start
}
pub(super) fn render_list_scrollbar(
buffer: &mut Buffer,
track: Rect,
+21 -1
View File
@@ -246,12 +246,32 @@ pub(crate) fn render_sidebar(
.map_or(0, |next| u16::from(!next.indented) * config.spaces.row_gap)
})
.collect::<Vec<_>>();
let metrics = super::scroll::list_scroll_metrics(
let mut metrics = super::scroll::list_scroll_metrics(
&row_heights,
&gaps,
body.height,
*state.workspace_scroll,
);
if !body.is_empty() && std::mem::take(state.reveal_focused_workspace) {
if let Some(target) = entries
.iter()
.position(|entry| snapshot.workspaces[entry.index].focused)
{
*state.workspace_scroll = super::scroll::list_scroll_start_to_reveal(
&row_heights,
&gaps,
body.height,
*state.workspace_scroll,
target,
);
metrics = super::scroll::list_scroll_metrics(
&row_heights,
&gaps,
body.height,
*state.workspace_scroll,
);
}
}
hits.workspace_max_scroll = metrics.max_offset_from_bottom;
hits.workspace_scroll_metrics = Some(metrics);
*state.workspace_scroll = metrics
+11
View File
@@ -880,6 +880,7 @@ pub(crate) struct ClientShellState {
pub(super) agent_scroll: usize,
pub(super) tab_scroll: usize,
pub(super) mobile_switcher_scroll: usize,
pub(super) reveal_focused_workspace: bool,
pub(super) reveal_mobile_workspace: bool,
pub(super) mobile_switcher_suspended: bool,
pub(super) reveal_focused_tab: bool,
@@ -1021,6 +1022,7 @@ impl ClientShellState {
agent_scroll: 0,
tab_scroll: 0,
mobile_switcher_scroll: 0,
reveal_focused_workspace: true,
reveal_mobile_workspace: false,
mobile_switcher_suspended: false,
reveal_focused_tab: true,
@@ -1242,6 +1244,7 @@ impl ClientShellState {
self.agent_scroll = 0;
self.tab_scroll = 0;
self.mobile_switcher_scroll = 0;
self.reveal_focused_workspace = true;
self.reveal_mobile_workspace = false;
self.mobile_switcher_suspended = false;
self.reveal_focused_tab = true;
@@ -1317,6 +1320,14 @@ impl ClientShellState {
})
|| render::tab_bar_status_width(current) != render::tab_bar_status_width(&snapshot)
});
if self
.snapshot
.as_deref()
.and_then(|current| current.focused_workspace_id.as_deref())
!= snapshot.focused_workspace_id.as_deref()
{
self.reveal_focused_workspace = true;
}
if tab_layout_changed
|| self
.snapshot
+47
View File
@@ -46,6 +46,53 @@ fn tab_overflow_controls_scroll_the_client_owned_tab_bar() {
assert!(state.hits.tabs.iter().any(|(_, tab_id)| tab_id == "tab_8"));
}
#[test]
fn focused_workspace_change_reveals_new_workspace_in_full_sidebar() {
let mut initial = snapshot();
let template = initial.workspaces[0].clone();
initial.workspaces = (1..=12)
.map(|number| ClientShellWorkspace {
workspace_id: format!("ws_{number}"),
number,
label: format!("space-{number}"),
branch: None,
focused: number == 1,
..template.clone()
})
.collect();
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
state.set_snapshot(Box::new(initial));
state.set_pane_surface(surface());
state.compose(106, 20).expect("full sidebar");
assert!(state.hits.workspace_max_scroll > 0);
assert!(state
.hits
.workspaces
.iter()
.all(|hit| hit.workspace_id != "ws_12"));
let mut update = state.snapshot.as_deref().expect("snapshot").clone();
update.revision = 2;
update.focused_workspace_id = Some("ws_12".into());
for workspace in &mut update.workspaces {
workspace.focused = workspace.workspace_id == "ws_12";
}
let mut updated_surface = surface();
updated_surface.projection_revision = 2;
state.set_snapshot(Box::new(update));
state.set_pane_surface(updated_surface);
state.compose(106, 2).expect("zero-height workspace body");
assert!(state.reveal_focused_workspace);
state.compose(106, 20).expect("updated full sidebar");
assert!(state
.hits
.workspaces
.iter()
.any(|hit| hit.workspace_id == "ws_12"));
}
#[test]
fn client_owned_sidebar_dividers_resize_live() {
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));