aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-01-26 12:11:47 +0000
committerAleksey Kladov <[email protected]>2019-01-26 13:28:04 +0000
commit20d7a431fd6e3e363e698a2e464160640868597b (patch)
treeb154835f03a0331aba6a5d6a2a6655fca10acd58
parent3feaf2a008be289dc4091f25e0925d0f427d6b65 (diff)
refactor-fvs
-rw-r--r--crates/ra_vfs/src/io.rs229
-rw-r--r--crates/ra_vfs/src/io/watcher.rs200
-rw-r--r--crates/ra_vfs/src/lib.rs146
3 files changed, 242 insertions, 333 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index 7ca1e9835..669240488 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -1,19 +1,23 @@
1use std::{fs, sync::Arc, thread}; 1use std::{
2 2 fs,
3use crossbeam_channel::{Receiver, Sender}; 3 path::{Path, PathBuf},
4 sync::{mpsc, Arc},
5 thread,
6 time::Duration,
7};
8use crossbeam_channel::{Receiver, Sender, SendError};
4use relative_path::RelativePathBuf; 9use relative_path::RelativePathBuf;
5use thread_worker::WorkerHandle; 10use thread_worker::WorkerHandle;
6use walkdir::WalkDir; 11use walkdir::WalkDir;
12use parking_lot::Mutex;
13use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
7 14
8mod watcher; 15use crate::{RootConfig, Roots, VfsRoot};
9use watcher::Watcher;
10
11use crate::{RootFilter, Roots, VfsRoot};
12 16
13pub(crate) enum Task { 17pub(crate) enum Task {
14 AddRoot { 18 AddRoot {
15 root: VfsRoot, 19 root: VfsRoot,
16 filter: Arc<RootFilter>, 20 config: Arc<RootConfig>,
17 }, 21 },
18} 22}
19 23
@@ -39,6 +43,15 @@ pub enum TaskResult {
39 }, 43 },
40} 44}
41 45
46#[derive(Debug)]
47enum ChangeKind {
48 Create,
49 Write,
50 Remove,
51}
52
53const WATCHER_DELAY: Duration = Duration::from_millis(250);
54
42pub(crate) struct Worker { 55pub(crate) struct Worker {
43 worker: thread_worker::Worker<Task, TaskResult>, 56 worker: thread_worker::Worker<Task, TaskResult>,
44 worker_handle: WorkerHandle, 57 worker_handle: WorkerHandle,
@@ -48,21 +61,36 @@ impl Worker {
48 pub(crate) fn start(roots: Arc<Roots>) -> Worker { 61 pub(crate) fn start(roots: Arc<Roots>) -> Worker {
49 let (worker, worker_handle) = 62 let (worker, worker_handle) =
50 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { 63 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| {
51 let mut watcher = match Watcher::start(roots, output_sender.clone()) { 64 let (notify_sender, notify_receiver) = mpsc::channel();
52 Ok(w) => Some(w), 65 let watcher = notify::watcher(notify_sender, WATCHER_DELAY)
53 Err(e) => { 66 .map_err(|e| log::error!("failed to spawn notify {}", e))
54 log::error!("could not start watcher: {}", e); 67 .ok();
55 None 68 let ctx = WatcherCtx {
56 } 69 roots,
70 watcher: Arc::new(Mutex::new(watcher)),
71 sender: output_sender,
57 }; 72 };
58 let res = input_receiver 73 let thread = thread::spawn({
59 .into_iter() 74 let ctx = ctx.clone();
60 .filter_map(|t| handle_task(t, &mut watcher)) 75 move || {
61 .try_for_each(|it| output_sender.send(it)); 76 let _ = notify_receiver
62 if let Some(watcher) = watcher { 77 .into_iter()
63 let _ = watcher.shutdown(); 78 // forward relevant events only
79 .try_for_each(|change| ctx.handle_debounced_event(change));
80 }
81 });
82 let res1 = input_receiver.into_iter().try_for_each(|t| match t {
83 Task::AddRoot { root, config } => watch_root(&ctx, root, Arc::clone(&config)),
84 });
85 drop(ctx.watcher.lock().take());
86 drop(ctx);
87 let res2 = thread.join();
88 match &res2 {
89 Ok(()) => log::info!("... Watcher terminated with ok"),
90 Err(_) => log::error!("... Watcher terminated with err"),
64 } 91 }
65 res.unwrap() 92 res1.unwrap();
93 res2.unwrap();
66 }); 94 });
67 Worker { 95 Worker {
68 worker, 96 worker,
@@ -84,46 +112,141 @@ impl Worker {
84 } 112 }
85} 113}
86 114
87fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> { 115fn watch_root(
88 match task { 116 woker: &WatcherCtx,
89 Task::AddRoot { root, filter } => { 117 root: VfsRoot,
90 if let Some(watcher) = watcher { 118 config: Arc<RootConfig>,
91 watcher.watch_root(&filter) 119) -> Result<(), SendError<TaskResult>> {
120 let mut guard = woker.watcher.lock();
121 log::debug!("loading {} ...", config.root.as_path().display());
122 let files = watch_recursive(guard.as_mut(), config.root.as_path(), &*config)
123 .into_iter()
124 .filter_map(|path| {
125 let abs_path = path.to_path(&config.root);
126 let text = fs::read_to_string(abs_path)
127 .map_err(|e| log::warn!("watcher error: {}", e))
128 .ok()?;
129 Some((path, text))
130 })
131 .collect();
132 woker
133 .sender
134 .send(TaskResult::BulkLoadRoot { root, files })?;
135 log::debug!("... loaded {}", config.root.as_path().display());
136 Ok(())
137}
138
139#[derive(Clone)]
140struct WatcherCtx {
141 roots: Arc<Roots>,
142 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
143 sender: Sender<TaskResult>,
144}
145
146impl WatcherCtx {
147 fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<(), SendError<TaskResult>> {
148 match ev {
149 DebouncedEvent::NoticeWrite(_)
150 | DebouncedEvent::NoticeRemove(_)
151 | DebouncedEvent::Chmod(_) => {
152 // ignore
153 }
154 DebouncedEvent::Rescan => {
155 // TODO rescan all roots
156 }
157 DebouncedEvent::Create(path) => {
158 self.handle_change(path, ChangeKind::Create)?;
159 }
160 DebouncedEvent::Write(path) => {
161 self.handle_change(path, ChangeKind::Write)?;
92 } 162 }
93 log::debug!("loading {} ...", filter.root.as_path().display()); 163 DebouncedEvent::Remove(path) => {
94 let files = load_root(filter.as_ref()); 164 self.handle_change(path, ChangeKind::Remove)?;
95 log::debug!("... loaded {}", filter.root.as_path().display()); 165 }
96 Some(TaskResult::BulkLoadRoot { root, files }) 166 DebouncedEvent::Rename(src, dst) => {
167 self.handle_change(src, ChangeKind::Remove)?;
168 self.handle_change(dst, ChangeKind::Create)?;
169 }
170 DebouncedEvent::Error(err, path) => {
171 // TODO should we reload the file contents?
172 log::warn!("watcher error \"{}\", {:?}", err, path);
173 }
174 }
175 Ok(())
176 }
177
178 fn handle_change(&self, path: PathBuf, kind: ChangeKind) -> Result<(), SendError<TaskResult>> {
179 let (root, rel_path) = match self.roots.find(&path) {
180 None => return Ok(()),
181 Some(it) => it,
182 };
183 let config = &self.roots[root];
184 match kind {
185 ChangeKind::Create => {
186 let mut paths = Vec::new();
187 if path.is_dir() {
188 let mut guard = self.watcher.lock();
189 paths.extend(watch_recursive(guard.as_mut(), &path, &config));
190 } else {
191 paths.push(rel_path);
192 }
193 paths
194 .into_iter()
195 .filter_map(|rel_path| {
196 let abs_path = rel_path.to_path(&config.root);
197 let text = fs::read_to_string(&abs_path)
198 .map_err(|e| log::warn!("watcher failed {}", e))
199 .ok()?;
200 Some((rel_path, text))
201 })
202 .try_for_each(|(path, text)| {
203 self.sender
204 .send(TaskResult::AddSingleFile { root, path, text })
205 })?
206 }
207 ChangeKind::Write => match fs::read_to_string(&path) {
208 Err(e) => log::warn!("watcher failed {}", e),
209 Ok(text) => self.sender.send(TaskResult::ChangeSingleFile {
210 root,
211 path: rel_path,
212 text,
213 })?,
214 },
215 ChangeKind::Remove => self.sender.send(TaskResult::RemoveSingleFile {
216 root,
217 path: rel_path,
218 })?,
97 } 219 }
220 Ok(())
98 } 221 }
99} 222}
100 223
101fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> { 224fn watch_recursive(
102 let mut res = Vec::new(); 225 mut watcher: Option<&mut RecommendedWatcher>,
103 for entry in WalkDir::new(&filter.root) 226 dir: &Path,
227 config: &RootConfig,
228) -> Vec<RelativePathBuf> {
229 let mut files = Vec::new();
230 for entry in WalkDir::new(dir)
104 .into_iter() 231 .into_iter()
105 .filter_entry(filter.entry_filter()) 232 .filter_entry(|it| config.contains(it.path()).is_some())
233 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
106 { 234 {
107 let entry = match entry { 235 if entry.file_type().is_dir() {
108 Ok(entry) => entry, 236 if let Some(watcher) = &mut watcher {
109 Err(e) => { 237 watch_one(watcher, entry.path());
110 log::warn!("watcher error: {}", e);
111 continue;
112 } 238 }
113 }; 239 } else {
114 if !entry.file_type().is_file() { 240 let path = config.contains(entry.path()).unwrap();
115 continue; 241 files.push(path.to_owned());
116 } 242 }
117 let path = entry.path();
118 let text = match fs::read_to_string(path) {
119 Ok(text) => text,
120 Err(e) => {
121 log::warn!("watcher error: {}", e);
122 continue;
123 }
124 };
125 let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap();
126 res.push((path.to_owned(), text))
127 } 243 }
128 res 244 files
245}
246
247fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) {
248 match watcher.watch(dir, RecursiveMode::NonRecursive) {
249 Ok(()) => log::debug!("watching \"{}\"", dir.display()),
250 Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
251 }
129} 252}
diff --git a/crates/ra_vfs/src/io/watcher.rs b/crates/ra_vfs/src/io/watcher.rs
deleted file mode 100644
index ff6775f59..000000000
--- a/crates/ra_vfs/src/io/watcher.rs
+++ /dev/null
@@ -1,200 +0,0 @@
1use crate::{io, RootFilter, Roots, VfsRoot};
2use crossbeam_channel::Sender;
3use drop_bomb::DropBomb;
4use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
5use parking_lot::Mutex;
6use std::{
7 fs,
8 path::{Path, PathBuf},
9 sync::{mpsc, Arc},
10 thread,
11 time::Duration,
12};
13use walkdir::WalkDir;
14
15#[derive(Debug)]
16enum ChangeKind {
17 Create,
18 Write,
19 Remove,
20}
21
22const WATCHER_DELAY: Duration = Duration::from_millis(250);
23
24pub(crate) struct Watcher {
25 thread: thread::JoinHandle<()>,
26 bomb: DropBomb,
27 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
28}
29
30impl Watcher {
31 pub(crate) fn start(
32 roots: Arc<Roots>,
33 output_sender: Sender<io::TaskResult>,
34 ) -> Result<Watcher, Box<std::error::Error>> {
35 let (input_sender, input_receiver) = mpsc::channel();
36 let watcher = Arc::new(Mutex::new(Some(notify::watcher(
37 input_sender,
38 WATCHER_DELAY,
39 )?)));
40 let sender = output_sender.clone();
41 let watcher_clone = watcher.clone();
42 let thread = thread::spawn(move || {
43 let worker = WatcherWorker {
44 roots,
45 watcher: watcher_clone,
46 sender,
47 };
48 input_receiver
49 .into_iter()
50 // forward relevant events only
51 .try_for_each(|change| worker.handle_debounced_event(change))
52 .unwrap()
53 });
54 Ok(Watcher {
55 thread,
56 watcher,
57 bomb: DropBomb::new(format!("Watcher was not shutdown")),
58 })
59 }
60
61 pub fn watch_root(&mut self, filter: &RootFilter) {
62 for res in WalkDir::new(&filter.root)
63 .into_iter()
64 .filter_entry(filter.entry_filter())
65 {
66 match res {
67 Ok(entry) => {
68 if entry.file_type().is_dir() {
69 watch_one(self.watcher.as_ref(), entry.path());
70 }
71 }
72 Err(e) => log::warn!("watcher error: {}", e),
73 }
74 }
75 }
76
77 pub fn shutdown(mut self) -> thread::Result<()> {
78 self.bomb.defuse();
79 drop(self.watcher.lock().take());
80 let res = self.thread.join();
81 match &res {
82 Ok(()) => log::info!("... Watcher terminated with ok"),
83 Err(_) => log::error!("... Watcher terminated with err"),
84 }
85 res
86 }
87}
88
89struct WatcherWorker {
90 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
91 roots: Arc<Roots>,
92 sender: Sender<io::TaskResult>,
93}
94
95impl WatcherWorker {
96 fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<(), Box<std::error::Error>> {
97 match ev {
98 DebouncedEvent::NoticeWrite(_)
99 | DebouncedEvent::NoticeRemove(_)
100 | DebouncedEvent::Chmod(_) => {
101 // ignore
102 }
103 DebouncedEvent::Rescan => {
104 // TODO rescan all roots
105 }
106 DebouncedEvent::Create(path) => {
107 self.handle_change(path, ChangeKind::Create);
108 }
109 DebouncedEvent::Write(path) => {
110 self.handle_change(path, ChangeKind::Write);
111 }
112 DebouncedEvent::Remove(path) => {
113 self.handle_change(path, ChangeKind::Remove);
114 }
115 DebouncedEvent::Rename(src, dst) => {
116 self.handle_change(src, ChangeKind::Remove);
117 self.handle_change(dst, ChangeKind::Create);
118 }
119 DebouncedEvent::Error(err, path) => {
120 // TODO should we reload the file contents?
121 log::warn!("watcher error \"{}\", {:?}", err, path);
122 }
123 }
124 Ok(())
125 }
126
127 fn handle_change(&self, path: PathBuf, kind: ChangeKind) {
128 if let Err(e) = self.try_handle_change(path, kind) {
129 log::warn!("watcher error: {}", e)
130 }
131 }
132
133 fn try_handle_change(
134 &self,
135 path: PathBuf,
136 kind: ChangeKind,
137 ) -> Result<(), Box<std::error::Error>> {
138 let (root, rel_path) = match self.roots.find(&path) {
139 Some(x) => x,
140 None => return Ok(()),
141 };
142 match kind {
143 ChangeKind::Create => {
144 if path.is_dir() {
145 self.watch_recursive(&path, root);
146 } else {
147 let text = fs::read_to_string(&path)?;
148 self.sender.send(io::TaskResult::AddSingleFile {
149 root,
150 path: rel_path,
151 text,
152 })?
153 }
154 }
155 ChangeKind::Write => {
156 let text = fs::read_to_string(&path)?;
157 self.sender.send(io::TaskResult::ChangeSingleFile {
158 root,
159 path: rel_path,
160 text,
161 })?
162 }
163 ChangeKind::Remove => self.sender.send(io::TaskResult::RemoveSingleFile {
164 root,
165 path: rel_path,
166 })?,
167 }
168 Ok(())
169 }
170
171 fn watch_recursive(&self, dir: &Path, root: VfsRoot) {
172 let filter = &self.roots[root];
173 for res in WalkDir::new(dir)
174 .into_iter()
175 .filter_entry(filter.entry_filter())
176 {
177 match res {
178 Ok(entry) => {
179 if entry.file_type().is_dir() {
180 watch_one(self.watcher.as_ref(), entry.path());
181 } else {
182 // emit only for files otherwise we will cause watch_recursive to be called again with a dir that we are already watching
183 // emit as create because we haven't seen it yet
184 self.handle_change(entry.path().to_path_buf(), ChangeKind::Create);
185 }
186 }
187 Err(e) => log::warn!("watcher error: {}", e),
188 }
189 }
190 }
191}
192
193fn watch_one(watcher: &Mutex<Option<RecommendedWatcher>>, dir: &Path) {
194 if let Some(watcher) = watcher.lock().as_mut() {
195 match watcher.watch(dir, RecursiveMode::NonRecursive) {
196 Ok(()) => log::debug!("watching \"{}\"", dir.display()),
197 Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
198 }
199 }
200}
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index 70a13f765..71a3f807d 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -18,94 +18,78 @@ mod io;
18use std::{ 18use std::{
19 cmp::Reverse, 19 cmp::Reverse,
20 fmt, fs, mem, 20 fmt, fs, mem,
21 ops::{Deref, DerefMut},
22 path::{Path, PathBuf}, 21 path::{Path, PathBuf},
23 sync::Arc, 22 sync::Arc,
24 thread, 23 thread,
25}; 24};
26 25
27use crossbeam_channel::Receiver; 26use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId}; 27use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29use relative_path::{Component, RelativePath, RelativePathBuf}; 28use relative_path::{Component, RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet}; 29use rustc_hash::{FxHashMap, FxHashSet};
31use walkdir::DirEntry;
32 30
33pub use crate::io::TaskResult as VfsTask; 31pub use crate::io::TaskResult as VfsTask;
34use io::{TaskResult, Worker}; 32use io::{TaskResult, Worker};
35 33
36/// `RootFilter` is a predicate that checks if a file can belong to a root. If 34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37/// several filters match a file (nested dirs), the most nested one wins. 35pub struct VfsRoot(pub RawId);
38pub(crate) struct RootFilter { 36impl_arena_id!(VfsRoot);
37
38/// Describes the contents of a single source root.
39///
40/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` whihc
41/// specifes the source root or as a function whihc takes a `PathBuf` and
42/// returns `true` iff path belongs to the source root
43pub(crate) struct RootConfig {
39 root: PathBuf, 44 root: PathBuf,
40 filter: fn(&Path, &RelativePath) -> bool,
41 excluded_dirs: Vec<PathBuf>, 45 excluded_dirs: Vec<PathBuf>,
42} 46}
43 47
44impl RootFilter { 48pub(crate) struct Roots {
45 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter { 49 roots: Arena<VfsRoot, Arc<RootConfig>>,
46 RootFilter { 50}
51
52impl std::ops::Deref for Roots {
53 type Target = Arena<VfsRoot, Arc<RootConfig>>;
54 fn deref(&self) -> &Self::Target {
55 &self.roots
56 }
57}
58
59impl RootConfig {
60 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
61 RootConfig {
47 root, 62 root,
48 filter: default_filter,
49 excluded_dirs, 63 excluded_dirs,
50 } 64 }
51 } 65 }
52 /// Check if this root can contain `path`. NB: even if this returns 66 /// Cheks if root contains a path and returns a root-relative path.
53 /// true, the `path` might actually be conained in some nested root. 67 pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
54 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { 68 // First, check excluded dirs
55 let rel_path = path.strip_prefix(&self.root).ok()?; 69 if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
56 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
57 if !(self.filter)(path, rel_path.as_relative_path()) {
58 return None; 70 return None;
59 } 71 }
60 Some(rel_path) 72 let rel_path = path.strip_prefix(&self.root).ok()?;
61 } 73 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
62
63 pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a {
64 move |entry: &DirEntry| {
65 if entry.file_type().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path())
66 {
67 // do not walk nested roots
68 false
69 } else {
70 self.can_contain(entry.path()).is_some()
71 }
72 }
73 }
74}
75 74
76pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { 75 // Ignore some common directories.
77 if path.is_dir() { 76 //
77 // FIXME: don't hard-code, specify at source-root creation time using
78 // gitignore
78 for (i, c) in rel_path.components().enumerate() { 79 for (i, c) in rel_path.components().enumerate() {
79 if let Component::Normal(c) = c { 80 if let Component::Normal(c) = c {
80 // TODO hardcoded for now
81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { 81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
82 return false; 82 return None;
83 } 83 }
84 } 84 }
85 } 85 }
86 true
87 } else {
88 rel_path.extension() == Some("rs")
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub struct VfsRoot(pub RawId);
94impl_arena_id!(VfsRoot);
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct VfsFile(pub RawId);
98impl_arena_id!(VfsFile);
99 86
100struct VfsFileData { 87 if path.is_file() && rel_path.extension() != Some("rs") {
101 root: VfsRoot, 88 return None;
102 path: RelativePathBuf, 89 }
103 is_overlayed: bool,
104 text: Arc<String>,
105}
106 90
107pub(crate) struct Roots { 91 Some(rel_path)
108 roots: Arena<VfsRoot, Arc<RootFilter>>, 92 }
109} 93}
110 94
111impl Roots { 95impl Roots {
@@ -120,59 +104,61 @@ impl Roots {
120 .map(|it| it.clone()) 104 .map(|it| it.clone())
121 .collect::<Vec<_>>(); 105 .collect::<Vec<_>>();
122 106
123 let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots)); 107 let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
124 108
125 roots.alloc(root_filter.clone()); 109 roots.alloc(config.clone());
126 } 110 }
127 Roots { roots } 111 Roots { roots }
128 } 112 }
129 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { 113 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
130 self.roots 114 self.roots
131 .iter() 115 .iter()
132 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it))) 116 .find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
133 } 117 }
134} 118}
135 119
136impl Deref for Roots { 120#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
137 type Target = Arena<VfsRoot, Arc<RootFilter>>; 121pub struct VfsFile(pub RawId);
138 fn deref(&self) -> &Self::Target { 122impl_arena_id!(VfsFile);
139 &self.roots
140 }
141}
142 123
143impl DerefMut for Roots { 124struct VfsFileData {
144 fn deref_mut(&mut self) -> &mut Self::Target { 125 root: VfsRoot,
145 &mut self.roots 126 path: RelativePathBuf,
146 } 127 is_overlayed: bool,
128 text: Arc<String>,
147} 129}
148 130
149pub struct Vfs { 131pub struct Vfs {
150 roots: Arc<Roots>, 132 roots: Arc<Roots>,
151 files: Arena<VfsFile, VfsFileData>, 133 files: Arena<VfsFile, VfsFileData>,
152 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, 134 root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
153 pending_changes: Vec<VfsChange>, 135 pending_changes: Vec<VfsChange>,
154 worker: Worker, 136 worker: Worker,
155} 137}
156 138
157impl fmt::Debug for Vfs { 139impl fmt::Debug for Vfs {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 f.write_str("Vfs { ... }") 141 f.debug_struct("Vfs")
142 .field("n_roots", &self.roots.len())
143 .field("n_files", &self.files.len())
144 .field("n_pending_changes", &self.pending_changes.len())
145 .finish()
160 } 146 }
161} 147}
162 148
163impl Vfs { 149impl Vfs {
164 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 150 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
165 let roots = Arc::new(Roots::new(roots)); 151 let roots = Arc::new(Roots::new(roots));
166 let worker = io::Worker::start(roots.clone()); 152 let worker = io::Worker::start(Arc::clone(&roots));
167 let mut root2files = FxHashMap::default(); 153 let mut root2files = ArenaMap::default();
168 154
169 for (root, filter) in roots.iter() { 155 for (root, config) in roots.iter() {
170 root2files.insert(root, Default::default()); 156 root2files.insert(root, Default::default());
171 worker 157 worker
172 .sender() 158 .sender()
173 .send(io::Task::AddRoot { 159 .send(io::Task::AddRoot {
174 root, 160 root,
175 filter: filter.clone(), 161 config: Arc::clone(config),
176 }) 162 })
177 .unwrap(); 163 .unwrap();
178 } 164 }
@@ -242,7 +228,7 @@ impl Vfs {
242 let mut cur_files = Vec::new(); 228 let mut cur_files = Vec::new();
243 // While we were scanning the root in the backgound, a file might have 229 // While we were scanning the root in the backgound, a file might have
244 // been open in the editor, so we need to account for that. 230 // been open in the editor, so we need to account for that.
245 let exising = self.root2files[&root] 231 let exising = self.root2files[root]
246 .iter() 232 .iter()
247 .map(|&file| (self.files[file].path.clone(), file)) 233 .map(|&file| (self.files[file].path.clone(), file))
248 .collect::<FxHashMap<_, _>>(); 234 .collect::<FxHashMap<_, _>>();
@@ -384,7 +370,7 @@ impl Vfs {
384 is_overlayed, 370 is_overlayed,
385 }; 371 };
386 let file = self.files.alloc(data); 372 let file = self.files.alloc(data);
387 self.root2files.get_mut(&root).unwrap().insert(file); 373 self.root2files.get_mut(root).unwrap().insert(file);
388 file 374 file
389 } 375 }
390 376
@@ -399,7 +385,7 @@ impl Vfs {
399 self.files[file].text = Default::default(); 385 self.files[file].text = Default::default();
400 self.files[file].path = Default::default(); 386 self.files[file].path = Default::default();
401 let root = self.files[file].root; 387 let root = self.files[file].root;
402 let removed = self.root2files.get_mut(&root).unwrap().remove(&file); 388 let removed = self.root2files.get_mut(root).unwrap().remove(&file);
403 assert!(removed); 389 assert!(removed);
404 } 390 }
405 391
@@ -410,7 +396,7 @@ impl Vfs {
410 } 396 }
411 397
412 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { 398 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
413 self.root2files[&root] 399 self.root2files[root]
414 .iter() 400 .iter()
415 .map(|&it| it) 401 .map(|&it| it)
416 .find(|&file| self.files[file].path == path) 402 .find(|&file| self.files[file].path == path)