mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-21 08:01:05 +00:00
perf: index navigator tabs and panes per endpoint (#4426)
This commit is contained in:
@@ -304,23 +304,39 @@ pub(super) fn navigator_rows(
|
||||
.iter()
|
||||
.map(|agent| (agent.pane_id.as_str(), agent))
|
||||
.collect::<HashMap<_, _>>();
|
||||
// Build endpoint-local indexes once. Walk each bucket in snapshot
|
||||
// order so interleaved input and overlapping IDs on other endpoints
|
||||
// retain their existing navigation order and targets.
|
||||
let mut tabs_by_workspace = HashMap::new();
|
||||
for tab in &snapshot.tabs {
|
||||
tabs_by_workspace
|
||||
.entry(tab.workspace_id.as_str())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(tab);
|
||||
}
|
||||
let mut panes_by_tab = HashMap::new();
|
||||
for pane in &snapshot.panes {
|
||||
panes_by_tab
|
||||
.entry(pane.tab_id.as_str())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(pane);
|
||||
}
|
||||
for workspace in &snapshot.workspaces {
|
||||
let workspace_matches = endpoint_query_matches
|
||||
|| text(&workspace.label)
|
||||
|| workspace.branch.as_deref().is_some_and(text);
|
||||
let mut children = Vec::new();
|
||||
let workspace_tabs = snapshot
|
||||
.tabs
|
||||
.iter()
|
||||
.filter(|tab| tab.workspace_id == workspace.workspace_id);
|
||||
let multiple_tabs = workspace_tabs.clone().nth(1).is_some();
|
||||
let workspace_tabs = tabs_by_workspace
|
||||
.get(workspace.workspace_id.as_str())
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or_default();
|
||||
let multiple_tabs = workspace_tabs.len() > 1;
|
||||
for tab in workspace_tabs {
|
||||
let tab_matches = workspace_matches || text(&tab.label);
|
||||
let tab_panes = snapshot
|
||||
.panes
|
||||
.iter()
|
||||
.filter(|pane| pane.tab_id == tab.tab_id)
|
||||
.collect::<Vec<_>>();
|
||||
let tab_panes = panes_by_tab
|
||||
.get(tab.tab_id.as_str())
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or_default();
|
||||
for (index, pane) in tab_panes.iter().enumerate() {
|
||||
let agent = agents.get(pane.pane_id.as_str()).copied();
|
||||
let status = agent
|
||||
|
||||
+131
-22
@@ -1534,32 +1534,141 @@ fn navigator_narrow_layout_and_long_search_stay_inside_the_popup() {
|
||||
}
|
||||
}
|
||||
|
||||
fn navigator_scale_snapshot(workspaces: usize, tabs: usize, panes: usize) -> ClientShellSnapshot {
|
||||
let mut result = snapshot();
|
||||
let workspace_template = result.workspaces[0].clone();
|
||||
let tab_template = result.tabs[0].clone();
|
||||
let pane_template = result.panes[0].clone();
|
||||
result.workspaces.clear();
|
||||
result.tabs.clear();
|
||||
result.panes.clear();
|
||||
for w in 0..workspaces {
|
||||
let mut workspace = workspace_template.clone();
|
||||
workspace.workspace_id = format!("workspace_{w}");
|
||||
workspace.active_tab_id = format!("tab_{w}_0");
|
||||
workspace.number = w + 1;
|
||||
workspace.label = format!("workspace {w}");
|
||||
workspace.focused = w == 0;
|
||||
for t in 0..tabs {
|
||||
let mut tab = tab_template.clone();
|
||||
tab.workspace_id = workspace.workspace_id.clone();
|
||||
tab.tab_id = format!("tab_{w}_{t}");
|
||||
tab.number = t + 1;
|
||||
tab.label = format!("tab {t}");
|
||||
tab.focused = w == 0 && t == 0;
|
||||
for p in 0..panes {
|
||||
let mut pane = pane_template.clone();
|
||||
pane.workspace_id = workspace.workspace_id.clone();
|
||||
pane.tab_id = tab.tab_id.clone();
|
||||
pane.pane_id = format!("pane_{w}_{t}_{p}");
|
||||
pane.label = Some(format!("terminal {p}"));
|
||||
pane.focused = w == 0 && t == 0 && p == 0;
|
||||
result.panes.push(pane);
|
||||
}
|
||||
result.tabs.push(tab);
|
||||
}
|
||||
result.workspaces.push(workspace);
|
||||
}
|
||||
result.focused_workspace_id = Some(result.workspaces[0].workspace_id.clone());
|
||||
result.focused_tab_id = Some(result.tabs[0].tab_id.clone());
|
||||
result.focused_pane_id = Some(result.panes[0].pane_id.clone());
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigator_grouping_keeps_snapshot_order_with_interleaved_tabs_and_panes() {
|
||||
let mut snapshot = navigator_scale_snapshot(2, 2, 2);
|
||||
snapshot.tabs.swap(1, 2);
|
||||
snapshot.panes.reverse();
|
||||
let expected = snapshot
|
||||
.workspaces
|
||||
.iter()
|
||||
.flat_map(|workspace| {
|
||||
snapshot
|
||||
.tabs
|
||||
.iter()
|
||||
.filter(|tab| tab.workspace_id == workspace.workspace_id)
|
||||
.flat_map(|tab| {
|
||||
snapshot
|
||||
.panes
|
||||
.iter()
|
||||
.filter(|pane| pane.tab_id == tab.tab_id)
|
||||
.map(|pane| pane.pane_id.clone())
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
|
||||
let remote = SavedSshEndpoint::new("Remote", "dev@example.invalid", "test").unwrap();
|
||||
let remote_id = ClientEndpointId::Ssh(remote.id.clone());
|
||||
state.set_endpoint_catalog(&[remote]);
|
||||
state.set_endpoint_status(&remote_id, ClientEndpointStatus::Online);
|
||||
state.set_endpoint_snapshot(&remote_id, Box::new(snapshot.clone()));
|
||||
state.set_snapshot(Box::new(snapshot));
|
||||
state.open_navigator_overlay();
|
||||
let Some(ClientShellOverlay::Navigator(navigator)) = state.overlay.as_ref() else {
|
||||
panic!("navigator")
|
||||
};
|
||||
let rows =
|
||||
render::client_navigator_rows(&state.endpoints, &state.active_endpoint_id, navigator);
|
||||
let actual = rows
|
||||
.iter()
|
||||
.filter_map(|row| match &row.target {
|
||||
ClientNavigatorTarget::Pane {
|
||||
endpoint_id,
|
||||
pane_id,
|
||||
} => {
|
||||
assert!(endpoint_id == &state.active_endpoint_id || endpoint_id == &remote_id);
|
||||
Some((endpoint_id.clone(), pane_id.clone()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let expected = [state.active_endpoint_id.clone(), remote_id]
|
||||
.into_iter()
|
||||
.flat_map(|endpoint| {
|
||||
expected
|
||||
.iter()
|
||||
.map(move |pane| (endpoint.clone(), pane.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual navigator composition scaling profile"]
|
||||
fn navigator_render_scale_profile() {
|
||||
for panes in [1, 15, 52] {
|
||||
let mut snapshot = snapshot();
|
||||
for index in 1..panes {
|
||||
let mut pane = snapshot.panes[0].clone();
|
||||
pane.pane_id = format!("pane_{index}_extra");
|
||||
pane.focused = false;
|
||||
snapshot.panes.push(pane);
|
||||
for (workspaces, tabs, panes) in [
|
||||
(1, 1, 1),
|
||||
(1, 1, 15),
|
||||
(1, 1, 52),
|
||||
(1, 1, 512),
|
||||
(1, 128, 4),
|
||||
(64, 2, 4),
|
||||
(128, 4, 1),
|
||||
] {
|
||||
for query in ["", "terminal 0"] {
|
||||
let mut state =
|
||||
ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
|
||||
state.set_snapshot(Box::new(navigator_scale_snapshot(workspaces, tabs, panes)));
|
||||
let mut pane_surface = surface();
|
||||
pane_surface.panes[0].pane_id = "pane_0_0_0".into();
|
||||
state.set_pane_surface(pane_surface);
|
||||
state.open_navigator_overlay();
|
||||
if let Some(ClientShellOverlay::Navigator(navigator)) = state.overlay.as_mut() {
|
||||
navigator.query = query.into();
|
||||
}
|
||||
for _ in 0..20 {
|
||||
std::hint::black_box(state.compose(106, 30).expect("navigator frame"));
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..1000 {
|
||||
std::hint::black_box(state.compose(106, 30).expect("navigator frame"));
|
||||
}
|
||||
eprintln!(
|
||||
"navigator: {workspaces}x{tabs}x{panes}, query={query:?}, {:.1} us/frame",
|
||||
start.elapsed().as_secs_f64() * 1000.0
|
||||
);
|
||||
}
|
||||
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
|
||||
state.set_snapshot(Box::new(snapshot));
|
||||
state.set_pane_surface(surface());
|
||||
state.open_navigator_overlay();
|
||||
for _ in 0..20 {
|
||||
std::hint::black_box(state.compose(106, 30).expect("navigator frame"));
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..1000 {
|
||||
std::hint::black_box(state.compose(106, 30).expect("navigator frame"));
|
||||
}
|
||||
eprintln!(
|
||||
"navigator: {panes} panes, {:.1} us/frame",
|
||||
start.elapsed().as_secs_f64() * 1000.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user