fix: allow file tree to scroll

This commit is contained in:
2026-06-04 23:20:23 +01:00
parent b8971027ac
commit 39093f3b69
2 changed files with 69 additions and 31 deletions

View File

@@ -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 {

View File

@@ -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<RefCell<FileTreeStateInner>>);
struct FileTreeStateInner {
list_state: gpui::ListState,
scroll_handle: UniformListScrollHandle,
collapsed_dirs: HashSet<Arc<str>>,
visible_items: Vec<usize>,
highlighted_items: HashSet<usize>,
max_width_item_index: Option<usize>,
}
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::<Vec<_>>();
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(),
)
}
}