From 39093f3b69935c84c91bb9b78d9fd96824fa8687 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Thu, 4 Jun 2026 23:20:23 +0100 Subject: [PATCH] fix: allow file tree to scroll --- src/component/diff_view.rs | 7 +++ src/component/file_tree.rs | 93 +++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/component/diff_view.rs b/src/component/diff_view.rs index 9ba48c4..7d7f643 100644 --- a/src/component/diff_view.rs +++ b/src/component/diff_view.rs @@ -120,6 +120,13 @@ impl DiffViewState { ) { self.0.borrow_mut().new_side_highlights = Some(highlights); } + + pub(crate) fn scroll_to_diff_line(&mut self, line_i: util::diff::DiffLineIndex) { + self.0 + .borrow_mut() + .list_state + .scroll_to_reveal_item(line_i.into()); + } } impl gpui::RenderOnce for DiffView { diff --git a/src/component/file_tree.rs b/src/component/file_tree.rs index 01b823c..4dcaa73 100644 --- a/src/component/file_tree.rs +++ b/src/component/file_tree.rs @@ -1,8 +1,9 @@ use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc}; use gpui::{ - InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div, list, - prelude::FluentBuilder, px, + InteractiveElement, IntoElement, ListHorizontalSizingBehavior, ListSizingBehavior, + ParentElement, StatefulInteractiveElement, Styled, UniformListScrollHandle, div, + prelude::FluentBuilder, px, uniform_list, }; use crate::{ @@ -11,10 +12,7 @@ use crate::{ font_icon::{FontIcon, font_icon}, text::text, }, - util::{ - self, - file::{FileTreeItem, FileTreeItemKind}, - }, + util::{self, file::FileTreeItemKind}, }; #[derive(gpui::IntoElement)] @@ -36,10 +34,11 @@ pub(crate) struct Item { pub(crate) struct FileTreeState(Rc>); struct FileTreeStateInner { - list_state: gpui::ListState, + scroll_handle: UniformListScrollHandle, collapsed_dirs: HashSet>, visible_items: Vec, highlighted_items: HashSet, + max_width_item_index: Option, } pub(crate) fn file_tree( @@ -56,17 +55,18 @@ pub(crate) fn file_tree( impl FileTreeState { pub(crate) fn new() -> Self { Self(Rc::new(RefCell::new(FileTreeStateInner { - list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(50.)), + scroll_handle: UniformListScrollHandle::new(), collapsed_dirs: HashSet::new(), visible_items: Vec::new(), highlighted_items: HashSet::new(), + max_width_item_index: None, }))) } pub(crate) fn reset(&self, items: &[util::file::FileTreeItem]) { let mut state = self.0.borrow_mut(); - state.list_state.reset(items.len()); state.visible_items = (0..items.len()).collect::>(); + state.update_max_width_item_index(items); } pub(crate) fn highlight_item(&mut self, index: usize) { @@ -104,7 +104,21 @@ impl FileTreeState { state.visible_items.push(i); } - state.list_state.reset(state.visible_items.len()); + state.update_max_width_item_index(items); + } +} + +impl FileTreeStateInner { + fn update_max_width_item_index(&mut self, items: &[util::file::FileTreeItem]) { + self.max_width_item_index = self + .visible_items + .iter() + .enumerate() + .max_by_key(|(_, item_i)| { + let item = &items[**item_i]; + item.level.saturating_mul(2) + item.name.chars().count() + }) + .map(|(visible_i, _)| visible_i); } } @@ -124,28 +138,41 @@ impl gpui::RenderOnce for FileTree { _window: &mut gpui::Window, _cx: &mut gpui::App, ) -> impl gpui::prelude::IntoElement { - let list_state = self.state.0.borrow().list_state.clone(); + let state = self.state.0.borrow(); + let item_count = state.visible_items.len(); + let scroll_handle = state.scroll_handle.clone(); + let max_width_item_index = state.max_width_item_index; + drop(state); - list(list_state, move |i, window, cx| { - let state = self.state.0.borrow(); - let item_index = state.visible_items[i]; - let on_item_click = self.on_item_click.as_ref().map(Rc::clone); + uniform_list("file-tree", item_count, move |range, window, cx| { + range + .map(|i| { + let item_index = self.state.0.borrow().visible_items[i]; + let on_item_click = self.on_item_click.as_ref().map(Rc::clone); - let item = (self.item)(item_index, window, cx); - let item = Item { - is_expanded: !state.collapsed_dirs.contains(&item.full_path), - is_highlighed: state.highlighted_items.contains(&item_index), - item, - on_click: Box::new(move |window, cx| { - if let Some(f) = &on_item_click { - f(&i, window, cx); - } - }), - }; + let item = (self.item)(item_index, window, cx); + let state = self.state.0.borrow(); + let item = Item { + is_expanded: !state.collapsed_dirs.contains(&item.full_path), + is_highlighed: state.highlighted_items.contains(&item_index), + item, + on_click: Box::new(move |window, cx| { + if let Some(f) = &on_item_click { + f(&item_index, window, cx); + } + }), + }; - item.into_any_element() + item.into_any_element() + }) + .collect() }) - .size_full() + .h_full() + .min_w_full() + .with_sizing_behavior(ListSizingBehavior::Infer) + .with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained) + .with_width_from_item(max_width_item_index) + .track_scroll(scroll_handle) } } @@ -154,7 +181,7 @@ impl gpui::RenderOnce for Item { let theme = app::current_theme(cx); div() .id(gpui::SharedString::new(self.item.full_path)) - .w_full() + .min_w_full() .flex() .flex_row() .items_center() @@ -162,7 +189,6 @@ impl gpui::RenderOnce for Item { .pr_2() .py_0p5() .pl(px(8. + (8 * self.item.level) as f32)) - .rounded_sm() .hover(|it| { if self.is_highlighed { it @@ -191,6 +217,11 @@ impl gpui::RenderOnce for Item { it.bg(theme.colors.accent_muted) .text_color(theme.colors.accent_fg) }) - .child(text(gpui::SharedString::new(self.item.name)).text_sm()) + .child( + text(gpui::SharedString::new(self.item.name)) + .text_sm() + .whitespace_nowrap() + .flex_shrink_0(), + ) } }