diff options
author | Aleksey Kladov <[email protected]> | 2018-12-18 13:38:05 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-12-20 09:15:38 +0000 |
commit | a422d480a188a28c6b5e7862fbf07817eb2c7447 (patch) | |
tree | d2a1945e49d1728f210c29ae8e88bffef19d22b7 /crates/ra_vfs/src | |
parent | e69b05781f7fb0f0dfdcd4acb433dbcde9cbb7b7 (diff) |
implement vfs events handling
Diffstat (limited to 'crates/ra_vfs/src')
-rw-r--r-- | crates/ra_vfs/src/arena.rs | 7 | ||||
-rw-r--r-- | crates/ra_vfs/src/io.rs | 43 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 153 |
3 files changed, 162 insertions, 41 deletions
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 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | hash::{Hash, Hasher}, | ||
3 | marker::PhantomData, | 2 | marker::PhantomData, |
4 | ops::{Index, IndexMut}, | 3 | ops::{Index, IndexMut}, |
5 | }; | 4 | }; |
@@ -21,6 +20,12 @@ impl<ID: ArenaId, T> Arena<ID, T> { | |||
21 | self.data.push(value); | 20 | self.data.push(value); |
22 | ID::from_u32(id) | 21 | ID::from_u32(id) |
23 | } | 22 | } |
23 | pub fn iter<'a>(&'a self) -> impl Iterator<Item = (ID, &'a T)> { | ||
24 | self.data | ||
25 | .iter() | ||
26 | .enumerate() | ||
27 | .map(|(idx, value)| (ID::from_u32(idx as u32), value)) | ||
28 | } | ||
24 | } | 29 | } |
25 | 30 | ||
26 | impl<ID: ArenaId, T> Default for Arena<ID, T> { | 31 | impl<ID: ArenaId, T> Default for Arena<ID, T> { |
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 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | fs, | 2 | fs, |
3 | path::{Path, PathBuf}, | 3 | path::{Path, PathBuf}, |
4 | thread::JoinHandle, | ||
5 | }; | 4 | }; |
6 | 5 | ||
7 | use walkdir::{DirEntry, WalkDir}; | 6 | use walkdir::{DirEntry, WalkDir}; |
8 | use crossbeam_channel::{Sender, Receiver}; | ||
9 | use thread_worker::{WorkerHandle}; | 7 | use thread_worker::{WorkerHandle}; |
8 | use relative_path::RelativePathBuf; | ||
10 | 9 | ||
11 | use crate::VfsRoot; | 10 | use crate::VfsRoot; |
12 | 11 | ||
13 | pub(crate) enum Task { | 12 | pub(crate) struct Task { |
14 | ScanRoot { | 13 | pub(crate) root: VfsRoot, |
15 | root: VfsRoot, | ||
16 | path: PathBuf, | ||
17 | filter: Box<FnMut(&DirEntry) -> bool + Send>, | ||
18 | }, | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | pub(crate) struct FileEvent { | ||
23 | pub(crate) path: PathBuf, | 14 | pub(crate) path: PathBuf, |
24 | pub(crate) kind: FileEventKind, | 15 | pub(crate) filter: Box<Fn(&DirEntry) -> bool + Send>, |
25 | } | 16 | } |
26 | 17 | ||
27 | #[derive(Debug)] | 18 | pub struct TaskResult { |
28 | pub(crate) enum FileEventKind { | 19 | pub(crate) root: VfsRoot, |
29 | Add(String), | 20 | pub(crate) files: Vec<(RelativePathBuf, String)>, |
30 | } | 21 | } |
31 | 22 | ||
32 | pub(crate) type Worker = thread_worker::Worker<Task, (PathBuf, Vec<FileEvent>)>; | 23 | pub(crate) type Worker = thread_worker::Worker<Task, TaskResult>; |
33 | 24 | ||
34 | pub(crate) fn start() -> (Worker, WorkerHandle) { | 25 | pub(crate) fn start() -> (Worker, WorkerHandle) { |
35 | thread_worker::spawn("vfs", 128, |input_receiver, output_sender| { | 26 | thread_worker::spawn("vfs", 128, |input_receiver, output_sender| { |
@@ -39,17 +30,17 @@ pub(crate) fn start() -> (Worker, WorkerHandle) { | |||
39 | }) | 30 | }) |
40 | } | 31 | } |
41 | 32 | ||
42 | fn handle_task(task: Task) -> (PathBuf, Vec<FileEvent>) { | 33 | fn handle_task(task: Task) -> TaskResult { |
43 | let Task::ScanRoot { path, .. } = task; | 34 | let Task { root, path, filter } = task; |
44 | log::debug!("loading {} ...", path.as_path().display()); | 35 | log::debug!("loading {} ...", path.as_path().display()); |
45 | let events = load_root(path.as_path()); | 36 | let files = load_root(path.as_path(), &*filter); |
46 | log::debug!("... loaded {}", path.as_path().display()); | 37 | log::debug!("... loaded {}", path.as_path().display()); |
47 | (path, events) | 38 | TaskResult { root, files } |
48 | } | 39 | } |
49 | 40 | ||
50 | fn load_root(path: &Path) -> Vec<FileEvent> { | 41 | fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { |
51 | let mut res = Vec::new(); | 42 | let mut res = Vec::new(); |
52 | for entry in WalkDir::new(path) { | 43 | for entry in WalkDir::new(root).into_iter().filter_entry(filter) { |
53 | let entry = match entry { | 44 | let entry = match entry { |
54 | Ok(entry) => entry, | 45 | Ok(entry) => entry, |
55 | Err(e) => { | 46 | Err(e) => { |
@@ -71,10 +62,8 @@ fn load_root(path: &Path) -> Vec<FileEvent> { | |||
71 | continue; | 62 | continue; |
72 | } | 63 | } |
73 | }; | 64 | }; |
74 | res.push(FileEvent { | 65 | let path = RelativePathBuf::from_path(path.strip_prefix(root).unwrap()).unwrap(); |
75 | path: path.to_owned(), | 66 | res.push((path.to_owned(), text)) |
76 | kind: FileEventKind::Add(text), | ||
77 | }) | ||
78 | } | 67 | } |
79 | res | 68 | res |
80 | } | 69 | } |
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; | |||
15 | mod io; | 15 | mod io; |
16 | 16 | ||
17 | use std::{ | 17 | use std::{ |
18 | mem, | ||
18 | thread, | 19 | thread, |
19 | cmp::Reverse, | 20 | cmp::Reverse, |
20 | path::{Path, PathBuf}, | 21 | path::{Path, PathBuf}, |
21 | ffi::OsStr, | 22 | ffi::OsStr, |
22 | sync::Arc, | 23 | sync::Arc, |
24 | fs, | ||
23 | }; | 25 | }; |
24 | 26 | ||
27 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
25 | use relative_path::RelativePathBuf; | 28 | use relative_path::RelativePathBuf; |
29 | use crossbeam_channel::Receiver; | ||
30 | use walkdir::DirEntry; | ||
26 | use thread_worker::{WorkerHandle}; | 31 | use thread_worker::{WorkerHandle}; |
27 | 32 | ||
28 | use crate::{ | 33 | use crate::{ |
@@ -40,15 +45,25 @@ impl RootFilter { | |||
40 | fn new(root: PathBuf) -> RootFilter { | 45 | fn new(root: PathBuf) -> RootFilter { |
41 | RootFilter { | 46 | RootFilter { |
42 | root, | 47 | root, |
43 | file_filter: rs_extension_filter, | 48 | file_filter: has_rs_extension, |
44 | } | 49 | } |
45 | } | 50 | } |
46 | fn can_contain(&self, path: &Path) -> bool { | 51 | /// Check if this root can contain `path`. NB: even if this returns |
47 | (self.file_filter)(path) && path.starts_with(&self.root) | 52 | /// true, the `path` might actually be conained in some nested root. |
53 | fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { | ||
54 | if !(self.file_filter)(path) { | ||
55 | return None; | ||
56 | } | ||
57 | if !(path.starts_with(&self.root)) { | ||
58 | return None; | ||
59 | } | ||
60 | let path = path.strip_prefix(&self.root).unwrap(); | ||
61 | let path = RelativePathBuf::from_path(path).unwrap(); | ||
62 | Some(path) | ||
48 | } | 63 | } |
49 | } | 64 | } |
50 | 65 | ||
51 | fn rs_extension_filter(p: &Path) -> bool { | 66 | fn has_rs_extension(p: &Path) -> bool { |
52 | p.extension() == Some(OsStr::new("rs")) | 67 | p.extension() == Some(OsStr::new("rs")) |
53 | } | 68 | } |
54 | 69 | ||
@@ -82,10 +97,11 @@ struct VfsFileData { | |||
82 | text: Arc<String>, | 97 | text: Arc<String>, |
83 | } | 98 | } |
84 | 99 | ||
85 | struct Vfs { | 100 | pub struct Vfs { |
86 | roots: Arena<VfsRoot, RootFilter>, | 101 | roots: Arena<VfsRoot, RootFilter>, |
87 | files: Arena<VfsFile, VfsFileData>, | 102 | files: Arena<VfsFile, VfsFileData>, |
88 | // pending_changes: Vec<PendingChange>, | 103 | root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, |
104 | pending_changes: Vec<VfsChange>, | ||
89 | worker: io::Worker, | 105 | worker: io::Worker, |
90 | worker_handle: WorkerHandle, | 106 | worker_handle: WorkerHandle, |
91 | } | 107 | } |
@@ -97,33 +113,144 @@ impl Vfs { | |||
97 | let mut res = Vfs { | 113 | let mut res = Vfs { |
98 | roots: Arena::default(), | 114 | roots: Arena::default(), |
99 | files: Arena::default(), | 115 | files: Arena::default(), |
116 | root2files: FxHashMap::default(), | ||
100 | worker, | 117 | worker, |
101 | worker_handle, | 118 | worker_handle, |
119 | pending_changes: Vec::new(), | ||
102 | }; | 120 | }; |
103 | 121 | ||
104 | // A hack to make nesting work. | 122 | // A hack to make nesting work. |
105 | roots.sort_by_key(|it| Reverse(it.as_os_str().len())); | 123 | roots.sort_by_key(|it| Reverse(it.as_os_str().len())); |
106 | 124 | for (i, path) in roots.iter().enumerate() { | |
107 | for path in roots { | 125 | let root = res.roots.alloc(RootFilter::new(path.clone())); |
108 | res.roots.alloc(RootFilter::new(path)); | 126 | let nested = roots[..i] |
127 | .iter() | ||
128 | .filter(|it| it.starts_with(path)) | ||
129 | .map(|it| it.clone()) | ||
130 | .collect::<Vec<_>>(); | ||
131 | let filter = move |entry: &DirEntry| { | ||
132 | if entry.file_type().is_file() { | ||
133 | has_rs_extension(entry.path()) | ||
134 | } else { | ||
135 | nested.iter().all(|it| it != entry.path()) | ||
136 | } | ||
137 | }; | ||
138 | let task = io::Task { | ||
139 | root, | ||
140 | path: path.clone(), | ||
141 | filter: Box::new(filter), | ||
142 | }; | ||
143 | res.worker.inp.send(task); | ||
109 | } | 144 | } |
110 | res | 145 | res |
111 | } | 146 | } |
112 | 147 | ||
113 | pub fn add_file_overlay(&mut self, path: &Path, content: String) {} | 148 | pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { |
149 | &self.worker.out | ||
150 | } | ||
151 | |||
152 | pub fn handle_task(&mut self, task: io::TaskResult) { | ||
153 | let mut files = Vec::new(); | ||
154 | for (path, text) in task.files { | ||
155 | let text = Arc::new(text); | ||
156 | let file = self.add_file(task.root, path.clone(), Arc::clone(&text)); | ||
157 | files.push((file, path, text)); | ||
158 | } | ||
159 | let change = VfsChange::AddRoot { | ||
160 | root: task.root, | ||
161 | files, | ||
162 | }; | ||
163 | self.pending_changes.push(change); | ||
164 | } | ||
114 | 165 | ||
115 | pub fn change_file_overlay(&mut self, path: &Path, new_content: String) {} | 166 | pub fn add_file_overlay(&mut self, path: &Path, text: String) { |
167 | if let Some((root, path, file)) = self.find_root(path) { | ||
168 | let text = Arc::new(text); | ||
169 | let change = if let Some(file) = file { | ||
170 | self.change_file(file, Arc::clone(&text)); | ||
171 | VfsChange::ChangeFile { file, text } | ||
172 | } else { | ||
173 | let file = self.add_file(root, path.clone(), Arc::clone(&text)); | ||
174 | VfsChange::AddFile { | ||
175 | file, | ||
176 | text, | ||
177 | root, | ||
178 | path, | ||
179 | } | ||
180 | }; | ||
181 | self.pending_changes.push(change); | ||
182 | } | ||
183 | } | ||
116 | 184 | ||
117 | pub fn remove_file_overlay(&mut self, path: &Path) {} | 185 | pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { |
186 | if let Some((_root, _path, file)) = self.find_root(path) { | ||
187 | let file = file.expect("can't change a file which wasn't added"); | ||
188 | let text = Arc::new(new_text); | ||
189 | self.change_file(file, Arc::clone(&text)); | ||
190 | let change = VfsChange::ChangeFile { file, text }; | ||
191 | self.pending_changes.push(change); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | pub fn remove_file_overlay(&mut self, path: &Path) { | ||
196 | if let Some((root, path, file)) = self.find_root(path) { | ||
197 | let file = file.expect("can't remove a file which wasn't added"); | ||
198 | let full_path = path.to_path(&self.roots[root].root); | ||
199 | let change = if let Ok(text) = fs::read_to_string(&full_path) { | ||
200 | let text = Arc::new(text); | ||
201 | self.change_file(file, Arc::clone(&text)); | ||
202 | VfsChange::ChangeFile { file, text } | ||
203 | } else { | ||
204 | self.remove_file(file); | ||
205 | VfsChange::RemoveFile { file } | ||
206 | }; | ||
207 | self.pending_changes.push(change); | ||
208 | } | ||
209 | } | ||
118 | 210 | ||
119 | pub fn commit_changes(&mut self) -> Vec<VfsChange> { | 211 | pub fn commit_changes(&mut self) -> Vec<VfsChange> { |
120 | unimplemented!() | 212 | mem::replace(&mut self.pending_changes, Vec::new()) |
121 | } | 213 | } |
122 | 214 | ||
123 | pub fn shutdown(self) -> thread::Result<()> { | 215 | pub fn shutdown(self) -> thread::Result<()> { |
124 | let _ = self.worker.shutdown(); | 216 | let _ = self.worker.shutdown(); |
125 | self.worker_handle.shutdown() | 217 | self.worker_handle.shutdown() |
126 | } | 218 | } |
219 | |||
220 | fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile { | ||
221 | let data = VfsFileData { root, path, text }; | ||
222 | let file = self.files.alloc(data); | ||
223 | self.root2files | ||
224 | .entry(root) | ||
225 | .or_insert_with(FxHashSet::default) | ||
226 | .insert(file); | ||
227 | file | ||
228 | } | ||
229 | |||
230 | fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) { | ||
231 | self.files[file].text = new_text; | ||
232 | } | ||
233 | |||
234 | fn remove_file(&mut self, file: VfsFile) { | ||
235 | //FIXME: use arena with removal | ||
236 | self.files[file].text = Default::default(); | ||
237 | self.files[file].path = Default::default(); | ||
238 | let root = self.files[file].root; | ||
239 | let removed = self.root2files.get_mut(&root).unwrap().remove(&file); | ||
240 | assert!(removed); | ||
241 | } | ||
242 | |||
243 | fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { | ||
244 | let (root, path) = self | ||
245 | .roots | ||
246 | .iter() | ||
247 | .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?; | ||
248 | let file = self.root2files[&root] | ||
249 | .iter() | ||
250 | .map(|&it| it) | ||
251 | .find(|&file| self.files[file].path == path); | ||
252 | Some((root, path, file)) | ||
253 | } | ||
127 | } | 254 | } |
128 | 255 | ||
129 | #[derive(Debug, Clone)] | 256 | #[derive(Debug, Clone)] |