Detailed changes
@@ -5,7 +5,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{self, json},
- ElementBox, MutableAppContext,
+ ElementBox,
};
use json::ToJson;
use parking_lot::Mutex;
@@ -38,7 +38,7 @@ pub struct LayoutState {
pub struct UniformList<F>
where
- F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
+ F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
state: UniformListState,
item_count: usize,
@@ -47,7 +47,7 @@ where
impl<F> UniformList<F>
where
- F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
+ F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
Self {
@@ -102,7 +102,7 @@ where
impl<F> Element for UniformList<F>
where
- F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
+ F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
type LayoutState = LayoutState;
type PaintState = ();
@@ -124,7 +124,7 @@ where
let mut scroll_max = 0.;
let mut items = Vec::new();
- (self.append_items)(0..1, &mut items, cx.app);
+ (self.append_items)(0..1, &mut items, cx);
if let Some(first_item) = items.first_mut() {
let mut item_size = first_item.layout(item_constraint, cx);
item_size.set_x(size.x());
@@ -146,7 +146,7 @@ where
self.item_count,
start + (size.y() / item_height).ceil() as usize + 1,
);
- (self.append_items)(start..end, &mut items, cx.app);
+ (self.append_items)(start..end, &mut items, cx);
for item in &mut items {
item.layout(item_constraint, cx);
}
@@ -7,7 +7,7 @@ use crate::{
platform::Event,
text_layout::TextLayoutCache,
Action, AnyAction, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, ReadModel,
- ReadView, Scene, View, ViewHandle,
+ ReadView, Scene, UpdateView, View, ViewHandle,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use serde_json::json;
@@ -264,6 +264,16 @@ impl<'a> ReadView for LayoutContext<'a> {
}
}
+impl<'a> UpdateView for LayoutContext<'a> {
+ fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
+ where
+ T: View,
+ F: FnOnce(&mut T, &mut crate::ViewContext<T>) -> S,
+ {
+ self.app.update_view(handle, update)
+ }
+}
+
impl<'a> ReadModel for LayoutContext<'a> {
fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
self.app.read_model(handle)
@@ -126,7 +126,7 @@ impl View for Select {
UniformList::new(
self.list_state.clone(),
self.item_count,
- move |mut range, items, mut cx| {
+ move |mut range, items, cx| {
let handle = handle.upgrade(cx).unwrap();
let this = handle.read(cx);
let selected_item_ix = this.selected_item_ix;
@@ -134,9 +134,9 @@ impl View for Select {
items.extend(range.map(|ix| {
MouseEventHandler::new::<Item, _, _, _>(
(handle.id(), ix),
- &mut cx,
+ cx,
|mouse_state, cx| {
- (handle.read(*cx).render_item)(
+ (handle.read(cx).render_item)(
ix,
if ix == selected_item_ix {
ItemType::Selected
@@ -278,29 +278,47 @@ pub async fn match_paths(
let start = max(tree_start, segment_start) - tree_start;
let end = min(tree_end, segment_end) - tree_start;
- let entries = if include_ignored {
- snapshot.files(start).take(end - start)
+ if include_ignored {
+ let paths = snapshot.files(start).take(end - start).map(|entry| {
+ if let EntryKind::File(char_bag) = entry.kind {
+ PathMatchCandidate {
+ path: &entry.path,
+ char_bag,
+ }
+ } else {
+ unreachable!()
+ }
+ });
+ matcher.match_paths(
+ snapshot.id(),
+ path_prefix,
+ paths,
+ results,
+ &cancel_flag,
+ );
} else {
- snapshot.visible_files(start).take(end - start)
+ let paths =
+ snapshot
+ .visible_files(start)
+ .take(end - start)
+ .map(|entry| {
+ if let EntryKind::File(char_bag) = entry.kind {
+ PathMatchCandidate {
+ path: &entry.path,
+ char_bag,
+ }
+ } else {
+ unreachable!()
+ }
+ });
+ matcher.match_paths(
+ snapshot.id(),
+ path_prefix,
+ paths,
+ results,
+ &cancel_flag,
+ );
};
- let paths = entries.map(|entry| {
- if let EntryKind::File(char_bag) = entry.kind {
- PathMatchCandidate {
- path: &entry.path,
- char_bag,
- }
- } else {
- unreachable!()
- }
- });
-
- matcher.match_paths(
- snapshot.id(),
- path_prefix,
- paths,
- results,
- &cancel_flag,
- );
}
if tree_end >= segment_end {
break;
@@ -13,7 +13,7 @@ use zed::{
channel::ChannelList,
chat_panel, editor, file_finder,
fs::RealFs,
- http, language, menus, rpc, settings, theme_selector,
+ http, language, menus, project_panel, rpc, settings, theme_selector,
user::UserStore,
workspace::{self, OpenNew, OpenParams, OpenPaths},
AppState,
@@ -55,6 +55,7 @@ fn main() {
editor::init(cx);
file_finder::init(cx);
chat_panel::init(cx);
+ project_panel::init(cx);
theme_selector::init(&app_state, cx);
cx.set_menus(menus::menus(&app_state.clone()));
@@ -1,19 +1,41 @@
-use crate::{
- project::Project,
- theme::Theme,
- worktree::{self, Worktree},
- Settings,
-};
+use crate::{project::Project, theme, Settings};
use gpui::{
- elements::{Empty, Label, List, ListState, Orientation},
- AppContext, Element, ElementBox, Entity, ModelHandle, View, ViewContext,
+ action,
+ elements::{Label, MouseEventHandler, UniformList, UniformListState},
+ Element, ElementBox, Entity, ModelHandle, MutableAppContext, ReadModel, View, ViewContext,
+ WeakViewHandle,
};
use postage::watch;
+use std::ops::Range;
pub struct ProjectPanel {
project: ModelHandle<Project>,
- list: ListState,
+ list: UniformListState,
+ visible_entries: Vec<Vec<usize>>,
+ expanded_dir_ids: Vec<Vec<usize>>,
settings: watch::Receiver<Settings>,
+ handle: WeakViewHandle<Self>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+struct EntryDetails {
+ filename: String,
+ depth: usize,
+ is_dir: bool,
+ is_expanded: bool,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct ProjectEntry {
+ worktree_ix: usize,
+ entry_id: usize,
+}
+
+action!(ToggleExpanded, ProjectEntry);
+action!(Open, ProjectEntry);
+
+pub fn init(cx: &mut MutableAppContext) {
+ cx.add_action(ProjectPanel::toggle_expanded);
}
pub enum Event {}
@@ -24,77 +46,139 @@ impl ProjectPanel {
settings: watch::Receiver<Settings>,
cx: &mut ViewContext<Self>,
) -> Self {
- cx.observe(&project, |this, project, cx| {
- let project = project.read(cx);
- this.list.reset(Self::entry_count(project, cx));
+ cx.observe(&project, |this, _, cx| {
+ this.update_visible_entries(cx);
cx.notify();
})
.detach();
- Self {
- list: ListState::new(
- {
- let project = project.read(cx);
- Self::entry_count(project, cx)
- },
- Orientation::Top,
- 1000.,
- {
- let project = project.clone();
- let settings = settings.clone();
- move |ix, cx| {
- let project = project.read(cx);
- Self::render_entry_at_index(project, ix, &settings.borrow().theme, cx)
- }
- },
- ),
+ let mut this = Self {
project,
settings,
+ list: Default::default(),
+ visible_entries: Default::default(),
+ expanded_dir_ids: Default::default(),
+ handle: cx.handle().downgrade(),
+ };
+ this.update_visible_entries(cx);
+ this
+ }
+
+ fn toggle_expanded(&mut self, action: &ToggleExpanded, cx: &mut ViewContext<Self>) {
+ let ProjectEntry {
+ worktree_ix,
+ entry_id,
+ } = action.0;
+ let expanded_dir_ids = &mut self.expanded_dir_ids[worktree_ix];
+ match expanded_dir_ids.binary_search(&entry_id) {
+ Ok(ix) => {
+ expanded_dir_ids.remove(ix);
+ }
+ Err(ix) => {
+ expanded_dir_ids.insert(ix, entry_id);
+ }
}
+ self.update_visible_entries(cx);
}
- fn entry_count(project: &Project, cx: &AppContext) -> usize {
- project
- .worktrees()
- .iter()
- .map(|worktree| worktree.read(cx).visible_entry_count())
- .sum()
+ fn update_visible_entries(&mut self, cx: &mut ViewContext<Self>) {
+ let worktrees = self.project.read(cx).worktrees();
+ self.visible_entries.clear();
+ for (worktree_ix, worktree) in worktrees.iter().enumerate() {
+ let snapshot = worktree.read(cx).snapshot();
+
+ if self.expanded_dir_ids.len() <= worktree_ix {
+ self.expanded_dir_ids
+ .push(vec![snapshot.root_entry().unwrap().id])
+ }
+
+ let expanded_dir_ids = &self.expanded_dir_ids[worktree_ix];
+ let mut visible_worktree_entries = Vec::new();
+ let mut entry_iter = snapshot.visible_entries(0);
+ while let Some(item) = entry_iter.item() {
+ visible_worktree_entries.push(entry_iter.ix());
+ if expanded_dir_ids.binary_search(&item.id).is_err() {
+ if entry_iter.advance_sibling() {
+ continue;
+ }
+ }
+ entry_iter.advance();
+ }
+ self.visible_entries.push(visible_worktree_entries);
+ }
}
- fn render_entry_at_index(
- project: &Project,
- mut ix: usize,
- theme: &Theme,
- cx: &AppContext,
- ) -> ElementBox {
- for worktree in project.worktrees() {
- let worktree = worktree.read(cx);
- let visible_entry_count = worktree.visible_entry_count();
- if ix < visible_entry_count {
- let entry = worktree.visible_entries(ix).next().unwrap();
- return Self::render_entry(worktree, entry, theme, cx);
- } else {
- ix -= visible_entry_count;
+ fn append_visible_entries<C: ReadModel, T>(
+ &self,
+ range: Range<usize>,
+ items: &mut Vec<T>,
+ cx: &mut C,
+ mut render_item: impl FnMut(ProjectEntry, EntryDetails, &mut C) -> T,
+ ) {
+ let worktrees = self.project.read(cx).worktrees().to_vec();
+ let mut total_ix = 0;
+ for (worktree_ix, visible_worktree_entries) in self.visible_entries.iter().enumerate() {
+ if total_ix >= range.end {
+ break;
+ }
+ if total_ix + visible_worktree_entries.len() <= range.start {
+ total_ix += visible_worktree_entries.len();
+ continue;
+ }
+
+ let expanded_entry_ids = &self.expanded_dir_ids[worktree_ix];
+ let snapshot = worktrees[worktree_ix].read(cx).snapshot();
+ let mut cursor = snapshot.visible_entries(0);
+ for ix in visible_worktree_entries[(range.start - total_ix)..]
+ .iter()
+ .copied()
+ {
+ cursor.advance_to_ix(ix);
+ if let Some(entry) = cursor.item() {
+ let details = EntryDetails {
+ filename: entry.path.file_name().map_or_else(
+ || snapshot.root_name().to_string(),
+ |name| name.to_string_lossy().to_string(),
+ ),
+ depth: entry.path.components().count(),
+ is_dir: entry.is_dir(),
+ is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(),
+ };
+ let entry = ProjectEntry {
+ worktree_ix,
+ entry_id: entry.id,
+ };
+ items.push(render_item(entry, details, cx));
+ }
+ total_ix += 1;
}
}
- Empty::new().boxed()
}
fn render_entry(
- worktree: &Worktree,
- entry: &worktree::Entry,
- theme: &Theme,
- _: &AppContext,
+ entry: ProjectEntry,
+ details: EntryDetails,
+ theme: &theme::ProjectPanel,
+ cx: &mut ViewContext<Self>,
) -> ElementBox {
- let path = &entry.path;
- let depth = path.iter().count() as f32;
- Label::new(
- path.file_name()
- .map_or(String::new(), |s| s.to_string_lossy().to_string()),
- theme.project_panel.entry.clone(),
+ let is_dir = details.is_dir;
+ MouseEventHandler::new::<Self, _, _, _>(
+ (entry.worktree_ix, entry.entry_id),
+ cx,
+ |state, cx| {
+ Label::new(details.filename, theme.entry.clone())
+ .contained()
+ .with_margin_left(details.depth as f32 * 20.)
+ .boxed()
+ },
)
- .contained()
- .with_margin_left(depth * 20.)
+ .on_click(move |cx| {
+ if is_dir {
+ cx.dispatch_action(ToggleExpanded(entry))
+ } else {
+ cx.dispatch_action(Open(entry))
+ }
+ })
.boxed()
}
}
@@ -105,14 +189,219 @@ impl View for ProjectPanel {
}
fn render(&mut self, _: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
- let theme = &self.settings.borrow().theme.project_panel;
- List::new(self.list.clone())
- .contained()
- .with_style(theme.container)
- .boxed()
+ let settings = self.settings.clone();
+ let handle = self.handle.clone();
+ UniformList::new(
+ self.list.clone(),
+ self.visible_entries.len(),
+ move |range, items, cx| {
+ let theme = &settings.borrow().theme.project_panel;
+ let this = handle.upgrade(cx).unwrap();
+ this.update(cx.app, |this, cx| {
+ this.append_visible_entries(range, items, cx, |entry, details, cx| {
+ Self::render_entry(entry, details, theme, cx)
+ });
+ })
+ },
+ )
+ .contained()
+ .with_style(self.settings.borrow().theme.project_panel.container)
+ .boxed()
}
}
impl Entity for ProjectPanel {
type Event = Event;
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test::test_app_state;
+ use gpui::{TestAppContext, ViewHandle};
+ use serde_json::json;
+ use std::{collections::HashSet, path::Path};
+
+ #[gpui::test]
+ async fn test_visible_list(mut cx: gpui::TestAppContext) {
+ let app_state = cx.update(test_app_state);
+ let settings = app_state.settings.clone();
+ let fs = app_state.fs.as_fake();
+
+ fs.insert_tree(
+ "/root1",
+ json!({
+ ".dockerignore": "",
+ ".git": {
+ "HEAD": "",
+ },
+ "a": {
+ "0": { "q": "", "r": "", "s": "" },
+ "1": { "t": "", "u": "" },
+ "2": { "v": "", "w": "", "x": "", "y": "" },
+ },
+ "b": {
+ "3": { "Q": "" },
+ "4": { "R": "", "S": "", "T": "", "U": "" },
+ },
+ "c": {
+ "5": {},
+ "6": { "V": "", "W": "" },
+ "7": { "X": "" },
+ "8": { "Y": {}, "Z": "" }
+ }
+ }),
+ )
+ .await;
+
+ let project = cx.add_model(|_| Project::new(&app_state));
+ let worktree = project
+ .update(&mut cx, |project, cx| {
+ project.add_local_worktree("/root1".as_ref(), cx)
+ })
+ .await
+ .unwrap();
+ worktree
+ .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
+ .await;
+
+ let (_, panel) = cx.add_window(|cx| ProjectPanel::new(project, settings, cx));
+ assert_eq!(
+ visible_entry_details(&panel, 0..50, &mut cx),
+ &[
+ EntryDetails {
+ filename: "root1".to_string(),
+ depth: 0,
+ is_dir: true,
+ is_expanded: true,
+ },
+ EntryDetails {
+ filename: ".dockerignore".to_string(),
+ depth: 1,
+ is_dir: false,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "a".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "b".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "c".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: false,
+ },
+ ]
+ );
+
+ toggle_expand_dir(&panel, "root1/b", &mut cx);
+ assert_eq!(
+ visible_entry_details(&panel, 0..50, &mut cx),
+ &[
+ EntryDetails {
+ filename: "root1".to_string(),
+ depth: 0,
+ is_dir: true,
+ is_expanded: true,
+ },
+ EntryDetails {
+ filename: ".dockerignore".to_string(),
+ depth: 1,
+ is_dir: false,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "a".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "b".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: true,
+ },
+ EntryDetails {
+ filename: "3".to_string(),
+ depth: 2,
+ is_dir: true,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "4".to_string(),
+ depth: 2,
+ is_dir: true,
+ is_expanded: false,
+ },
+ EntryDetails {
+ filename: "c".to_string(),
+ depth: 1,
+ is_dir: true,
+ is_expanded: false,
+ },
+ ]
+ );
+
+ fn toggle_expand_dir(
+ panel: &ViewHandle<ProjectPanel>,
+ path: impl AsRef<Path>,
+ cx: &mut TestAppContext,
+ ) {
+ let path = path.as_ref();
+ panel.update(cx, |panel, cx| {
+ for (worktree_ix, worktree) in panel.project.read(cx).worktrees().iter().enumerate()
+ {
+ let worktree = worktree.read(cx);
+ if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
+ let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
+ panel.toggle_expanded(
+ &ToggleExpanded(ProjectEntry {
+ worktree_ix,
+ entry_id,
+ }),
+ cx,
+ );
+ return;
+ }
+ }
+ panic!("no worktree for path {:?}", path);
+ });
+ }
+
+ fn visible_entry_details(
+ panel: &ViewHandle<ProjectPanel>,
+ range: Range<usize>,
+ cx: &mut TestAppContext,
+ ) -> Vec<EntryDetails> {
+ let mut result = Vec::new();
+ let mut project_entries = HashSet::new();
+ panel.update(cx, |panel, cx| {
+ panel.append_visible_entries(
+ range,
+ &mut result,
+ cx,
+ |project_entry, details, _| {
+ assert!(
+ project_entries.insert(project_entry),
+ "duplicate project entry {:?} {:?}",
+ project_entry,
+ details
+ );
+ details
+ },
+ );
+ });
+
+ result
+ }
+ }
+}
@@ -1482,16 +1482,16 @@ impl Snapshot {
self.entries_by_path.summary().visible_file_count
}
- pub fn files(&self, start: usize) -> EntryIter {
- EntryIter::files(self, start)
+ pub fn files(&self, start: usize) -> EntryIter<FileCount> {
+ EntryIter::new(self, start)
}
- pub fn visible_entries(&self, start: usize) -> EntryIter {
- EntryIter::visible(self, start)
+ pub fn visible_entries(&self, start: usize) -> EntryIter<VisibleCountAndPath> {
+ EntryIter::new(self, start)
}
- pub fn visible_files(&self, start: usize) -> EntryIter {
- EntryIter::visible_files(self, start)
+ pub fn visible_files(&self, start: usize) -> EntryIter<VisibleFileCount> {
+ EntryIter::new(self, start)
}
pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
@@ -1514,7 +1514,7 @@ impl Snapshot {
&self.root_name
}
- fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
+ pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
let mut cursor = self.entries_by_path.cursor::<_, ()>();
if cursor.seek(&PathSearch::Exact(path.as_ref()), Bias::Left, &()) {
cursor.item()
@@ -2065,30 +2065,108 @@ impl<'a: 'b, 'b> sum_tree::Dimension<'a, EntrySummary> for PathSearch<'b> {
}
}
-#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct FileCount(usize);
+#[derive(Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct VisibleFileCount(usize);
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct VisibleCountAndPath<'a> {
+ count: Option<usize>,
+ path: PathSearch<'a>,
+}
+
impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
self.0 += summary.file_count;
}
}
-#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct VisibleCount(usize);
+impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
+ fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
+ self.0 += summary.visible_file_count;
+ }
+}
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleCount {
+impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleCountAndPath<'a> {
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
- self.0 += summary.visible_count;
+ if let Some(count) = self.count.as_mut() {
+ *count += summary.visible_count;
+ } else {
+ unreachable!()
+ }
+ self.path = PathSearch::Exact(summary.max_path.as_ref());
}
}
-#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct VisibleFileCount(usize);
+impl<'a> Ord for VisibleCountAndPath<'a> {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ if let Some(count) = self.count {
+ count.cmp(&other.count.unwrap())
+ } else {
+ self.path.cmp(&other.path)
+ }
+ }
+}
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
- fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
- self.0 += summary.visible_file_count;
+impl<'a> PartialOrd for VisibleCountAndPath<'a> {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl From<usize> for FileCount {
+ fn from(count: usize) -> Self {
+ Self(count)
+ }
+}
+
+impl From<usize> for VisibleFileCount {
+ fn from(count: usize) -> Self {
+ Self(count)
+ }
+}
+
+impl<'a> From<usize> for VisibleCountAndPath<'a> {
+ fn from(count: usize) -> Self {
+ Self {
+ count: Some(count),
+ path: PathSearch::default(),
+ }
+ }
+}
+
+impl Deref for FileCount {
+ type Target = usize;
+
+ fn deref(&self) -> &usize {
+ &self.0
+ }
+}
+
+impl Deref for VisibleFileCount {
+ type Target = usize;
+
+ fn deref(&self) -> &usize {
+ &self.0
+ }
+}
+
+impl<'a> Deref for VisibleCountAndPath<'a> {
+ type Target = usize;
+
+ fn deref(&self) -> &usize {
+ self.count.as_ref().unwrap()
+ }
+}
+
+impl<'a> Default for VisibleCountAndPath<'a> {
+ fn default() -> Self {
+ Self {
+ count: Some(0),
+ path: Default::default(),
+ }
}
}
@@ -2584,63 +2662,66 @@ impl WorktreeHandle for ModelHandle<Worktree> {
}
}
-pub enum EntryIter<'a> {
- Files(Cursor<'a, Entry, FileCount, ()>),
- Visible(Cursor<'a, Entry, VisibleCount, ()>),
- VisibleFiles(Cursor<'a, Entry, VisibleFileCount, ()>),
+pub struct EntryIter<'a, Dim> {
+ cursor: Cursor<'a, Entry, Dim, ()>,
}
-impl<'a> EntryIter<'a> {
- fn files(snapshot: &'a Snapshot, start: usize) -> Self {
+impl<'a, Dim> EntryIter<'a, Dim>
+where
+ Dim: sum_tree::SeekDimension<'a, EntrySummary> + From<usize> + Deref<Target = usize>,
+{
+ fn new(snapshot: &'a Snapshot, start: usize) -> Self {
let mut cursor = snapshot.entries_by_path.cursor();
- cursor.seek(&FileCount(start), Bias::Right, &());
- Self::Files(cursor)
+ cursor.seek(&Dim::from(start), Bias::Right, &());
+ Self { cursor }
}
- fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
- let mut cursor = snapshot.entries_by_path.cursor();
- cursor.seek(&VisibleCount(start), Bias::Right, &());
- Self::Visible(cursor)
+ pub fn ix(&self) -> usize {
+ *self.cursor.seek_start().deref()
}
- fn visible_files(snapshot: &'a Snapshot, start: usize) -> Self {
- let mut cursor = snapshot.entries_by_path.cursor();
- cursor.seek(&VisibleFileCount(start), Bias::Right, &());
- Self::VisibleFiles(cursor)
+ pub fn advance_to_ix(&mut self, ix: usize) {
+ self.cursor.seek_forward(&Dim::from(ix), Bias::Right, &());
}
- fn next_internal(&mut self) {
- match self {
- Self::Files(cursor) => {
- let ix = *cursor.seek_start();
- cursor.seek_forward(&FileCount(ix.0 + 1), Bias::Right, &());
- }
- Self::Visible(cursor) => {
- let ix = *cursor.seek_start();
- cursor.seek_forward(&VisibleCount(ix.0 + 1), Bias::Right, &());
- }
- Self::VisibleFiles(cursor) => {
- let ix = *cursor.seek_start();
- cursor.seek_forward(&VisibleFileCount(ix.0 + 1), Bias::Right, &());
- }
- }
+ pub fn advance(&mut self) {
+ self.advance_to_ix(self.ix() + 1);
}
- fn item(&self) -> Option<&'a Entry> {
- match self {
- Self::Files(cursor) => cursor.item(),
- Self::Visible(cursor) => cursor.item(),
- Self::VisibleFiles(cursor) => cursor.item(),
+ pub fn item(&self) -> Option<&'a Entry> {
+ self.cursor.item()
+ }
+}
+
+impl<'a> EntryIter<'a, VisibleCountAndPath<'a>> {
+ pub fn advance_sibling(&mut self) -> bool {
+ let start_count = self.cursor.seek_start().count.unwrap();
+ while let Some(item) = self.cursor.item() {
+ self.cursor.seek_forward(
+ &VisibleCountAndPath {
+ count: None,
+ path: PathSearch::Successor(item.path.as_ref()),
+ },
+ Bias::Right,
+ &(),
+ );
+ if self.cursor.seek_start().count.unwrap() > start_count {
+ return true;
+ }
}
+ false
}
}
-impl<'a> Iterator for EntryIter<'a> {
+impl<'a, Dim> Iterator for EntryIter<'a, Dim>
+where
+ Dim: sum_tree::SeekDimension<'a, EntrySummary> + From<usize> + Deref<Target = usize>,
+{
type Item = &'a Entry;
fn next(&mut self) -> Option<Self::Item> {
if let Some(entry) = self.item() {
- self.next_internal();
+ self.advance();
Some(entry)
} else {
None