aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs
diff options
context:
space:
mode:
authorBernardo <[email protected]>2019-01-25 17:39:35 +0000
committerAleksey Kladov <[email protected]>2019-01-26 08:46:37 +0000
commitd63e1cebff771621b90bdce25ac013eecb415e1e (patch)
treeb4b83981fb7734d036be1dc4e9385a9c6e71e6ea /crates/ra_vfs
parent86fadbd4e59f12535edcc280ec227d7ee8a0848d (diff)
use `Roots` in watcher
Diffstat (limited to 'crates/ra_vfs')
-rw-r--r--crates/ra_vfs/src/io.rs200
-rw-r--r--crates/ra_vfs/src/io/watcher.rs215
-rw-r--r--crates/ra_vfs/src/lib.rs168
-rw-r--r--crates/ra_vfs/tests/vfs.rs21
4 files changed, 274 insertions, 330 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index 83a021c2f..7ca1e9835 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -1,95 +1,72 @@
1use std::{ 1use std::{fs, sync::Arc, thread};
2 fmt, fs,
3 path::{Path, PathBuf},
4 sync::Arc,
5 thread,
6};
7 2
8use crossbeam_channel::{Receiver, Sender}; 3use crossbeam_channel::{Receiver, Sender};
9use parking_lot::Mutex;
10use relative_path::RelativePathBuf; 4use relative_path::RelativePathBuf;
11use thread_worker::WorkerHandle; 5use thread_worker::WorkerHandle;
12use walkdir::WalkDir; 6use walkdir::WalkDir;
13 7
14mod watcher; 8mod watcher;
15use watcher::Watcher; 9use watcher::Watcher;
16pub use watcher::WatcherChange;
17 10
18use crate::{RootFilter, VfsRoot}; 11use crate::{RootFilter, Roots, VfsRoot};
19 12
20pub(crate) enum Task { 13pub(crate) enum Task {
21 AddRoot { 14 AddRoot {
22 root: VfsRoot, 15 root: VfsRoot,
23 path: PathBuf, 16 filter: Arc<RootFilter>,
24 root_filter: Arc<RootFilter>,
25 nested_roots: Vec<PathBuf>,
26 },
27 /// this variant should only be created by the watcher
28 HandleChange(WatcherChange),
29 LoadChange(WatcherChange),
30 Watch {
31 dir: PathBuf,
32 root_filter: Arc<RootFilter>,
33 }, 17 },
34} 18}
35 19
36#[derive(Debug)] 20#[derive(Debug)]
37pub struct AddRootResult {
38 pub(crate) root: VfsRoot,
39 pub(crate) files: Vec<(RelativePathBuf, String)>,
40}
41
42#[derive(Debug)]
43pub enum WatcherChangeData {
44 Create { path: PathBuf, text: String },
45 Write { path: PathBuf, text: String },
46 Remove { path: PathBuf },
47}
48
49pub enum TaskResult { 21pub enum TaskResult {
50 AddRoot(AddRootResult), 22 BulkLoadRoot {
51 HandleChange(WatcherChange), 23 root: VfsRoot,
52 LoadChange(WatcherChangeData), 24 files: Vec<(RelativePathBuf, String)>,
53} 25 },
54 26 AddSingleFile {
55impl fmt::Debug for TaskResult { 27 root: VfsRoot,
56 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 path: RelativePathBuf,
57 match self { 29 text: String,
58 TaskResult::AddRoot(..) => f.write_str("TaskResult::AddRoot(..)"), 30 },
59 TaskResult::HandleChange(c) => write!(f, "TaskResult::HandleChange({:?})", c), 31 ChangeSingleFile {
60 TaskResult::LoadChange(c) => write!(f, "TaskResult::LoadChange({:?})", c), 32 root: VfsRoot,
61 } 33 path: RelativePathBuf,
62 } 34 text: String,
35 },
36 RemoveSingleFile {
37 root: VfsRoot,
38 path: RelativePathBuf,
39 },
63} 40}
64 41
65pub(crate) struct Worker { 42pub(crate) struct Worker {
66 worker: thread_worker::Worker<Task, TaskResult>, 43 worker: thread_worker::Worker<Task, TaskResult>,
67 worker_handle: WorkerHandle, 44 worker_handle: WorkerHandle,
68 watcher: Arc<Mutex<Option<Watcher>>>,
69} 45}
70 46
71impl Worker { 47impl Worker {
72 pub(crate) fn start() -> Worker { 48 pub(crate) fn start(roots: Arc<Roots>) -> Worker {
73 let watcher = Arc::new(Mutex::new(None));
74 let watcher_clone = watcher.clone();
75 let (worker, worker_handle) = 49 let (worker, worker_handle) =
76 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { 50 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| {
77 input_receiver 51 let mut watcher = match Watcher::start(roots, output_sender.clone()) {
52 Ok(w) => Some(w),
53 Err(e) => {
54 log::error!("could not start watcher: {}", e);
55 None
56 }
57 };
58 let res = input_receiver
78 .into_iter() 59 .into_iter()
79 .filter_map(|t| handle_task(t, &watcher_clone)) 60 .filter_map(|t| handle_task(t, &mut watcher))
80 .try_for_each(|it| output_sender.send(it)) 61 .try_for_each(|it| output_sender.send(it));
81 .unwrap() 62 if let Some(watcher) = watcher {
63 let _ = watcher.shutdown();
64 }
65 res.unwrap()
82 }); 66 });
83 match Watcher::start(worker.inp.clone()) {
84 Ok(w) => {
85 watcher.lock().replace(w);
86 }
87 Err(e) => log::error!("could not start watcher: {}", e),
88 };
89 Worker { 67 Worker {
90 worker, 68 worker,
91 worker_handle, 69 worker_handle,
92 watcher,
93 } 70 }
94 } 71 }
95 72
@@ -102,72 +79,31 @@ impl Worker {
102 } 79 }
103 80
104 pub(crate) fn shutdown(self) -> thread::Result<()> { 81 pub(crate) fn shutdown(self) -> thread::Result<()> {
105 if let Some(watcher) = self.watcher.lock().take() {
106 let _ = watcher.shutdown();
107 }
108 let _ = self.worker.shutdown(); 82 let _ = self.worker.shutdown();
109 self.worker_handle.shutdown() 83 self.worker_handle.shutdown()
110 } 84 }
111} 85}
112 86
113fn watch( 87fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> {
114 watcher: &Arc<Mutex<Option<Watcher>>>,
115 dir: &Path,
116 filter_entry: &RootFilter,
117 emit_for_existing: bool,
118) {
119 if let Some(watcher) = watcher.lock().as_mut() {
120 watcher.watch_recursive(dir, filter_entry, emit_for_existing)
121 }
122}
123
124fn handle_task(task: Task, watcher: &Arc<Mutex<Option<Watcher>>>) -> Option<TaskResult> {
125 match task { 88 match task {
126 Task::AddRoot { 89 Task::AddRoot { root, filter } => {
127 root, 90 if let Some(watcher) = watcher {
128 path, 91 watcher.watch_root(&filter)
129 root_filter, 92 }
130 nested_roots, 93 log::debug!("loading {} ...", filter.root.as_path().display());
131 } => { 94 let files = load_root(filter.as_ref());
132 watch(watcher, &path, root_filter.as_ref(), false); 95 log::debug!("... loaded {}", filter.root.as_path().display());
133 log::debug!("loading {} ...", path.as_path().display()); 96 Some(TaskResult::BulkLoadRoot { root, files })
134 let files = load_root(
135 path.as_path(),
136 root_filter.as_ref(),
137 nested_roots.as_slice(),
138 );
139 log::debug!("... loaded {}", path.as_path().display());
140 Some(TaskResult::AddRoot(AddRootResult { root, files }))
141 }
142 Task::HandleChange(change) => {
143 // forward as is because Vfs has to decide if we should load it
144 Some(TaskResult::HandleChange(change))
145 }
146 Task::LoadChange(change) => {
147 log::debug!("loading {:?} ...", change);
148 load_change(change).map(TaskResult::LoadChange)
149 }
150 Task::Watch { dir, root_filter } => {
151 watch(watcher, &dir, root_filter.as_ref(), true);
152 None
153 } 97 }
154 } 98 }
155} 99}
156 100
157fn load_root( 101fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> {
158 root: &Path,
159 root_filter: &RootFilter,
160 nested_roots: &[PathBuf],
161) -> Vec<(RelativePathBuf, String)> {
162 let mut res = Vec::new(); 102 let mut res = Vec::new();
163 for entry in WalkDir::new(root).into_iter().filter_entry(|entry| { 103 for entry in WalkDir::new(&filter.root)
164 if entry.file_type().is_dir() && nested_roots.iter().any(|it| it == entry.path()) { 104 .into_iter()
165 // do not load files of a nested root 105 .filter_entry(filter.entry_filter())
166 false 106 {
167 } else {
168 root_filter.can_contain(entry.path()).is_some()
169 }
170 }) {
171 let entry = match entry { 107 let entry = match entry {
172 Ok(entry) => entry, 108 Ok(entry) => entry,
173 Err(e) => { 109 Err(e) => {
@@ -186,42 +122,8 @@ fn load_root(
186 continue; 122 continue;
187 } 123 }
188 }; 124 };
189 let path = RelativePathBuf::from_path(path.strip_prefix(root).unwrap()).unwrap(); 125 let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap();
190 res.push((path.to_owned(), text)) 126 res.push((path.to_owned(), text))
191 } 127 }
192 res 128 res
193} 129}
194
195fn load_change(change: WatcherChange) -> Option<WatcherChangeData> {
196 let data = match change {
197 WatcherChange::Create(path) => {
198 if path.is_dir() {
199 return None;
200 }
201 let text = match fs::read_to_string(&path) {
202 Ok(text) => text,
203 Err(e) => {
204 log::warn!("watcher error \"{}\": {}", path.display(), e);
205 return None;
206 }
207 };
208 WatcherChangeData::Create { path, text }
209 }
210 WatcherChange::Write(path) => {
211 let text = match fs::read_to_string(&path) {
212 Ok(text) => text,
213 Err(e) => {
214 log::warn!("watcher error \"{}\": {}", path.display(), e);
215 return None;
216 }
217 };
218 WatcherChangeData::Write { path, text }
219 }
220 WatcherChange::Remove(path) => WatcherChangeData::Remove { path },
221 WatcherChange::Rescan => {
222 // this should be handled by Vfs::handle_task
223 return None;
224 }
225 };
226 Some(data)
227}
diff --git a/crates/ra_vfs/src/io/watcher.rs b/crates/ra_vfs/src/io/watcher.rs
index 68bb6b692..1d7ce2136 100644
--- a/crates/ra_vfs/src/io/watcher.rs
+++ b/crates/ra_vfs/src/io/watcher.rs
@@ -1,118 +1,72 @@
1use crate::{io, RootFilter}; 1use crate::{io, RootFilter, Roots, VfsRoot};
2use crossbeam_channel::Sender; 2use crossbeam_channel::Sender;
3use drop_bomb::DropBomb; 3use drop_bomb::DropBomb;
4use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; 4use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
5use parking_lot::Mutex;
5use std::{ 6use std::{
7 fs,
6 path::{Path, PathBuf}, 8 path::{Path, PathBuf},
7 sync::mpsc, 9 sync::{mpsc, Arc},
8 thread, 10 thread,
9 time::Duration, 11 time::Duration,
10}; 12};
11use walkdir::WalkDir; 13use walkdir::WalkDir;
12 14
13#[derive(Debug)] 15#[derive(Debug)]
14pub enum WatcherChange { 16enum ChangeKind {
15 Create(PathBuf), 17 Create,
16 Write(PathBuf), 18 Write,
17 Remove(PathBuf), 19 Remove,
18 Rescan,
19}
20
21fn handle_change_event(
22 ev: DebouncedEvent,
23 sender: &Sender<io::Task>,
24) -> Result<(), Box<std::error::Error>> {
25 match ev {
26 DebouncedEvent::NoticeWrite(_)
27 | DebouncedEvent::NoticeRemove(_)
28 | DebouncedEvent::Chmod(_) => {
29 // ignore
30 }
31 DebouncedEvent::Rescan => {
32 sender.send(io::Task::HandleChange(WatcherChange::Rescan))?;
33 }
34 DebouncedEvent::Create(path) => {
35 sender.send(io::Task::HandleChange(WatcherChange::Create(path)))?;
36 }
37 DebouncedEvent::Write(path) => {
38 sender.send(io::Task::HandleChange(WatcherChange::Write(path)))?;
39 }
40 DebouncedEvent::Remove(path) => {
41 sender.send(io::Task::HandleChange(WatcherChange::Remove(path)))?;
42 }
43 DebouncedEvent::Rename(src, dst) => {
44 sender.send(io::Task::HandleChange(WatcherChange::Remove(src)))?;
45 sender.send(io::Task::HandleChange(WatcherChange::Create(dst)))?;
46 }
47 DebouncedEvent::Error(err, path) => {
48 // TODO should we reload the file contents?
49 log::warn!("watcher error \"{}\", {:?}", err, path);
50 }
51 }
52 Ok(())
53} 20}
54 21
55const WATCHER_DELAY: Duration = Duration::from_millis(250); 22const WATCHER_DELAY: Duration = Duration::from_millis(250);
56 23
57pub(crate) struct Watcher { 24pub(crate) struct Watcher {
58 watcher: RecommendedWatcher,
59 thread: thread::JoinHandle<()>, 25 thread: thread::JoinHandle<()>,
60 bomb: DropBomb, 26 bomb: DropBomb,
61 sender: Sender<io::Task>, 27 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
62} 28}
63 29
64impl Watcher { 30impl Watcher {
65 pub(crate) fn start( 31 pub(crate) fn start(
66 output_sender: Sender<io::Task>, 32 roots: Arc<Roots>,
33 output_sender: Sender<io::TaskResult>,
67 ) -> Result<Watcher, Box<std::error::Error>> { 34 ) -> Result<Watcher, Box<std::error::Error>> {
68 let (input_sender, input_receiver) = mpsc::channel(); 35 let (input_sender, input_receiver) = mpsc::channel();
69 let watcher = notify::watcher(input_sender, WATCHER_DELAY)?; 36 let watcher = Arc::new(Mutex::new(Some(notify::watcher(
37 input_sender,
38 WATCHER_DELAY,
39 )?)));
70 let sender = output_sender.clone(); 40 let sender = output_sender.clone();
41 let watcher_clone = watcher.clone();
71 let thread = thread::spawn(move || { 42 let thread = thread::spawn(move || {
43 let worker = WatcherWorker {
44 roots,
45 watcher: watcher_clone,
46 sender,
47 };
72 input_receiver 48 input_receiver
73 .into_iter() 49 .into_iter()
74 // forward relevant events only 50 // forward relevant events only
75 .try_for_each(|change| handle_change_event(change, &output_sender)) 51 .try_for_each(|change| worker.handle_debounced_event(change))
76 .unwrap() 52 .unwrap()
77 }); 53 });
78 Ok(Watcher { 54 Ok(Watcher {
79 watcher,
80 thread, 55 thread,
81 sender, 56 watcher,
82 bomb: DropBomb::new(format!("Watcher was not shutdown")), 57 bomb: DropBomb::new(format!("Watcher was not shutdown")),
83 }) 58 })
84 } 59 }
85 60
86 pub fn watch_recursive(&mut self, dir: &Path, filter: &RootFilter, emit_for_contents: bool) { 61 pub fn watch_root(&mut self, filter: &RootFilter) {
87 for res in WalkDir::new(dir) 62 for res in WalkDir::new(&filter.root)
88 .into_iter() 63 .into_iter()
89 .filter_entry(|entry| filter.can_contain(entry.path()).is_some()) 64 .filter_entry(filter.entry_filter())
90 { 65 {
91 match res { 66 match res {
92 Ok(entry) => { 67 Ok(entry) => {
93 if entry.path().is_dir() { 68 if entry.path().is_dir() {
94 match self 69 watch_one(self.watcher.as_ref(), entry.path());
95 .watcher
96 .watch(entry.path(), RecursiveMode::NonRecursive)
97 {
98 Ok(()) => log::debug!("watching \"{}\"", entry.path().display()),
99 Err(e) => {
100 log::warn!("could not watch \"{}\": {}", entry.path().display(), e)
101 }
102 }
103 } else {
104 if emit_for_contents && entry.depth() > 0 {
105 // emit only for files otherwise we will cause watch_recursive to be called again with a dir that we are already watching
106 // emit as create because we haven't seen it yet
107 if let Err(e) =
108 self.sender
109 .send(io::Task::HandleChange(WatcherChange::Create(
110 entry.path().to_path_buf(),
111 )))
112 {
113 log::warn!("watcher error: {}", e)
114 }
115 }
116 } 70 }
117 } 71 }
118 Err(e) => log::warn!("watcher error: {}", e), 72 Err(e) => log::warn!("watcher error: {}", e),
@@ -122,7 +76,7 @@ impl Watcher {
122 76
123 pub fn shutdown(mut self) -> thread::Result<()> { 77 pub fn shutdown(mut self) -> thread::Result<()> {
124 self.bomb.defuse(); 78 self.bomb.defuse();
125 drop(self.watcher); 79 drop(self.watcher.lock().take());
126 let res = self.thread.join(); 80 let res = self.thread.join();
127 match &res { 81 match &res {
128 Ok(()) => log::info!("... Watcher terminated with ok"), 82 Ok(()) => log::info!("... Watcher terminated with ok"),
@@ -131,3 +85,116 @@ impl Watcher {
131 res 85 res
132 } 86 }
133} 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(|entry| filter.can_contain(entry.path()).is_some())
176 {
177 match res {
178 Ok(entry) => {
179 if entry.path().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 cba3a463a..661892f8a 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -28,22 +28,25 @@ use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId}; 28use ra_arena::{impl_arena_id, Arena, RawId};
29use relative_path::{Component, RelativePath, RelativePathBuf}; 29use relative_path::{Component, RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet}; 30use rustc_hash::{FxHashMap, FxHashSet};
31use walkdir::DirEntry;
31 32
32pub use crate::io::TaskResult as VfsTask; 33pub use crate::io::TaskResult as VfsTask;
33use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker}; 34use io::{TaskResult, Worker};
34 35
35/// `RootFilter` is a predicate that checks if a file can belong to a root. If 36/// `RootFilter` is a predicate that checks if a file can belong to a root. If
36/// several filters match a file (nested dirs), the most nested one wins. 37/// several filters match a file (nested dirs), the most nested one wins.
37pub(crate) struct RootFilter { 38pub(crate) struct RootFilter {
38 root: PathBuf, 39 root: PathBuf,
39 filter: fn(&Path, &RelativePath) -> bool, 40 filter: fn(&Path, &RelativePath) -> bool,
41 excluded_dirs: Vec<PathBuf>,
40} 42}
41 43
42impl RootFilter { 44impl RootFilter {
43 fn new(root: PathBuf) -> RootFilter { 45 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter {
44 RootFilter { 46 RootFilter {
45 root, 47 root,
46 filter: default_filter, 48 filter: default_filter,
49 excluded_dirs,
47 } 50 }
48 } 51 }
49 /// Check if this root can contain `path`. NB: even if this returns 52 /// Check if this root can contain `path`. NB: even if this returns
@@ -56,6 +59,17 @@ impl RootFilter {
56 } 59 }
57 Some(rel_path) 60 Some(rel_path)
58 } 61 }
62
63 pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a {
64 move |entry: &DirEntry| {
65 if entry.path().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path()) {
66 // do not walk nested roots
67 false
68 } else {
69 self.can_contain(entry.path()).is_some()
70 }
71 }
72 }
59} 73}
60 74
61pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { 75pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
@@ -94,10 +108,22 @@ pub(crate) struct Roots {
94} 108}
95 109
96impl Roots { 110impl Roots {
97 pub(crate) fn new() -> Roots { 111 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
98 Roots { 112 let mut roots = Arena::default();
99 roots: Arena::default(), 113 // A hack to make nesting work.
114 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
115 for (i, path) in paths.iter().enumerate() {
116 let nested_roots = paths[..i]
117 .iter()
118 .filter(|it| it.starts_with(path))
119 .map(|it| it.clone())
120 .collect::<Vec<_>>();
121
122 let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots));
123
124 roots.alloc(root_filter.clone());
100 } 125 }
126 Roots { roots }
101 } 127 }
102 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { 128 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
103 self.roots 129 self.roots
@@ -135,36 +161,22 @@ impl fmt::Debug for Vfs {
135 161
136impl Vfs { 162impl Vfs {
137 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 163 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
138 let mut root_paths = roots; 164 let roots = Arc::new(Roots::new(roots));
139 let worker = io::Worker::start(); 165 let worker = io::Worker::start(roots.clone());
140
141 let mut roots = Roots::new();
142 let mut root2files = FxHashMap::default(); 166 let mut root2files = FxHashMap::default();
143 167
144 // A hack to make nesting work. 168 for (root, filter) in roots.iter() {
145 root_paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
146 for (i, path) in root_paths.iter().enumerate() {
147 let root_filter = Arc::new(RootFilter::new(path.clone()));
148
149 let root = roots.alloc(root_filter.clone());
150 root2files.insert(root, Default::default()); 169 root2files.insert(root, Default::default());
151 170 worker
152 let nested_roots = root_paths[..i] 171 .sender()
153 .iter() 172 .send(io::Task::AddRoot {
154 .filter(|it| it.starts_with(path)) 173 root,
155 .map(|it| it.clone()) 174 filter: filter.clone(),
156 .collect::<Vec<_>>(); 175 })
157 176 .unwrap();
158 let task = io::Task::AddRoot {
159 root,
160 path: path.clone(),
161 root_filter,
162 nested_roots,
163 };
164 worker.sender().send(task).unwrap();
165 } 177 }
166 let res = Vfs { 178 let res = Vfs {
167 roots: Arc::new(roots), 179 roots,
168 files: Arena::default(), 180 files: Arena::default(),
169 root2files, 181 root2files,
170 worker, 182 worker,
@@ -225,90 +237,46 @@ impl Vfs {
225 237
226 pub fn handle_task(&mut self, task: io::TaskResult) { 238 pub fn handle_task(&mut self, task: io::TaskResult) {
227 match task { 239 match task {
228 TaskResult::AddRoot(task) => { 240 TaskResult::BulkLoadRoot { root, files } => {
229 let mut files = Vec::new(); 241 let mut cur_files = Vec::new();
230 // While we were scanning the root in the backgound, a file might have 242 // While we were scanning the root in the backgound, a file might have
231 // been open in the editor, so we need to account for that. 243 // been open in the editor, so we need to account for that.
232 let exising = self.root2files[&task.root] 244 let exising = self.root2files[&root]
233 .iter() 245 .iter()
234 .map(|&file| (self.files[file].path.clone(), file)) 246 .map(|&file| (self.files[file].path.clone(), file))
235 .collect::<FxHashMap<_, _>>(); 247 .collect::<FxHashMap<_, _>>();
236 for (path, text) in task.files { 248 for (path, text) in files {
237 if let Some(&file) = exising.get(&path) { 249 if let Some(&file) = exising.get(&path) {
238 let text = Arc::clone(&self.files[file].text); 250 let text = Arc::clone(&self.files[file].text);
239 files.push((file, path, text)); 251 cur_files.push((file, path, text));
240 continue; 252 continue;
241 } 253 }
242 let text = Arc::new(text); 254 let text = Arc::new(text);
243 let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false); 255 let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
244 files.push((file, path, text)); 256 cur_files.push((file, path, text));
245 } 257 }
246 258
247 let change = VfsChange::AddRoot { 259 let change = VfsChange::AddRoot {
248 root: task.root, 260 root,
249 files, 261 files: cur_files,
250 }; 262 };
251 self.pending_changes.push(change); 263 self.pending_changes.push(change);
252 } 264 }
253 TaskResult::HandleChange(change) => match &change { 265 TaskResult::AddSingleFile { root, path, text } => {
254 WatcherChange::Create(path) if path.is_dir() => { 266 self.do_add_file(root, path, text, false);
255 if let Some((root, _path, _file)) = self.find_root(&path) { 267 }
256 let root_filter = self.roots[root].clone(); 268 TaskResult::ChangeSingleFile { root, path, text } => {
257 self.worker 269 if let Some(file) = self.find_file(root, &path) {
258 .sender() 270 self.do_change_file(file, text, false);
259 .send(Task::Watch { 271 } else {
260 dir: path.to_path_buf(), 272 self.do_add_file(root, path, text, false);
261 root_filter,
262 })
263 .unwrap()
264 }
265 }
266 WatcherChange::Create(path)
267 | WatcherChange::Remove(path)
268 | WatcherChange::Write(path) => {
269 if self.should_handle_change(&path) {
270 self.worker.sender().send(Task::LoadChange(change)).unwrap()
271 }
272 }
273 WatcherChange::Rescan => {
274 // TODO we should reload all files
275 }
276 },
277 TaskResult::LoadChange(change) => match change {
278 WatcherChangeData::Create { path, text }
279 | WatcherChangeData::Write { path, text } => {
280 if let Some((root, path, file)) = self.find_root(&path) {
281 if let Some(file) = file {
282 self.do_change_file(file, text, false);
283 } else {
284 self.do_add_file(root, path, text, false);
285 }
286 }
287 }
288 WatcherChangeData::Remove { path } => {
289 if let Some((root, path, file)) = self.find_root(&path) {
290 if let Some(file) = file {
291 self.do_remove_file(root, path, file, false);
292 }
293 }
294 } 273 }
295 }, 274 }
296 } 275 TaskResult::RemoveSingleFile { root, path } => {
297 } 276 if let Some(file) = self.find_file(root, &path) {
298 277 self.do_remove_file(root, path, file, false);
299 fn should_handle_change(&self, path: &Path) -> bool {
300 if let Some((_root, _rel_path, file)) = self.find_root(&path) {
301 if let Some(file) = file {
302 if self.files[file].is_overlayed {
303 // file is overlayed
304 log::debug!("skipping overlayed \"{}\"", path.display());
305 return false;
306 } 278 }
307 } 279 }
308 true
309 } else {
310 // file doesn't belong to any root
311 false
312 } 280 }
313 } 281 }
314 282
@@ -434,11 +402,15 @@ impl Vfs {
434 402
435 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { 403 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
436 let (root, path) = self.roots.find(&path)?; 404 let (root, path) = self.roots.find(&path)?;
437 let file = self.root2files[&root] 405 let file = self.find_file(root, &path);
406 Some((root, path, file))
407 }
408
409 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
410 self.root2files[&root]
438 .iter() 411 .iter()
439 .map(|&it| it) 412 .map(|&it| it)
440 .find(|&file| self.files[file].path == path); 413 .find(|&file| self.files[file].path == path)
441 Some((root, path, file))
442 } 414 }
443} 415}
444 416
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs
index bf44e97c5..8562c56b9 100644
--- a/crates/ra_vfs/tests/vfs.rs
+++ b/crates/ra_vfs/tests/vfs.rs
@@ -75,27 +75,31 @@ fn test_vfs_works() -> std::io::Result<()> {
75 } 75 }
76 76
77 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); 77 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap();
78 // 2 tasks per change, HandleChange and then LoadChange 78 process_tasks(&mut vfs, 1);
79 process_tasks(&mut vfs, 2);
80 assert_match!( 79 assert_match!(
81 vfs.commit_changes().as_slice(), 80 vfs.commit_changes().as_slice(),
82 [VfsChange::ChangeFile { text, .. }], 81 [VfsChange::ChangeFile { text, .. }],
83 assert_eq!(text.as_str(), "quux") 82 assert_eq!(text.as_str(), "quux")
84 ); 83 );
85 84
86 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); 85 vfs.add_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string());
87 assert_match!( 86 assert_match!(
88 vfs.commit_changes().as_slice(), 87 vfs.commit_changes().as_slice(),
89 [VfsChange::ChangeFile { text, .. }], 88 [VfsChange::ChangeFile { text, .. }],
90 assert_eq!(text.as_str(), "m") 89 assert_eq!(text.as_str(), "m")
91 ); 90 );
92 91
92 // changing file on disk while overlayed doesn't generate a VfsChange
93 fs::write(&dir.path().join("a/b/baz.rs"), "corge").unwrap();
94 process_tasks(&mut vfs, 1);
95 assert_match!(vfs.commit_changes().as_slice(), []);
96
93 // removing overlay restores data on disk 97 // removing overlay restores data on disk
94 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); 98 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs"));
95 assert_match!( 99 assert_match!(
96 vfs.commit_changes().as_slice(), 100 vfs.commit_changes().as_slice(),
97 [VfsChange::ChangeFile { text, .. }], 101 [VfsChange::ChangeFile { text, .. }],
98 assert_eq!(text.as_str(), "quux") 102 assert_eq!(text.as_str(), "corge")
99 ); 103 );
100 104
101 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); 105 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string());
@@ -117,7 +121,7 @@ fn test_vfs_works() -> std::io::Result<()> {
117 121
118 fs::create_dir_all(dir.path().join("a/sub1/sub2")).unwrap(); 122 fs::create_dir_all(dir.path().join("a/sub1/sub2")).unwrap();
119 fs::write(dir.path().join("a/sub1/sub2/new.rs"), "new hello").unwrap(); 123 fs::write(dir.path().join("a/sub1/sub2/new.rs"), "new hello").unwrap();
120 process_tasks(&mut vfs, 3); 124 process_tasks(&mut vfs, 1);
121 assert_match!( 125 assert_match!(
122 vfs.commit_changes().as_slice(), 126 vfs.commit_changes().as_slice(),
123 [VfsChange::AddFile { text, path, .. }], 127 [VfsChange::AddFile { text, path, .. }],
@@ -132,7 +136,7 @@ fn test_vfs_works() -> std::io::Result<()> {
132 &dir.path().join("a/sub1/sub2/new1.rs"), 136 &dir.path().join("a/sub1/sub2/new1.rs"),
133 ) 137 )
134 .unwrap(); 138 .unwrap();
135 process_tasks(&mut vfs, 4); 139 process_tasks(&mut vfs, 2);
136 assert_match!( 140 assert_match!(
137 vfs.commit_changes().as_slice(), 141 vfs.commit_changes().as_slice(),
138 [VfsChange::RemoveFile { 142 [VfsChange::RemoveFile {
@@ -150,17 +154,16 @@ fn test_vfs_works() -> std::io::Result<()> {
150 ); 154 );
151 155
152 fs::remove_file(&dir.path().join("a/sub1/sub2/new1.rs")).unwrap(); 156 fs::remove_file(&dir.path().join("a/sub1/sub2/new1.rs")).unwrap();
153 process_tasks(&mut vfs, 2); 157 process_tasks(&mut vfs, 1);
154 assert_match!( 158 assert_match!(
155 vfs.commit_changes().as_slice(), 159 vfs.commit_changes().as_slice(),
156 [VfsChange::RemoveFile { path, .. }], 160 [VfsChange::RemoveFile { path, .. }],
157 assert_eq!(path, "sub1/sub2/new1.rs") 161 assert_eq!(path, "sub1/sub2/new1.rs")
158 ); 162 );
159 163
160 fs::create_dir_all(dir.path().join("a/target")).unwrap();
161 // should be ignored 164 // should be ignored
165 fs::create_dir_all(dir.path().join("a/target")).unwrap();
162 fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap(); 166 fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap();
163 process_tasks(&mut vfs, 1); // 1 task because no LoadChange will happen, just HandleChange for dir creation
164 167
165 assert_match!( 168 assert_match!(
166 vfs.task_receiver().try_recv(), 169 vfs.task_receiver().try_recv(),