From a422d480a188a28c6b5e7862fbf07817eb2c7447 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Dec 2018 16:38:05 +0300 Subject: implement vfs events handling --- crates/ra_vfs/src/lib.rs | 153 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 140 insertions(+), 13 deletions(-) (limited to 'crates/ra_vfs/src/lib.rs') diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 8ce6b6ee0..792f722a7 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -15,14 +15,19 @@ mod arena; mod io; use std::{ + mem, thread, cmp::Reverse, path::{Path, PathBuf}, ffi::OsStr, sync::Arc, + fs, }; +use rustc_hash::{FxHashMap, FxHashSet}; use relative_path::RelativePathBuf; +use crossbeam_channel::Receiver; +use walkdir::DirEntry; use thread_worker::{WorkerHandle}; use crate::{ @@ -40,15 +45,25 @@ impl RootFilter { fn new(root: PathBuf) -> RootFilter { RootFilter { root, - file_filter: rs_extension_filter, + file_filter: has_rs_extension, } } - fn can_contain(&self, path: &Path) -> bool { - (self.file_filter)(path) && path.starts_with(&self.root) + /// Check if this root can contain `path`. NB: even if this returns + /// true, the `path` might actually be conained in some nested root. + fn can_contain(&self, path: &Path) -> Option { + if !(self.file_filter)(path) { + return None; + } + if !(path.starts_with(&self.root)) { + return None; + } + let path = path.strip_prefix(&self.root).unwrap(); + let path = RelativePathBuf::from_path(path).unwrap(); + Some(path) } } -fn rs_extension_filter(p: &Path) -> bool { +fn has_rs_extension(p: &Path) -> bool { p.extension() == Some(OsStr::new("rs")) } @@ -82,10 +97,11 @@ struct VfsFileData { text: Arc, } -struct Vfs { +pub struct Vfs { roots: Arena, files: Arena, - // pending_changes: Vec, + root2files: FxHashMap>, + pending_changes: Vec, worker: io::Worker, worker_handle: WorkerHandle, } @@ -97,33 +113,144 @@ impl Vfs { let mut res = Vfs { roots: Arena::default(), files: Arena::default(), + root2files: FxHashMap::default(), worker, worker_handle, + pending_changes: Vec::new(), }; // A hack to make nesting work. roots.sort_by_key(|it| Reverse(it.as_os_str().len())); - - for path in roots { - res.roots.alloc(RootFilter::new(path)); + for (i, path) in roots.iter().enumerate() { + let root = res.roots.alloc(RootFilter::new(path.clone())); + let nested = roots[..i] + .iter() + .filter(|it| it.starts_with(path)) + .map(|it| it.clone()) + .collect::>(); + let filter = move |entry: &DirEntry| { + if entry.file_type().is_file() { + has_rs_extension(entry.path()) + } else { + nested.iter().all(|it| it != entry.path()) + } + }; + let task = io::Task { + root, + path: path.clone(), + filter: Box::new(filter), + }; + res.worker.inp.send(task); } res } - pub fn add_file_overlay(&mut self, path: &Path, content: String) {} + pub fn task_receiver(&self) -> &Receiver { + &self.worker.out + } + + pub fn handle_task(&mut self, task: io::TaskResult) { + let mut files = Vec::new(); + for (path, text) in task.files { + let text = Arc::new(text); + let file = self.add_file(task.root, path.clone(), Arc::clone(&text)); + files.push((file, path, text)); + } + let change = VfsChange::AddRoot { + root: task.root, + files, + }; + self.pending_changes.push(change); + } - pub fn change_file_overlay(&mut self, path: &Path, new_content: String) {} + pub fn add_file_overlay(&mut self, path: &Path, text: String) { + if let Some((root, path, file)) = self.find_root(path) { + let text = Arc::new(text); + let change = if let Some(file) = file { + self.change_file(file, Arc::clone(&text)); + VfsChange::ChangeFile { file, text } + } else { + let file = self.add_file(root, path.clone(), Arc::clone(&text)); + VfsChange::AddFile { + file, + text, + root, + path, + } + }; + self.pending_changes.push(change); + } + } - pub fn remove_file_overlay(&mut self, path: &Path) {} + pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { + if let Some((_root, _path, file)) = self.find_root(path) { + let file = file.expect("can't change a file which wasn't added"); + let text = Arc::new(new_text); + self.change_file(file, Arc::clone(&text)); + let change = VfsChange::ChangeFile { file, text }; + self.pending_changes.push(change); + } + } + + pub fn remove_file_overlay(&mut self, path: &Path) { + if let Some((root, path, file)) = self.find_root(path) { + let file = file.expect("can't remove a file which wasn't added"); + let full_path = path.to_path(&self.roots[root].root); + let change = if let Ok(text) = fs::read_to_string(&full_path) { + let text = Arc::new(text); + self.change_file(file, Arc::clone(&text)); + VfsChange::ChangeFile { file, text } + } else { + self.remove_file(file); + VfsChange::RemoveFile { file } + }; + self.pending_changes.push(change); + } + } pub fn commit_changes(&mut self) -> Vec { - unimplemented!() + mem::replace(&mut self.pending_changes, Vec::new()) } pub fn shutdown(self) -> thread::Result<()> { let _ = self.worker.shutdown(); self.worker_handle.shutdown() } + + fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc) -> VfsFile { + let data = VfsFileData { root, path, text }; + let file = self.files.alloc(data); + self.root2files + .entry(root) + .or_insert_with(FxHashSet::default) + .insert(file); + file + } + + fn change_file(&mut self, file: VfsFile, new_text: Arc) { + self.files[file].text = new_text; + } + + fn remove_file(&mut self, file: VfsFile) { + //FIXME: use arena with removal + self.files[file].text = Default::default(); + self.files[file].path = Default::default(); + let root = self.files[file].root; + let removed = self.root2files.get_mut(&root).unwrap().remove(&file); + assert!(removed); + } + + fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option)> { + let (root, path) = self + .roots + .iter() + .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?; + let file = self.root2files[&root] + .iter() + .map(|&it| it) + .find(|&file| self.files[file].path == path); + Some((root, path, file)) + } } #[derive(Debug, Clone)] -- cgit v1.2.3