diff --git a/gpui/src/elements/uniform_list.rs b/gpui/src/elements/uniform_list.rs index c82d8aa3d6fc740c3179f50b367348b32816cc76..298f87d3bb095d060dde66033bc575d056b9d26e 100644 --- a/gpui/src/elements/uniform_list.rs +++ b/gpui/src/elements/uniform_list.rs @@ -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 where - F: Fn(Range, &mut Vec, &mut MutableAppContext), + F: Fn(Range, &mut Vec, &mut LayoutContext), { state: UniformListState, item_count: usize, @@ -47,7 +47,7 @@ where impl UniformList where - F: Fn(Range, &mut Vec, &mut MutableAppContext), + F: Fn(Range, &mut Vec, &mut LayoutContext), { pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self { Self { @@ -102,7 +102,7 @@ where impl Element for UniformList where - F: Fn(Range, &mut Vec, &mut MutableAppContext), + F: Fn(Range, &mut Vec, &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); } diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index 354f0a0f821af81040581a7afedb2eb4652acbc2..8bcdd08aadcd779b0fbb1f4eb894bd3d8f267c21 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -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(&mut self, handle: &ViewHandle, update: F) -> S + where + T: View, + F: FnOnce(&mut T, &mut crate::ViewContext) -> S, + { + self.app.update_view(handle, update) + } +} + impl<'a> ReadModel for LayoutContext<'a> { fn read_model(&self, handle: &ModelHandle) -> &T { self.app.read_model(handle) diff --git a/gpui/src/views/select.rs b/gpui/src/views/select.rs index e257455a7afc3ede7f02fc3489d3855eb5444438..a76f156ebfe0fd31ec842b351680063d6a2d1e50 100644 --- a/gpui/src/views/select.rs +++ b/gpui/src/views/select.rs @@ -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::( (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 diff --git a/zed/src/fuzzy.rs b/zed/src/fuzzy.rs index dde7f8fd7aff2fd198816f6115119e6128172978..3f767e4a30cf2dc221577e8514933832274b7fb8 100644 --- a/zed/src/fuzzy.rs +++ b/zed/src/fuzzy.rs @@ -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; diff --git a/zed/src/main.rs b/zed/src/main.rs index c88b1465d14742e69341ee7a68513555fa937d08..6070f7eab683421a850c8251afcd3756ed145670 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -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())); diff --git a/zed/src/project_panel.rs b/zed/src/project_panel.rs index 4a017a3fe88beaf51af2141c2c8e21be1b86e83e..985ed72529fcefa39c0925d66fe77e5f456ad8ed 100644 --- a/zed/src/project_panel.rs +++ b/zed/src/project_panel.rs @@ -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, - list: ListState, + list: UniformListState, + visible_entries: Vec>, + expanded_dir_ids: Vec>, settings: watch::Receiver, + handle: WeakViewHandle, +} + +#[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, cx: &mut ViewContext, ) -> 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) { + 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) { + 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( + &self, + range: Range, + items: &mut Vec, + 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, ) -> 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::( + (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, + path: impl AsRef, + 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, + range: Range, + cx: &mut TestAppContext, + ) -> Vec { + 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 + } + } +} diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 4fe9a31b67eb4b80b0cc3770afcf93836d972923..9704056489f907b9eecde98891ede981b6045f1b 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -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 { + EntryIter::new(self, start) } - pub fn visible_entries(&self, start: usize) -> EntryIter { - EntryIter::visible(self, start) + pub fn visible_entries(&self, start: usize) -> EntryIter { + 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 { + EntryIter::new(self, start) } pub fn paths(&self) -> impl Iterator> { @@ -1514,7 +1514,7 @@ impl Snapshot { &self.root_name } - fn entry_for_path(&self, path: impl AsRef) -> Option<&Entry> { + pub fn entry_for_path(&self, path: impl AsRef) -> 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, + 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 { + Some(self.cmp(other)) + } +} + +impl From for FileCount { + fn from(count: usize) -> Self { + Self(count) + } +} + +impl From for VisibleFileCount { + fn from(count: usize) -> Self { + Self(count) + } +} + +impl<'a> From 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 { } } -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 + Deref, +{ + 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 + Deref, +{ type Item = &'a Entry; fn next(&mut self) -> Option { if let Some(entry) = self.item() { - self.next_internal(); + self.advance(); Some(entry) } else { None