Skip to content

Commit bd7202f

Browse files
authored
Fix file tree refresh logic (#10184)
## Description Fix local file tree blinking and reshuffling after connecting to an SSH session. **Root cause:** When a remote SSH session is active, the remote server streams frequent `FileTreeEntryUpdated { Remote }` events for every filesystem change on the remote host. The previous code called `rebuild_flattened_items()` on each such event, which re-flattened *all* roots (including local ones), causing the local file tree to re-render on every remote filesystem change — visible as constant blinking/reshuffling. **Fix:** Add an optional `target_root` parameter to the core `rebuild_flatten_items_impl` method. When `Some`, only the specified root is re-flattened; all other roots keep their existing items untouched. Three thin wrappers provide the public API: - `rebuild_flattened_items_for_root(path)` — single-root rebuild - `rebuild_flattened_items()` — full rebuild (all roots) - `rebuild_flattened_items_without(path)` — full rebuild excluding a deleted path Updated the following event handlers to use per-root rebuilds instead of full rebuilds: - `FileTreeEntryUpdated { Local }` — rebuilds only the affected local root(s) - `FileTreeEntryUpdated { Remote }` — rebuilds only the affected remote root - `RepositoryRemoved { Remote }` — no-op rebuild (root already removed), avoids touching remaining roots Skipping unchanged roots is safe because the rebuild is fully deterministic given the same `entry`, `expanded_folders`, and `item_states` — re-flattening an unmodified root produces an identical items list and selection index. ## Testing - `cargo check -p warp --lib` passes cleanly - Verified the three updated event handlers and the rename flow (editing.rs) all route through `rebuild_flatten_items_impl` correctly ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode <!-- CHANGELOG-BUG-FIX: Fixed local file tree blinking/reshuffling when connected to an SSH session -->
1 parent 74bdbd1 commit bd7202f

2 files changed

Lines changed: 44 additions & 14 deletions

File tree

app/src/code/file_tree/view.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -549,13 +549,15 @@ impl FileTreeView {
549549
if !root_paths.is_empty() {
550550
let id = RepositoryIdentifier::Local(std_path.clone());
551551
if let Some(state) = RepoMetadataModel::as_ref(ctx).get_repository(&id, ctx) {
552-
for root_path in root_paths {
553-
if let Some(root_dir) = self.root_directories.get_mut(&root_path) {
552+
for root_path in &root_paths {
553+
if let Some(root_dir) = self.root_directories.get_mut(root_path) {
554554
root_dir.entry = state.entry.clone();
555555
}
556556
}
557557

558-
self.rebuild_flattened_items();
558+
for root_path in &root_paths {
559+
self.rebuild_flattened_items_for_root(root_path);
560+
}
559561
self.apply_pending_focus_target();
560562
ctx.notify();
561563
}
@@ -600,7 +602,11 @@ impl FileTreeView {
600602
if let Some(root_dir) = self.root_directories.get_mut(&repo_path) {
601603
root_dir.entry = state.entry.clone();
602604
}
603-
self.rebuild_flattened_items();
605+
// Only rebuild the affected remote root instead of all roots.
606+
// Remote servers stream frequent incremental updates; a full
607+
// rebuild would cause unrelated local roots to re-render on
608+
// every remote filesystem change, leading to visible flicker.
609+
self.rebuild_flattened_items_for_root(&repo_path);
604610
ctx.notify();
605611
}
606612
}
@@ -610,7 +616,10 @@ impl FileTreeView {
610616
let repo_path = &remote_id.path;
611617
self.displayed_directories.retain(|p| p != repo_path);
612618
self.root_directories.remove(repo_path);
613-
self.rebuild_flattened_items();
619+
// The removed root is already gone from root_directories, so
620+
// this is effectively a no-op rebuild that avoids touching
621+
// the remaining roots' flattened items.
622+
self.rebuild_flattened_items_for_root(repo_path);
614623
ctx.notify();
615624
}
616625
RepoMetadataEvent::FileTreeUpdated { .. }
@@ -1594,32 +1603,53 @@ impl FileTreeView {
15941603
});
15951604
}
15961605

1606+
/// Rebuilds the flattened items list for a single root directory only,
1607+
/// leaving all other roots untouched. Use this when only one root's
1608+
/// backing data has changed (e.g. a metadata update) to avoid
1609+
/// unnecessarily re-flattening — and re-rendering — unrelated roots.
1610+
fn rebuild_flattened_items_for_root(&mut self, target_root: &StandardizedPath) {
1611+
self.rebuild_flatten_items_impl(None, None, Some(target_root));
1612+
}
1613+
15971614
/// Rebuilds the flattened items list from the current entry tree, optionally removing an item.
15981615
fn rebuild_flattened_items(&mut self) {
1599-
self.rebuild_flatten_items_and_select_path(None, None);
1616+
self.rebuild_flatten_items_impl(None, None, None);
16001617
}
16011618

16021619
fn rebuild_flattened_items_without(&mut self, path_to_remove: &StandardizedPath) -> bool {
1603-
self.rebuild_flatten_items_and_select_path(None, Some(path_to_remove))
1620+
self.rebuild_flatten_items_impl(None, Some(path_to_remove), None)
16041621
}
16051622

1606-
/// Rebuilds the flattened items list from the current entry tree
1607-
/// If `id_to_select` is `Some`, the item identified by that FileTreeIdentifier will be selected.
1608-
/// If `path_to_remove` is `Some`, the item identified by `path_to_remove` will be removed
1609-
/// upon rebuilding.
1623+
/// Core implementation for rebuilding the flattened items list.
1624+
///
1625+
/// When `target_root` is `Some`, only that root is re-flattened; all
1626+
/// other roots keep their existing items. When `None`, every displayed
1627+
/// root is rebuilt.
1628+
///
1629+
/// If `id_to_select` is `Some`, the item identified by that
1630+
/// `FileTreeIdentifier` will be selected. If `path_to_remove` is
1631+
/// `Some`, the item at that path will be excluded from the result.
1632+
///
16101633
/// Returns `true` if an item was removed.
1611-
fn rebuild_flatten_items_and_select_path(
1634+
fn rebuild_flatten_items_impl(
16121635
&mut self,
16131636
id_to_select: Option<&FileTreeIdentifier>,
16141637
path_to_remove: Option<&StandardizedPath>,
1638+
target_root: Option<&StandardizedPath>,
16151639
) -> bool {
16161640
let mut any_item_removed = false;
16171641

16181642
// Clone the ID to preserve so we don't hold a borrow on self.selected_item
16191643
let id_to_preserve = id_to_select.cloned().or_else(|| self.selected_item.clone());
16201644

1621-
// Process all displayed directories
1645+
// Process displayed directories, optionally filtering to a single root.
16221646
for root_path in self.displayed_directories.clone() {
1647+
if let Some(target) = target_root {
1648+
if root_path != *target {
1649+
continue;
1650+
}
1651+
}
1652+
16231653
let Some(root_dir) = self.root_directories.get(&root_path) else {
16241654
continue;
16251655
};

app/src/code/file_tree/view/editing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ impl FileTreeView {
240240
});
241241

242242
// Rebuild and select the renamed item using its FileTreeIdentifier
243-
self.rebuild_flatten_items_and_select_path(Some(&file_tree_id), None);
243+
self.rebuild_flatten_items_impl(Some(&file_tree_id), None, None);
244244
ctx.notify();
245245
}
246246
}

0 commit comments

Comments
 (0)