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/arena.rs | 7 ++- crates/ra_vfs/src/io.rs | 43 +++++-------- crates/ra_vfs/src/lib.rs | 153 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 162 insertions(+), 41 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/arena.rs b/crates/ra_vfs/src/arena.rs index d6fad753b..6b42ae26d 100644 --- a/crates/ra_vfs/src/arena.rs +++ b/crates/ra_vfs/src/arena.rs @@ -1,5 +1,4 @@ use std::{ - hash::{Hash, Hasher}, marker::PhantomData, ops::{Index, IndexMut}, }; @@ -21,6 +20,12 @@ impl Arena { self.data.push(value); ID::from_u32(id) } + pub fn iter<'a>(&'a self) -> impl Iterator { + self.data + .iter() + .enumerate() + .map(|(idx, value)| (ID::from_u32(idx as u32), value)) + } } impl Default for Arena { diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index c46760583..178c9beff 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -1,35 +1,26 @@ use std::{ fs, path::{Path, PathBuf}, - thread::JoinHandle, }; use walkdir::{DirEntry, WalkDir}; -use crossbeam_channel::{Sender, Receiver}; use thread_worker::{WorkerHandle}; +use relative_path::RelativePathBuf; use crate::VfsRoot; -pub(crate) enum Task { - ScanRoot { - root: VfsRoot, - path: PathBuf, - filter: Box bool + Send>, - }, -} - -#[derive(Debug)] -pub(crate) struct FileEvent { +pub(crate) struct Task { + pub(crate) root: VfsRoot, pub(crate) path: PathBuf, - pub(crate) kind: FileEventKind, + pub(crate) filter: Box bool + Send>, } -#[derive(Debug)] -pub(crate) enum FileEventKind { - Add(String), +pub struct TaskResult { + pub(crate) root: VfsRoot, + pub(crate) files: Vec<(RelativePathBuf, String)>, } -pub(crate) type Worker = thread_worker::Worker)>; +pub(crate) type Worker = thread_worker::Worker; pub(crate) fn start() -> (Worker, WorkerHandle) { thread_worker::spawn("vfs", 128, |input_receiver, output_sender| { @@ -39,17 +30,17 @@ pub(crate) fn start() -> (Worker, WorkerHandle) { }) } -fn handle_task(task: Task) -> (PathBuf, Vec) { - let Task::ScanRoot { path, .. } = task; +fn handle_task(task: Task) -> TaskResult { + let Task { root, path, filter } = task; log::debug!("loading {} ...", path.as_path().display()); - let events = load_root(path.as_path()); + let files = load_root(path.as_path(), &*filter); log::debug!("... loaded {}", path.as_path().display()); - (path, events) + TaskResult { root, files } } -fn load_root(path: &Path) -> Vec { +fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { let mut res = Vec::new(); - for entry in WalkDir::new(path) { + for entry in WalkDir::new(root).into_iter().filter_entry(filter) { let entry = match entry { Ok(entry) => entry, Err(e) => { @@ -71,10 +62,8 @@ fn load_root(path: &Path) -> Vec { continue; } }; - res.push(FileEvent { - path: path.to_owned(), - kind: FileEventKind::Add(text), - }) + let path = RelativePathBuf::from_path(path.strip_prefix(root).unwrap()).unwrap(); + res.push((path.to_owned(), text)) } res } 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