92 lines · 3.7 KB · Rust
RawDownload
//! Iterator implementations for FileTreeSnapshot. use std::path::Path;use std::sync::Arc; use sum_tree::{Cursor, SeekBias}; use super::{FileEntry, FileTreeSnapshot, PathKey}; /// Iterator over direct children of a directory.////// # How it works////// Entries in the SumTree are sorted lexicographically by path. This means all entries/// within a directory's subtree are **contiguous** in the sorted order:////// ```text/// Sorted entries for child_entries("/project/src/"):/// ┌─────────────┬─────────────────────┬──────────────────────┬─────────────────────────────┬─────────────┐/// │ /project/   │ /project/src/       │ /project/src/lib.rs  │ /project/src/utils/         │ /project/z  │/// │             │ (skip: parent)      │ ✓ yield (1 comp)     │ ✓ yield (1 comp)            │ (stop)      │/// │             │         ↓           │                      │                             │             │/// │             │  cursor starts here │                      │ /project/src/utils/helper.rs│             │/// │             │                     │                      │ (skip: 2 components)        │             │/// └─────────────┴─────────────────────┴──────────────────────┴─────────────────────────────┴─────────────┘/// ```////// The iterator:/// 1. Seeks to the parent path in O(log n)/// 2. Skips the parent directory entry itself/// 3. Iterates forward, yielding entries with exactly 1 path component after the parent prefix/// 4. Skips deeper descendants (2+ components) — they'll be visited when their parent is expanded/// 5. Stops when reaching an entry outside the parent's subtreepub struct ChildEntriesIter<'a> {    cursor: Cursor<'a, FileEntry, PathKey, ()>,    parent_path: &'a Path,    done: bool,} impl<'a> ChildEntriesIter<'a> {    pub(super) fn new(snapshot: &'a FileTreeSnapshot, parent_path: &'a Path) -> Self {        let mut cursor = snapshot.entries_by_path.cursor::<PathKey, ()>();        let key = PathKey::new(Arc::from(parent_path));        cursor.seek(&key, SeekBias::Left);         // Skip past the parent directory itself        if cursor            .item()            .is_some_and(|e| e.path.as_ref() == parent_path)        {            cursor.next();        }         Self {            cursor,            parent_path,            done: false,        }    }} impl<'a> Iterator for ChildEntriesIter<'a> {    type Item = &'a FileEntry;     fn next(&mut self) -> Option<Self::Item> {        if self.done {            return None;        }         loop {            let entry = self.cursor.item()?;             // Stop if we've moved past the parent's subtree (lexicographically)            if !entry.path.starts_with(self.parent_path) {                self.done = true;                return None;            }             // Count path components after the parent prefix to determine depth            let relative = entry.path.strip_prefix(self.parent_path).ok()?;            let components: Vec<_> = relative.components().collect();             self.cursor.next();             // Yield only direct children (exactly 1 component after parent)            // Skip grandchildren and deeper (2+ components)            if components.len() == 1 {                return Some(entry);            }        }    }}