diff --git a/Cargo.lock b/Cargo.lock index b8b7d9ad22..c6a91422ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2220,6 +2220,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "cocoa", "crossbeam-channel", "ctor", "dirs", @@ -2233,6 +2234,7 @@ dependencies = [ "libc", "log", "num_cpus", + "objc", "parking_lot", "postage", "rand 0.8.3", diff --git a/zed/Cargo.toml b/zed/Cargo.toml index c38db66d0f..0ab9b2495c 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -42,3 +42,7 @@ env_logger = "0.8" serde_json = {version = "1.0.64", features = ["preserve_order"]} tempdir = "0.3.7" unindent = "0.1.7" + +[target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.24" +objc = "0.2" diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 4a96729ef4..bd2de1cfdd 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -354,7 +354,11 @@ impl Snapshot { self.insert_ignore_file(new_parent_inode); } - let mut new_parent = self.entries.get(&new_parent_inode).unwrap().clone(); + let mut new_parent = self + .entries + .get(&new_parent_inode) + .expect(&format!("no entry for inode {}", new_parent_inode)) + .clone(); if let Entry::Dir { children, .. } = &mut new_parent { *children = children .iter() @@ -417,7 +421,7 @@ impl Snapshot { *children = new_children.into(); *pending = false; } else { - unreachable!("non-directory parent"); + unreachable!("non-directory parent {}", parent_inode); } edits.push(Edit::Insert(parent)); @@ -703,16 +707,30 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount { struct BackgroundScanner { snapshot: Arc>, notify: Sender, + other_mount_paths: HashSet, thread_pool: scoped_pool::Pool, } impl BackgroundScanner { fn new(snapshot: Arc>, notify: Sender, worktree_id: usize) -> Self { - Self { + let mut scanner = Self { snapshot, notify, + other_mount_paths: Default::default(), thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)), - } + }; + scanner.update_other_mount_paths(); + scanner + } + + fn update_other_mount_paths(&mut self) { + let path = self.snapshot.lock().path.clone(); + self.other_mount_paths.clear(); + self.other_mount_paths.extend( + mounted_volume_paths() + .into_iter() + .filter(|mount_path| !path.starts_with(mount_path)), + ); } fn path(&self) -> Arc { @@ -723,7 +741,7 @@ impl BackgroundScanner { self.snapshot.lock().clone() } - fn run(self, event_stream: fsevent::EventStream) { + fn run(mut self, event_stream: fsevent::EventStream) { if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() { return; } @@ -792,23 +810,17 @@ impl BackgroundScanner { .unwrap(); drop(tx); - let mut results = Vec::new(); - results.resize_with(self.thread_pool.thread_count(), || Ok(())); self.thread_pool.scoped(|pool| { - for result in &mut results { + for _ in 0..self.thread_pool.thread_count() { pool.execute(|| { - let result = result; while let Ok(job) = rx.recv() { if let Err(err) = self.scan_dir(&job) { log::error!("error scanning {:?}: {}", job.path, err); - *result = Err(err); - break; } } }); } }); - results.into_iter().collect::>()?; } else { let mut snapshot = self.snapshot.lock(); snapshot.insert_entry( @@ -842,6 +854,11 @@ impl BackgroundScanner { 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) { + continue; + } + if child_metadata.is_dir() { new_entries.push(( child_name, @@ -874,7 +891,6 @@ impl BackgroundScanner { }; } - dbg!(&job.path); self.snapshot.lock().populate_dir(job.inode, new_entries); for new_job in new_jobs { job.scan_queue.send(new_job).unwrap(); @@ -883,7 +899,9 @@ impl BackgroundScanner { Ok(()) } - fn process_events(&self, mut events: Vec) -> bool { + fn process_events(&mut self, mut events: Vec) -> bool { + self.update_other_mount_paths(); + let mut snapshot = self.snapshot(); snapshot.scan_id += 1; @@ -1210,6 +1228,34 @@ impl<'a> Iterator for FileIter<'a> { } } +fn mounted_volume_paths() -> Vec { + use cocoa::{ + base::{id, nil}, + foundation::{NSArray, NSString, NSURL}, + }; + use objc::{class, msg_send, sel, sel_impl}; + + unsafe { + let manager: id = msg_send![class!(NSFileManager), defaultManager]; + let array = NSArray::array(nil); + let urls: id = + msg_send![manager, mountedVolumeURLsIncludingResourceValuesForKeys:array options:0]; + let len = urls.count() as usize; + let mut result = Vec::with_capacity(len); + for i in 0..len { + let url = urls.objectAtIndex(i as u64); + let string = url.absoluteString(); + let string = std::ffi::CStr::from_ptr(string.UTF8String()) + .to_string_lossy() + .to_string(); + if let Some(path) = string.strip_prefix("file://") { + result.push(PathBuf::from(path)); + } + } + result + } +} + #[cfg(test)] mod tests { use super::*; @@ -1273,8 +1319,6 @@ mod tests { ); }) }); - - eprintln!("HI"); } #[test] @@ -1383,6 +1427,12 @@ mod tests { }); } + #[test] + fn test_mounted_volume_paths() { + let paths = mounted_volume_paths(); + assert!(paths.contains(&"/".into())); + } + #[test] fn test_random() { let iterations = env::var("ITERATIONS") @@ -1411,7 +1461,7 @@ mod tests { log::info!("Generated initial tree"); let (notify_tx, _notify_rx) = smol::channel::unbounded(); - let scanner = BackgroundScanner::new( + let mut scanner = BackgroundScanner::new( Arc::new(Mutex::new(Snapshot { id: 0, scan_id: 0,