From 7509901fa0985f8fc4893a83e0275a063f072dda Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Dec 2018 12:29:14 +0300 Subject: wip --- crates/ra_vfs/src/arena.rs | 48 +++++++++++++++++ crates/ra_vfs/src/io.rs | 72 +++++++++++++++++++++++++ crates/ra_vfs/src/lib.rs | 128 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 crates/ra_vfs/src/arena.rs create mode 100644 crates/ra_vfs/src/io.rs create mode 100644 crates/ra_vfs/src/lib.rs (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/arena.rs b/crates/ra_vfs/src/arena.rs new file mode 100644 index 000000000..d6fad753b --- /dev/null +++ b/crates/ra_vfs/src/arena.rs @@ -0,0 +1,48 @@ +use std::{ + hash::{Hash, Hasher}, + marker::PhantomData, + ops::{Index, IndexMut}, +}; + +#[derive(Clone, Debug)] +pub(crate) struct Arena { + data: Vec, + _ty: PhantomData, +} + +pub(crate) trait ArenaId { + fn from_u32(id: u32) -> Self; + fn to_u32(self) -> u32; +} + +impl Arena { + pub fn alloc(&mut self, value: T) -> ID { + let id = self.data.len() as u32; + self.data.push(value); + ID::from_u32(id) + } +} + +impl Default for Arena { + fn default() -> Arena { + Arena { + data: Vec::new(), + _ty: PhantomData, + } + } +} + +impl Index for Arena { + type Output = T; + fn index(&self, idx: ID) -> &T { + let idx = idx.to_u32() as usize; + &self.data[idx] + } +} + +impl IndexMut for Arena { + fn index_mut(&mut self, idx: ID) -> &mut T { + let idx = idx.to_u32() as usize; + &mut self.data[idx] + } +} diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs new file mode 100644 index 000000000..f90fe0e84 --- /dev/null +++ b/crates/ra_vfs/src/io.rs @@ -0,0 +1,72 @@ +use std::{ + fs, + path::{Path, PathBuf}, + thread::JoinHandle, +}; + +use walkdir::WalkDir; +use crossbeam_channel::{Sender, Receiver}; + +pub(crate) fn start_io() -> (JoinHandle<(), Sender<()>, Receiver()>) {} + +// use crate::thread_watcher::{ThreadWatcher, Worker}; + +// #[derive(Debug)] +// pub struct FileEvent { +// pub path: PathBuf, +// pub kind: FileEventKind, +// } + +// #[derive(Debug)] +// pub enum FileEventKind { +// Add(String), +// } + +// pub fn roots_loader() -> (Worker)>, ThreadWatcher) { +// Worker::)>::spawn( +// "roots loader", +// 128, +// |input_receiver, output_sender| { +// input_receiver +// .map(|path| { +// log::debug!("loading {} ...", path.as_path().display()); +// let events = load_root(path.as_path()); +// log::debug!("... loaded {}", path.as_path().display()); +// (path, events) +// }) +// .for_each(|it| output_sender.send(it)) +// }, +// ) +// } + +// fn load_root(path: &Path) -> Vec { +// let mut res = Vec::new(); +// for entry in WalkDir::new(path) { +// let entry = match entry { +// Ok(entry) => entry, +// Err(e) => { +// log::warn!("watcher error: {}", e); +// continue; +// } +// }; +// if !entry.file_type().is_file() { +// continue; +// } +// let path = entry.path(); +// if path.extension().and_then(|os| os.to_str()) != Some("rs") { +// continue; +// } +// let text = match fs::read_to_string(path) { +// Ok(text) => text, +// Err(e) => { +// log::warn!("watcher error: {}", e); +// continue; +// } +// }; +// res.push(FileEvent { +// path: path.to_owned(), +// kind: FileEventKind::Add(text), +// }) +// } +// res +// } diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs new file mode 100644 index 000000000..8f6abadb7 --- /dev/null +++ b/crates/ra_vfs/src/lib.rs @@ -0,0 +1,128 @@ +//! VFS stands for Virtual File System. +//! +//! When doing analysis, we don't want to do any IO, we want to keep all source +//! code in memory. However, the actual source code is stored on disk, so you +//! need to get it into the memory in the first place somehow. VFS is the +//! component which does this. +//! +//! It also is responsible for watching the disk for changes, and for merging +//! editor state (modified, unsaved files) with disk state. +//! +//! VFS is based on a concept of roots: a set of directories on the file system +//! whihc are watched for changes. Typically, there will be a root for each +//! Cargo package. +mod arena; +mod io; + +use std::{ + cmp::Reverse, + path::{Path, PathBuf}, + ffi::OsStr, + sync::Arc, +}; + +use relative_path::RelativePathBuf; +use crate::arena::{ArenaId, Arena}; + +/// `RootFilter` is a predicate that checks if a file can belong to a root +struct RootFilter { + root: PathBuf, + file_filter: fn(&Path) -> bool, +} + +impl RootFilter { + fn new(root: PathBuf) -> RootFilter { + RootFilter { + root, + file_filter: rs_extension_filter, + } + } + fn can_contain(&self, path: &Path) -> bool { + (self.file_filter)(path) && path.starts_with(&self.root) + } +} + +fn rs_extension_filter(p: &Path) -> bool { + p.extension() == Some(OsStr::new("rs")) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct VfsRoot(u32); + +impl ArenaId for VfsRoot { + fn from_u32(idx: u32) -> VfsRoot { + VfsRoot(idx) + } + fn to_u32(self) -> u32 { + self.0 + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct VfsFile(u32); + +impl ArenaId for VfsFile { + fn from_u32(idx: u32) -> VfsFile { + VfsFile(idx) + } + fn to_u32(self) -> u32 { + self.0 + } +} + +struct VfsFileData { + root: VfsRoot, + path: RelativePathBuf, + text: Arc, +} + +#[derive(Default)] +struct Vfs { + roots: Arena, + files: Arena, + // pending_changes: Vec, +} + +impl Vfs { + pub fn new(mut roots: Vec) -> Vfs { + let mut res = Vfs::default(); + + roots.sort_by_key(|it| Reverse(it.as_os_str().len())); + + for path in roots { + res.roots.alloc(RootFilter::new(path)); + } + res + } + + pub fn add_file_overlay(&mut self, path: &Path, content: String) {} + + pub fn change_file_overlay(&mut self, path: &Path, new_content: String) {} + + pub fn remove_file_overlay(&mut self, path: &Path) {} + + pub fn commit_changes(&mut self) -> Vec { + unimplemented!() + } +} + +#[derive(Debug, Clone)] +pub enum VfsChange { + AddRoot { + root: VfsRoot, + files: Vec<(VfsFile, RelativePathBuf, Arc)>, + }, + AddFile { + file: VfsFile, + root: VfsRoot, + path: RelativePathBuf, + text: Arc, + }, + RemoveFile { + file: VfsFile, + }, + ChangeFile { + file: VfsFile, + text: Arc, + }, +} -- cgit v1.2.3 From 2ae05a6163d8b15f3d8a18a2ab713d1fbd83c505 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Dec 2018 13:18:55 +0300 Subject: vfs crate scaffold --- crates/ra_vfs/src/io.rs | 117 +++++++++++++++++++++++------------------------ crates/ra_vfs/src/lib.rs | 25 ++++++++-- 2 files changed, 79 insertions(+), 63 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index f90fe0e84..ce3271d48 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -6,67 +6,64 @@ use std::{ use walkdir::WalkDir; use crossbeam_channel::{Sender, Receiver}; +use thread_worker::{WorkerHandle, Worker}; -pub(crate) fn start_io() -> (JoinHandle<(), Sender<()>, Receiver()>) {} +#[derive(Debug)] +pub struct FileEvent { + pub path: PathBuf, + pub kind: FileEventKind, +} -// use crate::thread_watcher::{ThreadWatcher, Worker}; +#[derive(Debug)] +pub enum FileEventKind { + Add(String), +} -// #[derive(Debug)] -// pub struct FileEvent { -// pub path: PathBuf, -// pub kind: FileEventKind, -// } +pub fn start() -> (Worker)>, WorkerHandle) { + thread_worker::spawn::), _>( + "vfs", + 128, + |input_receiver, output_sender| { + input_receiver + .map(|path| { + log::debug!("loading {} ...", path.as_path().display()); + let events = load_root(path.as_path()); + log::debug!("... loaded {}", path.as_path().display()); + (path, events) + }) + .for_each(|it| output_sender.send(it)) + }, + ) +} -// #[derive(Debug)] -// pub enum FileEventKind { -// Add(String), -// } - -// pub fn roots_loader() -> (Worker)>, ThreadWatcher) { -// Worker::)>::spawn( -// "roots loader", -// 128, -// |input_receiver, output_sender| { -// input_receiver -// .map(|path| { -// log::debug!("loading {} ...", path.as_path().display()); -// let events = load_root(path.as_path()); -// log::debug!("... loaded {}", path.as_path().display()); -// (path, events) -// }) -// .for_each(|it| output_sender.send(it)) -// }, -// ) -// } - -// fn load_root(path: &Path) -> Vec { -// let mut res = Vec::new(); -// for entry in WalkDir::new(path) { -// let entry = match entry { -// Ok(entry) => entry, -// Err(e) => { -// log::warn!("watcher error: {}", e); -// continue; -// } -// }; -// if !entry.file_type().is_file() { -// continue; -// } -// let path = entry.path(); -// if path.extension().and_then(|os| os.to_str()) != Some("rs") { -// continue; -// } -// let text = match fs::read_to_string(path) { -// Ok(text) => text, -// Err(e) => { -// log::warn!("watcher error: {}", e); -// continue; -// } -// }; -// res.push(FileEvent { -// path: path.to_owned(), -// kind: FileEventKind::Add(text), -// }) -// } -// res -// } +fn load_root(path: &Path) -> Vec { + let mut res = Vec::new(); + for entry in WalkDir::new(path) { + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + log::warn!("watcher error: {}", e); + continue; + } + }; + if !entry.file_type().is_file() { + continue; + } + let path = entry.path(); + if path.extension().and_then(|os| os.to_str()) != Some("rs") { + continue; + } + let text = match fs::read_to_string(path) { + Ok(text) => text, + Err(e) => { + log::warn!("watcher error: {}", e); + continue; + } + }; + res.push(FileEvent { + path: path.to_owned(), + kind: FileEventKind::Add(text), + }) + } + res +} diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 8f6abadb7..b80c12058 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -15,6 +15,7 @@ mod arena; mod io; use std::{ + thread, cmp::Reverse, path::{Path, PathBuf}, ffi::OsStr, @@ -22,7 +23,12 @@ use std::{ }; use relative_path::RelativePathBuf; -use crate::arena::{ArenaId, Arena}; +use thread_worker::{WorkerHandle, Worker}; + +use crate::{ + arena::{ArenaId, Arena}, + io::FileEvent, +}; /// `RootFilter` is a predicate that checks if a file can belong to a root struct RootFilter { @@ -76,16 +82,24 @@ struct VfsFileData { text: Arc, } -#[derive(Default)] struct Vfs { roots: Arena, files: Arena, // pending_changes: Vec, + worker: Worker)>, + worker_handle: WorkerHandle, } impl Vfs { pub fn new(mut roots: Vec) -> Vfs { - let mut res = Vfs::default(); + let (worker, worker_handle) = io::start(); + + let mut res = Vfs { + roots: Arena::default(), + files: Arena::default(), + worker, + worker_handle, + }; roots.sort_by_key(|it| Reverse(it.as_os_str().len())); @@ -104,6 +118,11 @@ impl Vfs { pub fn commit_changes(&mut self) -> Vec { unimplemented!() } + + pub fn shutdown(self) -> thread::Result<()> { + let _ = self.worker.shutdown(); + self.worker_handle.shutdown() + } } #[derive(Debug, Clone)] -- cgit v1.2.3 From 99561cf2f20ad572c7149644a70cd7740c0f8b86 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Dec 2018 13:23:23 +0300 Subject: Add type alias --- crates/ra_vfs/src/io.rs | 4 +++- crates/ra_vfs/src/lib.rs | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index ce3271d48..257f1bf53 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -19,7 +19,9 @@ pub enum FileEventKind { Add(String), } -pub fn start() -> (Worker)>, WorkerHandle) { +pub(crate) type FsWorker = Worker)>; + +pub(crate) fn start() -> (FsWorker, WorkerHandle) { thread_worker::spawn::), _>( "vfs", 128, diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index b80c12058..d4ba2cb45 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -27,10 +27,11 @@ use thread_worker::{WorkerHandle, Worker}; use crate::{ arena::{ArenaId, Arena}, - io::FileEvent, + io::{FileEvent, FsWorker}, }; -/// `RootFilter` is a predicate that checks if a file can belong to a root +/// `RootFilter` is a predicate that checks if a file can belong to a root. If +/// several filters match a file (nested dirs), the most nested one wins. struct RootFilter { root: PathBuf, file_filter: fn(&Path) -> bool, @@ -86,7 +87,7 @@ struct Vfs { roots: Arena, files: Arena, // pending_changes: Vec, - worker: Worker)>, + worker: FsWorker, worker_handle: WorkerHandle, } @@ -101,6 +102,7 @@ impl Vfs { worker_handle, }; + // A hack to make nesting work. roots.sort_by_key(|it| Reverse(it.as_os_str().len())); for path in roots { -- cgit v1.2.3 From e69b05781f7fb0f0dfdcd4acb433dbcde9cbb7b7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Dec 2018 13:35:05 +0300 Subject: add io::Task --- crates/ra_vfs/src/io.rs | 53 ++++++++++++++++++++++++++++-------------------- crates/ra_vfs/src/lib.rs | 7 +++---- 2 files changed, 34 insertions(+), 26 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 257f1bf53..c46760583 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -4,38 +4,47 @@ use std::{ thread::JoinHandle, }; -use walkdir::WalkDir; +use walkdir::{DirEntry, WalkDir}; use crossbeam_channel::{Sender, Receiver}; -use thread_worker::{WorkerHandle, Worker}; +use thread_worker::{WorkerHandle}; + +use crate::VfsRoot; + +pub(crate) enum Task { + ScanRoot { + root: VfsRoot, + path: PathBuf, + filter: Box bool + Send>, + }, +} #[derive(Debug)] -pub struct FileEvent { - pub path: PathBuf, - pub kind: FileEventKind, +pub(crate) struct FileEvent { + pub(crate) path: PathBuf, + pub(crate) kind: FileEventKind, } #[derive(Debug)] -pub enum FileEventKind { +pub(crate) enum FileEventKind { Add(String), } -pub(crate) type FsWorker = Worker)>; +pub(crate) type Worker = thread_worker::Worker)>; + +pub(crate) fn start() -> (Worker, WorkerHandle) { + thread_worker::spawn("vfs", 128, |input_receiver, output_sender| { + input_receiver + .map(handle_task) + .for_each(|it| output_sender.send(it)) + }) +} -pub(crate) fn start() -> (FsWorker, WorkerHandle) { - thread_worker::spawn::), _>( - "vfs", - 128, - |input_receiver, output_sender| { - input_receiver - .map(|path| { - log::debug!("loading {} ...", path.as_path().display()); - let events = load_root(path.as_path()); - log::debug!("... loaded {}", path.as_path().display()); - (path, events) - }) - .for_each(|it| output_sender.send(it)) - }, - ) +fn handle_task(task: Task) -> (PathBuf, Vec) { + let Task::ScanRoot { path, .. } = task; + log::debug!("loading {} ...", path.as_path().display()); + let events = load_root(path.as_path()); + log::debug!("... loaded {}", path.as_path().display()); + (path, events) } fn load_root(path: &Path) -> Vec { diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index d4ba2cb45..8ce6b6ee0 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -2,8 +2,8 @@ //! //! When doing analysis, we don't want to do any IO, we want to keep all source //! code in memory. However, the actual source code is stored on disk, so you -//! need to get it into the memory in the first place somehow. VFS is the //! component which does this. +//! need to get it into the memory in the first place somehow. VFS is the //! //! It also is responsible for watching the disk for changes, and for merging //! editor state (modified, unsaved files) with disk state. @@ -23,11 +23,10 @@ use std::{ }; use relative_path::RelativePathBuf; -use thread_worker::{WorkerHandle, Worker}; +use thread_worker::{WorkerHandle}; use crate::{ arena::{ArenaId, Arena}, - io::{FileEvent, FsWorker}, }; /// `RootFilter` is a predicate that checks if a file can belong to a root. If @@ -87,7 +86,7 @@ struct Vfs { roots: Arena, files: Arena, // pending_changes: Vec, - worker: FsWorker, + worker: io::Worker, worker_handle: WorkerHandle, } -- cgit v1.2.3 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 From d0bab735c81d616629a54cca7b35c2f7cd26408a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 10:28:57 +0300 Subject: add root & path info to remove event --- crates/ra_vfs/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 792f722a7..20482c396 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -202,7 +202,7 @@ impl Vfs { VfsChange::ChangeFile { file, text } } else { self.remove_file(file); - VfsChange::RemoveFile { file } + VfsChange::RemoveFile { root, file, path } }; self.pending_changes.push(change); } @@ -260,13 +260,15 @@ pub enum VfsChange { files: Vec<(VfsFile, RelativePathBuf, Arc)>, }, AddFile { - file: VfsFile, root: VfsRoot, + file: VfsFile, path: RelativePathBuf, text: Arc, }, RemoveFile { + root: VfsRoot, file: VfsFile, + path: RelativePathBuf, }, ChangeFile { file: VfsFile, -- cgit v1.2.3 From 815a0e577821a3876aa4c79c13200607acadcd2f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 10:29:48 +0300 Subject: doc comment --- crates/ra_vfs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 20482c396..6c1af5ef9 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -212,6 +212,7 @@ impl Vfs { mem::replace(&mut self.pending_changes, Vec::new()) } + /// Sutdown the VFS and terminate the background watching thread. pub fn shutdown(self) -> thread::Result<()> { let _ = self.worker.shutdown(); self.worker_handle.shutdown() -- cgit v1.2.3 From a5ef8ad05b7c1f7148c59814b55d641fd75aff75 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 15:04:15 +0300 Subject: swtich lsp server to vfs --- crates/ra_vfs/src/io.rs | 7 +++++++ crates/ra_vfs/src/lib.rs | 53 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 6 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 178c9beff..be400bae9 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -1,4 +1,5 @@ use std::{ + fmt, fs, path::{Path, PathBuf}, }; @@ -20,6 +21,12 @@ pub struct TaskResult { pub(crate) files: Vec<(RelativePathBuf, String)>, } +impl fmt::Debug for TaskResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("TaskResult { ... }") + } +} + pub(crate) type Worker = thread_worker::Worker; pub(crate) fn start() -> (Worker, WorkerHandle) { diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 6c1af5ef9..dc980c3d2 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -15,6 +15,7 @@ mod arena; mod io; use std::{ + fmt, mem, thread, cmp::Reverse, @@ -34,6 +35,8 @@ use crate::{ arena::{ArenaId, Arena}, }; +pub use crate::io::TaskResult as VfsTask; + /// `RootFilter` is a predicate that checks if a file can belong to a root. If /// several filters match a file (nested dirs), the most nested one wins. struct RootFilter { @@ -68,7 +71,7 @@ fn has_rs_extension(p: &Path) -> bool { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub struct VfsRoot(u32); +pub struct VfsRoot(pub u32); impl ArenaId for VfsRoot { fn from_u32(idx: u32) -> VfsRoot { @@ -80,7 +83,7 @@ impl ArenaId for VfsRoot { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub struct VfsFile(u32); +pub struct VfsFile(pub u32); impl ArenaId for VfsFile { fn from_u32(idx: u32) -> VfsFile { @@ -106,8 +109,14 @@ pub struct Vfs { worker_handle: WorkerHandle, } +impl fmt::Debug for Vfs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Vfs { ... }") + } +} + impl Vfs { - pub fn new(mut roots: Vec) -> Vfs { + pub fn new(mut roots: Vec) -> (Vfs, Vec) { let (worker, worker_handle) = io::start(); let mut res = Vfs { @@ -142,7 +151,32 @@ impl Vfs { }; res.worker.inp.send(task); } - res + let roots = res.roots.iter().map(|(id, _)| id).collect(); + (res, roots) + } + + pub fn path2file(&self, path: &Path) -> Option { + if let Some((_root, _path, Some(file))) = self.find_root(path) { + return Some(file); + } + None + } + + pub fn file2path(&self, file: VfsFile) -> PathBuf { + let rel_path = &self.files[file].path; + let root_path = &self.roots[self.files[file].root].root; + rel_path.to_path(root_path) + } + + pub fn file_for_path(&self, path: &Path) -> Option { + if let Some((_root, _path, Some(file))) = self.find_root(path) { + return Some(file); + } + None + } + + pub fn load(&mut self, path: &Path) -> Option { + None } pub fn task_receiver(&self) -> &Receiver { @@ -163,14 +197,17 @@ impl Vfs { self.pending_changes.push(change); } - pub fn add_file_overlay(&mut self, path: &Path, text: String) { + pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option { + let mut res = None; if let Some((root, path, file)) = self.find_root(path) { let text = Arc::new(text); let change = if let Some(file) = file { + res = Some(file); self.change_file(file, Arc::clone(&text)); VfsChange::ChangeFile { file, text } } else { let file = self.add_file(root, path.clone(), Arc::clone(&text)); + res = Some(file); VfsChange::AddFile { file, text, @@ -180,6 +217,7 @@ impl Vfs { }; self.pending_changes.push(change); } + res } pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { @@ -192,9 +230,11 @@ impl Vfs { } } - pub fn remove_file_overlay(&mut self, path: &Path) { + pub fn remove_file_overlay(&mut self, path: &Path) -> Option { + let mut res = None; if let Some((root, path, file)) = self.find_root(path) { let file = file.expect("can't remove a file which wasn't added"); + res = Some(file); 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); @@ -206,6 +246,7 @@ impl Vfs { }; self.pending_changes.push(change); } + res } pub fn commit_changes(&mut self) -> Vec { -- cgit v1.2.3 From 7b6bafa631e6272946da568e9da7c3adc01ba625 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 15:40:42 +0300 Subject: fix syc --- crates/ra_vfs/src/lib.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index dc980c3d2..3a68039f0 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -132,6 +132,7 @@ impl Vfs { roots.sort_by_key(|it| Reverse(it.as_os_str().len())); for (i, path) in roots.iter().enumerate() { let root = res.roots.alloc(RootFilter::new(path.clone())); + res.root2files.insert(root, Default::default()); let nested = roots[..i] .iter() .filter(|it| it.starts_with(path)) @@ -155,6 +156,10 @@ impl Vfs { (res, roots) } + pub fn root2path(&self, root: VfsRoot) -> PathBuf { + self.roots[root].root.clone() + } + pub fn path2file(&self, path: &Path) -> Option { if let Some((_root, _path, Some(file))) = self.find_root(path) { return Some(file); @@ -176,6 +181,23 @@ impl Vfs { } pub fn load(&mut self, path: &Path) -> Option { + if let Some((root, rel_path, file)) = self.find_root(path) { + return if let Some(file) = file { + Some(file) + } else { + let text = fs::read_to_string(path).unwrap_or_default(); + let text = Arc::new(text); + let file = self.add_file(root, rel_path.clone(), Arc::clone(&text)); + let change = VfsChange::AddFile { + file, + text, + root, + path: rel_path, + }; + self.pending_changes.push(change); + Some(file) + }; + } None } @@ -262,10 +284,7 @@ impl Vfs { 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); + self.root2files.get_mut(&root).unwrap().insert(file); file } -- cgit v1.2.3 From 1b946ef8a61fa520459435d4e1cd674e0c550771 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 15:56:51 +0300 Subject: File can be opened before the root is scanned --- crates/ra_vfs/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'crates/ra_vfs/src') diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 3a68039f0..4de07b093 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -207,11 +207,23 @@ impl Vfs { pub fn handle_task(&mut self, task: io::TaskResult) { let mut files = Vec::new(); + // While we were scanning the root in the backgound, a file might have + // been open in the editor, so we need to account for that. + let exising = self.root2files[&task.root] + .iter() + .map(|&file| (self.files[file].path.clone(), file)) + .collect::>(); for (path, text) in task.files { + if let Some(&file) = exising.get(&path) { + let text = Arc::clone(&self.files[file].text); + files.push((file, path, text)); + continue; + } 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, -- cgit v1.2.3