perf(tui): flatten a browsed share once and draw one page of it

Moving through a browsed share felt laggy for the same reason the
results did: the popup re-flattened the whole expanded tree on every
frame, cloning every row's name and path, then built a table row for
each, and every keypress flattened it twice more. A heavy sharer's
tree runs to tens of thousands of rows once a few folders are open.

The flattened rows now live on the browse state and are rebuilt only
when a folder opens or closes, and the popup draws the visible page
alone. In the harness a 30,000-row tree went from 11ms to 0.35ms a
frame and a keypress from 2.7ms to under a microsecond.

The downloads pane had the same shape (every transfer became a row
each frame, and a select-all can queue ten thousand), so it takes the
same page window, which now lives beside page_of for every pane to
share.
This commit is contained in:
Michel de Graaf
2026-09-08 11:24:36 +02:00
parent 1fcde0a78b
commit 0931bb01b4
6 changed files with 129 additions and 68 deletions
+12 -20
View File
@@ -48,8 +48,8 @@ impl MainTui {
_ => {}
}
// Snapshot the flattened rows + current selection, then drop the borrow.
let (rows, sel, row) = {
// The highlighted row and where it sits, then drop the borrow.
let (len, sel, row) = {
let Some(browse) = self.state.browse.active_tab() else {
self.state.show_browse = false;
return;
@@ -62,13 +62,12 @@ impl MainTui {
return;
}
let sel = browse.selected_row.min(rows.len() - 1);
let row = rows[sel].clone();
(rows, sel, row)
(rows.len(), sel, rows[sel].clone())
};
if let Some(jump) = jump {
if let Some(browse) = self.state.browse.active_tab_mut() {
browse.selected_row = jumped(sel, rows.len(), jump);
browse.selected_row = jumped(sel, len, jump);
}
self.sync_browse_selection();
return;
@@ -102,38 +101,31 @@ impl MainTui {
browse.selected_row = sel.saturating_sub(1);
}
KeyCode::Down | KeyCode::Char('j') => {
browse.selected_row = (sel + 1).min(rows.len() - 1);
browse.selected_row = (sel + 1).min(len - 1);
}
KeyCode::Right | KeyCode::Char('l') => {
if row.is_folder && !row.expanded {
browse.expanded.insert(row.path.clone());
browse.set_expanded(&row.path, true);
} else if row.is_folder {
browse.selected_row = (sel + 1).min(rows.len() - 1);
browse.selected_row = (sel + 1).min(len - 1);
}
}
KeyCode::Left | KeyCode::Char('h') => {
if row.is_folder && row.expanded {
browse.expanded.remove(&row.path);
} else if let Some(parent) =
(0..sel).rev().find(|&i| rows[i].depth < row.depth)
browse.set_expanded(&row.path, false);
} else if let Some(parent) = (0..sel)
.rev()
.find(|&i| browse.rows()[i].depth < row.depth)
{
browse.selected_row = parent;
}
}
KeyCode::Enter => {
// Folder toggle (files handled above).
if row.expanded {
browse.expanded.remove(&row.path);
} else {
browse.expanded.insert(row.path.clone());
}
browse.set_expanded(&row.path, !row.expanded);
}
_ => {}
}
// Re-clamp against the new flattened length.
let new_len = browse.rows().len();
browse.selected_row =
browse.selected_row.min(new_len.saturating_sub(1));
}
self.sync_browse_selection();