diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index 4f6ec810554826970f7f71508005a71c4203a3e5..8b2e9e78b733cfecc40db1f3704df941c7df6dec 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -199,6 +199,9 @@ where } pub fn next(&mut self) { + if !self.did_seek { + self.descend_to_first_item(self.tree, |_| true) + } self.next_internal(|_| true) } diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index eceaf45578ea3ac74e5dcd8ebc08de37acb4ae51..d98bd9c7981027b8fb0799752665a3df42766ee5 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -114,7 +114,7 @@ impl Workspace { pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool { self.worktrees .iter() - .any(|worktree| worktree.read(app).contains_path(path)) + .any(|worktree| worktree.read(app).contains_abs_path(path)) } pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext) { @@ -125,7 +125,7 @@ impl Workspace { pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext) { for tree in self.worktrees.iter() { - if tree.read(ctx).contains_path(&path) { + if tree.read(ctx).contains_abs_path(&path) { return; } } diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 0f5b8fd0efb8ce48f70e6368ba78d5d55cc85139..7d42d5ca4ff039ba0fc2d50e27aca21fcd7c5e8a 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -64,12 +64,12 @@ impl Worktree { let snapshot = Snapshot { id, scan_id: 0, - path: path.into(), + abs_path: path.into(), ignores: Default::default(), entries: Default::default(), }; let (event_stream, event_stream_handle) = - fsevent::EventStream::new(&[snapshot.path.as_ref()], Duration::from_millis(100)); + fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100)); let background_snapshot = Arc::new(Mutex::new(snapshot.clone())); @@ -148,18 +148,18 @@ impl Worktree { self.snapshot.clone() } - pub fn contains_path(&self, path: &Path) -> bool { - path.starts_with(&self.snapshot.path) + pub fn contains_abs_path(&self, path: &Path) -> bool { + path.starts_with(&self.snapshot.abs_path) } pub fn load_history( &self, - relative_path: &Path, + path: &Path, ctx: &AppContext, ) -> impl Future> { - let path = self.snapshot.path.join(relative_path); + let abs_path = self.snapshot.abs_path.join(path); ctx.background_executor().spawn(async move { - let mut file = std::fs::File::open(&path)?; + let mut file = std::fs::File::open(&abs_path)?; let mut base_text = String::new(); file.read_to_string(&mut base_text)?; Ok(History::new(Arc::from(base_text))) @@ -168,14 +168,14 @@ impl Worktree { pub fn save<'a>( &self, - relative_path: &Path, + path: &Path, content: BufferSnapshot, ctx: &AppContext, ) -> Task> { - let path = self.snapshot.path.join(relative_path); + let abs_path = self.snapshot.abs_path.join(path); ctx.background_executor().spawn(async move { let buffer_size = content.text_summary().bytes.min(10 * 1024); - let file = std::fs::File::create(&path)?; + let file = std::fs::File::create(&abs_path)?; let mut writer = std::io::BufWriter::with_capacity(buffer_size, file); for chunk in content.fragments() { writer.write(chunk.as_bytes())?; @@ -208,7 +208,7 @@ impl fmt::Debug for Worktree { pub struct Snapshot { id: usize, scan_id: usize, - path: Arc, + abs_path: Arc, ignores: BTreeMap, (Arc, usize)>, entries: SumTree, } @@ -226,16 +226,23 @@ impl Snapshot { FileIter::all(self, start) } + #[cfg(test)] + pub fn paths(&self) -> impl Iterator> { + let mut cursor = self.entries.cursor::<(), ()>(); + cursor.next(); + cursor.map(|entry| entry.path()) + } + pub fn visible_files(&self, start: usize) -> FileIter { FileIter::visible(self, start) } pub fn root_entry(&self) -> Entry { - self.entry_for_path(&self.path).unwrap() + self.entry_for_path(&self.abs_path).unwrap() } pub fn root_name(&self) -> Option<&OsStr> { - self.path.file_name() + self.abs_path.file_name() } fn entry_for_path(&self, path: impl AsRef) -> Option { @@ -252,6 +259,8 @@ impl Snapshot { } fn is_path_ignored(&self, path: &Path) -> Result { + dbg!(path); + let mut entry = self .entry_for_path(path) .ok_or_else(|| anyhow!("entry does not exist in worktree"))?; @@ -263,6 +272,7 @@ impl Snapshot { entry.path().parent().and_then(|p| self.entry_for_path(p)) { let parent_path = parent_entry.path(); + dbg!(parent_path); if let Some((ignore, _)) = self.ignores.get(parent_path) { let relative_path = path.strip_prefix(parent_path).unwrap(); match ignore.matched_path_or_any_parents(relative_path, entry.is_dir()) { @@ -322,8 +332,7 @@ impl Snapshot { } fn insert_ignore_file(&mut self, path: &Path) { - let root_path = self.path.parent().unwrap_or(Path::new("")); - let (ignore, err) = Gitignore::new(root_path.join(path)); + let (ignore, err) = Gitignore::new(self.abs_path.join(path)); if let Some(err) = err { log::error!("error in ignore file {:?} - {:?}", path, err); } @@ -573,7 +582,7 @@ impl BackgroundScanner { } fn update_other_mount_paths(&mut self) { - let path = self.snapshot.lock().path.clone(); + let path = self.snapshot.lock().abs_path.clone(); self.other_mount_paths.clear(); self.other_mount_paths.extend( mounted_volume_paths() @@ -582,8 +591,8 @@ impl BackgroundScanner { ); } - fn path(&self) -> Arc { - self.snapshot.lock().path.clone() + fn abs_path(&self) -> Arc { + self.snapshot.lock().abs_path.clone() } fn snapshot(&self) -> Snapshot { @@ -625,16 +634,15 @@ impl BackgroundScanner { fn scan_dirs(&self) -> io::Result<()> { self.snapshot.lock().scan_id += 1; - let path = self.path(); - let metadata = fs::metadata(&path)?; + let path: Arc = Arc::from(Path::new("")); + let abs_path = self.abs_path(); + let metadata = fs::metadata(&abs_path)?; let inode = metadata.ino(); - let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink(); - let name: Arc = path.file_name().unwrap_or(OsStr::new("/")).into(); - let relative_path: Arc = Arc::from((*name).as_ref()); + let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink(); if metadata.file_type().is_dir() { let dir_entry = Entry::Dir { - path: relative_path.clone(), + path: path.clone(), inode, is_symlink, pending: true, @@ -645,8 +653,8 @@ impl BackgroundScanner { let (tx, rx) = crossbeam_channel::unbounded(); tx.send(ScanJob { - path: path.to_path_buf(), - relative_path, + abs_path: abs_path.to_path_buf(), + path, scan_queue: tx.clone(), }) .unwrap(); @@ -657,7 +665,7 @@ impl BackgroundScanner { pool.execute(|| { while let Ok(job) = rx.recv() { if let Err(err) = self.scan_dir(&job) { - log::error!("error scanning {:?}: {}", job.path, err); + log::error!("error scanning {:?}: {}", job.abs_path, err); } } }); @@ -665,8 +673,8 @@ impl BackgroundScanner { }); } else { self.snapshot.lock().insert_entry(Entry::File { - path_entry: PathEntry::new(inode, relative_path.clone()), - path: relative_path, + path_entry: PathEntry::new(inode, path.clone()), + path, inode, is_symlink, is_ignored: None, @@ -682,37 +690,37 @@ impl BackgroundScanner { let mut new_entries = Vec::new(); let mut new_jobs = Vec::new(); - for child_entry in fs::read_dir(&job.path)? { + for child_entry in fs::read_dir(&job.abs_path)? { let child_entry = child_entry?; - let child_name: Arc = child_entry.file_name().into(); - let child_relative_path: Arc = job.relative_path.join(child_name.as_ref()).into(); + let child_name = child_entry.file_name(); + let child_abs_path = job.abs_path.join(&child_name); + let child_path: Arc = job.path.join(&child_name).into(); let child_metadata = child_entry.metadata()?; let child_inode = child_metadata.ino(); let child_is_symlink = child_metadata.file_type().is_symlink(); - let child_path = job.path.join(child_name.as_ref()); // Disallow mount points outside the file system containing the root of this worktree - if self.other_mount_paths.contains(&child_path) { + if self.other_mount_paths.contains(&child_abs_path) { continue; } if child_metadata.is_dir() { new_entries.push(Entry::Dir { - path: child_relative_path.clone(), + path: child_path.clone(), inode: child_inode, is_symlink: child_is_symlink, pending: true, is_ignored: None, }); new_jobs.push(ScanJob { + abs_path: child_abs_path, path: child_path, - relative_path: child_relative_path, scan_queue: job.scan_queue.clone(), }); } else { new_entries.push(Entry::File { - path_entry: PathEntry::new(child_inode, child_relative_path.clone()), - path: child_relative_path, + path_entry: PathEntry::new(child_inode, child_path.clone()), + path: child_path, inode: child_inode, is_symlink: child_is_symlink, is_ignored: None, @@ -722,7 +730,7 @@ impl BackgroundScanner { self.snapshot .lock() - .populate_dir(job.relative_path.clone(), new_entries); + .populate_dir(job.path.clone(), new_entries); for new_job in new_jobs { job.scan_queue.send(new_job).unwrap(); } @@ -736,40 +744,44 @@ impl BackgroundScanner { let mut snapshot = self.snapshot(); snapshot.scan_id += 1; - let root_path = if let Ok(path) = snapshot.path.canonicalize() { - path + let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() { + abs_path } else { return false; }; events.sort_unstable_by(|a, b| a.path.cmp(&b.path)); - let mut paths = events.into_iter().map(|e| e.path).peekable(); + let mut abs_paths = events.into_iter().map(|e| e.path).peekable(); let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded(); - while let Some(path) = paths.next() { - let relative_path = - match path.strip_prefix(&root_path.parent().unwrap_or(Path::new(""))) { - Ok(relative_path) => relative_path.to_path_buf(), - Err(_) => { - log::error!("unexpected event {:?} for root path {:?}", path, root_path); - continue; - } - }; - while paths.peek().map_or(false, |p| p.starts_with(&path)) { - paths.next(); + while let Some(abs_path) = abs_paths.next() { + let path = match abs_path.strip_prefix(&root_abs_path) { + Ok(path) => Arc::from(path.to_path_buf()), + Err(_) => { + log::error!( + "unexpected event {:?} for root path {:?}", + abs_path, + root_abs_path + ); + continue; + } + }; + + while abs_paths.peek().map_or(false, |p| p.starts_with(&abs_path)) { + abs_paths.next(); } - snapshot.remove_path(&relative_path); + snapshot.remove_path(&path); - match self.fs_entry_for_path(&root_path, &path) { + match self.fs_entry_for_path(path.clone(), &abs_path) { Ok(Some(fs_entry)) => { let is_dir = fs_entry.is_dir(); snapshot.insert_entry(fs_entry); if is_dir { scan_queue_tx .send(ScanJob { + abs_path, path, - relative_path: relative_path.into(), scan_queue: scan_queue_tx.clone(), }) .unwrap(); @@ -792,7 +804,7 @@ impl BackgroundScanner { pool.execute(|| { while let Ok(job) = scan_queue_rx.recv() { if let Err(err) = self.scan_dir(&job) { - log::error!("error scanning {:?}: {}", job.path, err); + log::error!("error scanning {:?}: {}", job.abs_path, err); } } }); @@ -919,8 +931,8 @@ impl BackgroundScanner { }); } - fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result> { - let metadata = match fs::metadata(&path) { + fn fs_entry_for_path(&self, path: Arc, abs_path: &Path) -> Result> { + let metadata = match fs::metadata(&abs_path) { Err(err) => { return match (err.kind(), err.raw_os_error()) { (io::ErrorKind::NotFound, _) => Ok(None), @@ -930,20 +942,15 @@ impl BackgroundScanner { } Ok(metadata) => metadata, }; - let inode = metadata.ino(); - let is_symlink = fs::symlink_metadata(&path) + let is_symlink = fs::symlink_metadata(&abs_path) .context("failed to read symlink metadata")? .file_type() .is_symlink(); - let relative_path_with_root = root_path - .parent() - .map_or(path, |parent| path.strip_prefix(parent).unwrap()) - .into(); let entry = if metadata.file_type().is_dir() { Entry::Dir { - path: relative_path_with_root, + path, inode, is_symlink, pending: true, @@ -951,8 +958,8 @@ impl BackgroundScanner { } } else { Entry::File { - path_entry: PathEntry::new(inode, relative_path_with_root.clone()), - path: relative_path_with_root, + path_entry: PathEntry::new(inode, path.clone()), + path, inode, is_symlink, is_ignored: None, @@ -964,8 +971,8 @@ impl BackgroundScanner { } struct ScanJob { - path: PathBuf, - relative_path: Arc, + abs_path: PathBuf, + path: Arc, scan_queue: crossbeam_channel::Sender, } @@ -1149,7 +1156,7 @@ mod tests { } #[test] - fn test_rescan() { + fn test_rescan_simple() { App::test_async((), |mut app| async move { let dir = temp_tree(json!({ "a": { @@ -1173,10 +1180,23 @@ mod tests { }); std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); - tree.condition(&app, move |_, _| { - file2.path().as_ref() == Path::new("d/file2") - }) - .await; + + app.read(|ctx| tree.read(ctx).next_scan_complete()).await; + + app.read(|ctx| { + assert_eq!( + tree.read(ctx) + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(), + vec!["a", "a/file1", "b", "d", "d/file2"] + ) + }); + + // tree.condition(&app, move |_, _| { + // file2.path().as_ref() == Path::new("d/file2") + // }) + // .await; }); } @@ -1196,6 +1216,16 @@ mod tests { let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx)); app.read(|ctx| tree.read(ctx).scan_complete()).await; + + app.read(|ctx| { + let paths = tree + .read(ctx) + .paths() + .map(|p| p.to_str().unwrap()) + .collect::>(); + println!("paths {:?}", paths); + }); + app.read(|ctx| { let tree = tree.read(ctx); let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap(); @@ -1255,7 +1285,7 @@ mod tests { Arc::new(Mutex::new(Snapshot { id: 0, scan_id: 0, - path: root_dir.path().into(), + abs_path: root_dir.path().into(), entries: Default::default(), ignores: Default::default(), })), @@ -1288,7 +1318,7 @@ mod tests { Arc::new(Mutex::new(Snapshot { id: 0, scan_id: 0, - path: root_dir.path().into(), + abs_path: root_dir.path().into(), entries: Default::default(), ignores: Default::default(), })), diff --git a/zed/src/worktree/fuzzy.rs b/zed/src/worktree/fuzzy.rs index 5b1a61e8c07a911434e0e179a011f5ec0ef90e3f..f8307b81ef3b189be8db6a0633e24e192e9a5df5 100644 --- a/zed/src/worktree/fuzzy.rs +++ b/zed/src/worktree/fuzzy.rs @@ -523,7 +523,7 @@ mod tests { &Snapshot { id: 0, scan_id: 0, - path: PathBuf::new().into(), + abs_path: PathBuf::new().into(), ignores: Default::default(), entries: Default::default(), },