aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-26 15:13:44 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-26 15:13:44 +0000
commita8d32c4d1ae4b3e4276f7a97b6c6e5f95f91e67a (patch)
tree9e56b095a073ff3c82322302356f4d096a70afcd
parent5af7b2f4af51291fa4a0549c549796ba0520927b (diff)
parent9f16892b94817d144f37dfe0081b39aacec65635 (diff)
Merge #671
671: Makre VFS slightly less super obscure r=vemoo a=matklad I've decided to better understand what we do in VFS, and this turns out to be really hard. Jugling threads and channels is one of the most unfortunately arcane bits of rust... I had some success though by flattenning the structure so that all channel & thread creation routines are on one screen. r? @vemoo Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/ra_vfs/src/io.rs271
-rw-r--r--crates/ra_vfs/src/io/watcher.rs200
-rw-r--r--crates/ra_vfs/src/lib.rs146
3 files changed, 281 insertions, 336 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index 7ca1e9835..d764c534a 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -1,19 +1,22 @@
1use std::{fs, sync::Arc, thread}; 1use std::{
2 2 fs,
3use crossbeam_channel::{Receiver, Sender}; 3 thread,
4 path::{Path, PathBuf},
5 sync::{mpsc, Arc},
6 time::Duration,
7};
8use crossbeam_channel::{Receiver, Sender, unbounded, RecvError, select};
4use relative_path::RelativePathBuf; 9use relative_path::RelativePathBuf;
5use thread_worker::WorkerHandle; 10use thread_worker::WorkerHandle;
6use walkdir::WalkDir; 11use walkdir::WalkDir;
12use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
7 13
8mod watcher; 14use crate::{RootConfig, Roots, VfsRoot};
9use watcher::Watcher;
10
11use crate::{RootFilter, Roots, VfsRoot};
12 15
13pub(crate) enum Task { 16pub(crate) enum Task {
14 AddRoot { 17 AddRoot {
15 root: VfsRoot, 18 root: VfsRoot,
16 filter: Arc<RootFilter>, 19 config: Arc<RootConfig>,
17 }, 20 },
18} 21}
19 22
@@ -39,6 +42,15 @@ pub enum TaskResult {
39 }, 42 },
40} 43}
41 44
45#[derive(Debug)]
46enum ChangeKind {
47 Create,
48 Write,
49 Remove,
50}
51
52const WATCHER_DELAY: Duration = Duration::from_millis(250);
53
42pub(crate) struct Worker { 54pub(crate) struct Worker {
43 worker: thread_worker::Worker<Task, TaskResult>, 55 worker: thread_worker::Worker<Task, TaskResult>,
44 worker_handle: WorkerHandle, 56 worker_handle: WorkerHandle,
@@ -46,24 +58,75 @@ pub(crate) struct Worker {
46 58
47impl Worker { 59impl Worker {
48 pub(crate) fn start(roots: Arc<Roots>) -> Worker { 60 pub(crate) fn start(roots: Arc<Roots>) -> Worker {
49 let (worker, worker_handle) = 61 // This is a pretty elaborate setup of threads & channels! It is
50 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { 62 // explained by the following concerns:
51 let mut watcher = match Watcher::start(roots, output_sender.clone()) { 63 // * we need to burn a thread translating from notify's mpsc to
52 Ok(w) => Some(w), 64 // crossbeam_channel.
53 Err(e) => { 65 // * we want to read all files from a single thread, to gurantee that
54 log::error!("could not start watcher: {}", e); 66 // we always get fresher versions and never go back in time.
55 None 67 // * we want to tear down everything neatly during shutdown.
68 let (worker, worker_handle) = thread_worker::spawn(
69 "vfs",
70 128,
71 // This are the channels we use to communicate with outside world.
72 // If `input_receiver` is closed we need to tear ourselves down.
73 // `output_sender` should not be closed unless the parent died.
74 move |input_receiver, output_sender| {
75 // These are `std` channels notify will send events to
76 let (notify_sender, notify_receiver) = mpsc::channel();
77 // These are the corresponding crossbeam channels
78 let (watcher_sender, watcher_receiver) = unbounded();
79
80 let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY)
81 .map_err(|e| log::error!("failed to spawn notify {}", e))
82 .ok();
83 // Start a silly thread to tranform between two channels
84 let thread = thread::spawn(move || {
85 notify_receiver
86 .into_iter()
87 .for_each(|event| convert_notify_event(event, &watcher_sender))
88 });
89
90 // Process requests from the called or notifications from
91 // watcher until the caller says stop.
92 loop {
93 select! {
94 // Received request from the caller. If this channel is
95 // closed, we should shutdown everything.
96 recv(input_receiver) -> t => match t {
97 Err(RecvError) => {
98 drop(input_receiver);
99 break
100 },
101 Ok(Task::AddRoot { root, config }) => {
102 watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config));
103 }
104 },
105 // Watcher send us changes. If **this** channel is
106 // closed, the watcher has died, which indicates a bug
107 // -- escalate!
108 recv(watcher_receiver) -> event => match event {
109 Err(RecvError) => panic!("watcher is dead"),
110 Ok((path, change)) => {
111 handle_change(watcher.as_mut(), &output_sender, &*roots, path, change);
112 }
113 },
56 } 114 }
57 };
58 let res = input_receiver
59 .into_iter()
60 .filter_map(|t| handle_task(t, &mut watcher))
61 .try_for_each(|it| output_sender.send(it));
62 if let Some(watcher) = watcher {
63 let _ = watcher.shutdown();
64 } 115 }
65 res.unwrap() 116 // Stopped the watcher
66 }); 117 drop(watcher.take());
118 // Drain pending events: we are not inrerested in them anyways!
119 watcher_receiver.into_iter().for_each(|_| ());
120
121 let res = thread.join();
122 match &res {
123 Ok(()) => log::info!("... Watcher terminated with ok"),
124 Err(_) => log::error!("... Watcher terminated with err"),
125 }
126 res.unwrap();
127 },
128 );
129
67 Worker { 130 Worker {
68 worker, 131 worker,
69 worker_handle, 132 worker_handle,
@@ -84,46 +147,142 @@ impl Worker {
84 } 147 }
85} 148}
86 149
87fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> { 150fn watch_root(
88 match task { 151 watcher: Option<&mut RecommendedWatcher>,
89 Task::AddRoot { root, filter } => { 152 sender: &Sender<TaskResult>,
90 if let Some(watcher) = watcher { 153 root: VfsRoot,
91 watcher.watch_root(&filter) 154 config: Arc<RootConfig>,
155) {
156 log::debug!("loading {} ...", config.root.as_path().display());
157 let files = watch_recursive(watcher, config.root.as_path(), &*config)
158 .into_iter()
159 .filter_map(|path| {
160 let abs_path = path.to_path(&config.root);
161 let text = read_to_string(&abs_path)?;
162 Some((path, text))
163 })
164 .collect();
165 sender
166 .send(TaskResult::BulkLoadRoot { root, files })
167 .unwrap();
168 log::debug!("... loaded {}", config.root.as_path().display());
169}
170
171fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) {
172 // forward relevant events only
173 match event {
174 DebouncedEvent::NoticeWrite(_)
175 | DebouncedEvent::NoticeRemove(_)
176 | DebouncedEvent::Chmod(_) => {
177 // ignore
178 }
179 DebouncedEvent::Rescan => {
180 // TODO rescan all roots
181 }
182 DebouncedEvent::Create(path) => {
183 sender.send((path, ChangeKind::Create)).unwrap();
184 }
185 DebouncedEvent::Write(path) => {
186 sender.send((path, ChangeKind::Write)).unwrap();
187 }
188 DebouncedEvent::Remove(path) => {
189 sender.send((path, ChangeKind::Remove)).unwrap();
190 }
191 DebouncedEvent::Rename(src, dst) => {
192 sender.send((src, ChangeKind::Remove)).unwrap();
193 sender.send((dst, ChangeKind::Create)).unwrap();
194 }
195 DebouncedEvent::Error(err, path) => {
196 // TODO should we reload the file contents?
197 log::warn!("watcher error \"{}\", {:?}", err, path);
198 }
199 }
200}
201
202fn handle_change(
203 watcher: Option<&mut RecommendedWatcher>,
204 sender: &Sender<TaskResult>,
205 roots: &Roots,
206 path: PathBuf,
207 kind: ChangeKind,
208) {
209 let (root, rel_path) = match roots.find(&path) {
210 None => return,
211 Some(it) => it,
212 };
213 let config = &roots[root];
214 match kind {
215 ChangeKind::Create => {
216 let mut paths = Vec::new();
217 if path.is_dir() {
218 paths.extend(watch_recursive(watcher, &path, &config));
219 } else {
220 paths.push(rel_path);
92 } 221 }
93 log::debug!("loading {} ...", filter.root.as_path().display()); 222 paths
94 let files = load_root(filter.as_ref()); 223 .into_iter()
95 log::debug!("... loaded {}", filter.root.as_path().display()); 224 .filter_map(|rel_path| {
96 Some(TaskResult::BulkLoadRoot { root, files }) 225 let abs_path = rel_path.to_path(&config.root);
226 let text = read_to_string(&abs_path)?;
227 Some((rel_path, text))
228 })
229 .try_for_each(|(path, text)| {
230 sender.send(TaskResult::AddSingleFile { root, path, text })
231 })
232 .unwrap()
97 } 233 }
234 ChangeKind::Write => {
235 if let Some(text) = read_to_string(&path) {
236 sender
237 .send(TaskResult::ChangeSingleFile {
238 root,
239 path: rel_path,
240 text,
241 })
242 .unwrap();
243 }
244 }
245 ChangeKind::Remove => sender
246 .send(TaskResult::RemoveSingleFile {
247 root,
248 path: rel_path,
249 })
250 .unwrap(),
98 } 251 }
99} 252}
100 253
101fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> { 254fn watch_recursive(
102 let mut res = Vec::new(); 255 mut watcher: Option<&mut RecommendedWatcher>,
103 for entry in WalkDir::new(&filter.root) 256 dir: &Path,
257 config: &RootConfig,
258) -> Vec<RelativePathBuf> {
259 let mut files = Vec::new();
260 for entry in WalkDir::new(dir)
104 .into_iter() 261 .into_iter()
105 .filter_entry(filter.entry_filter()) 262 .filter_entry(|it| config.contains(it.path()).is_some())
263 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
106 { 264 {
107 let entry = match entry { 265 if entry.file_type().is_dir() {
108 Ok(entry) => entry, 266 if let Some(watcher) = &mut watcher {
109 Err(e) => { 267 watch_one(watcher, entry.path());
110 log::warn!("watcher error: {}", e);
111 continue;
112 } 268 }
113 }; 269 } else {
114 if !entry.file_type().is_file() { 270 let path = config.contains(entry.path()).unwrap();
115 continue; 271 files.push(path.to_owned());
116 } 272 }
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 } 273 }
128 res 274 files
275}
276
277fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) {
278 match watcher.watch(dir, RecursiveMode::NonRecursive) {
279 Ok(()) => log::debug!("watching \"{}\"", dir.display()),
280 Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
281 }
282}
283
284fn read_to_string(path: &Path) -> Option<String> {
285 fs::read_to_string(&path)
286 .map_err(|e| log::warn!("failed to read file {}", e))
287 .ok()
129} 288}
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)