diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-18 13:21:01 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-18 13:21:01 +0000 |
commit | a591c3460b88f56fbab6af2d4e0c47ca12197405 (patch) | |
tree | 6c77bfc9ae120e8741ccd3a354c942e8e15b759b | |
parent | bd2b4ef2d1c631215523f79a8138cfa8cbd3f70e (diff) | |
parent | d93097a4936b9eea98e4759fa2fde3a052acfb42 (diff) |
Merge #856
856: Reduce dependnecies of ra_vfs r=pnkfelix a=matklad
In preparation for moving `ra_vfs` to a separate repo with extensive cross-platform CI, remove dependency on `ra_thread_workder` and `ra_arena`.
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | crates/ra_vfs/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/ra_vfs/src/io.rs | 182 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 92 | ||||
-rw-r--r-- | crates/ra_vfs/src/roots.rs | 23 |
5 files changed, 178 insertions, 126 deletions
diff --git a/Cargo.lock b/Cargo.lock index 39bfcde41..8f087749f 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1123,16 +1123,13 @@ name = "ra_vfs" | |||
1123 | version = "0.1.0" | 1123 | version = "0.1.0" |
1124 | dependencies = [ | 1124 | dependencies = [ |
1125 | "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", | 1125 | "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", |
1126 | "drop_bomb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1127 | "flexi_logger 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", | 1126 | "flexi_logger 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1128 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | 1127 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1129 | "notify 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)", | 1128 | "notify 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)", |
1130 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1129 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1131 | "ra_arena 0.1.0", | ||
1132 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1130 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1133 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1131 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1134 | "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | 1132 | "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", |
1135 | "thread_worker 0.1.0", | ||
1136 | "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", | 1133 | "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", |
1137 | ] | 1134 | ] |
1138 | 1135 | ||
diff --git a/crates/ra_vfs/Cargo.toml b/crates/ra_vfs/Cargo.toml index 2da5c499b..fdaf31b9c 100644 --- a/crates/ra_vfs/Cargo.toml +++ b/crates/ra_vfs/Cargo.toml | |||
@@ -11,12 +11,8 @@ rustc-hash = "1.0" | |||
11 | crossbeam-channel = "0.3.5" | 11 | crossbeam-channel = "0.3.5" |
12 | log = "0.4.6" | 12 | log = "0.4.6" |
13 | notify = "4.0.9" | 13 | notify = "4.0.9" |
14 | drop_bomb = "0.1.0" | ||
15 | parking_lot = "0.7.0" | 14 | parking_lot = "0.7.0" |
16 | 15 | ||
17 | thread_worker = { path = "../thread_worker" } | ||
18 | ra_arena = { path = "../ra_arena" } | ||
19 | |||
20 | [dev-dependencies] | 16 | [dev-dependencies] |
21 | tempfile = "3" | 17 | tempfile = "3" |
22 | flexi_logger = "0.10.0" | 18 | flexi_logger = "0.10.0" |
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 0cffc03f3..5969ee0d0 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs | |||
@@ -3,13 +3,14 @@ use std::{ | |||
3 | path::{Path, PathBuf}, | 3 | path::{Path, PathBuf}, |
4 | sync::{mpsc, Arc}, | 4 | sync::{mpsc, Arc}, |
5 | time::Duration, | 5 | time::Duration, |
6 | thread, | ||
6 | }; | 7 | }; |
7 | use crossbeam_channel::{Sender, unbounded, RecvError, select}; | 8 | use crossbeam_channel::{Sender, Receiver, unbounded, RecvError, select}; |
8 | use relative_path::RelativePathBuf; | 9 | use relative_path::RelativePathBuf; |
9 | use walkdir::WalkDir; | 10 | use walkdir::WalkDir; |
10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; | 11 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; |
11 | 12 | ||
12 | use crate::{Roots, VfsRoot}; | 13 | use crate::{Roots, VfsRoot, VfsTask}; |
13 | 14 | ||
14 | pub(crate) enum Task { | 15 | pub(crate) enum Task { |
15 | AddRoot { root: VfsRoot }, | 16 | AddRoot { root: VfsRoot }, |
@@ -18,7 +19,7 @@ pub(crate) enum Task { | |||
18 | /// `TaskResult` transfers files read on the IO thread to the VFS on the main | 19 | /// `TaskResult` transfers files read on the IO thread to the VFS on the main |
19 | /// thread. | 20 | /// thread. |
20 | #[derive(Debug)] | 21 | #[derive(Debug)] |
21 | pub enum TaskResult { | 22 | pub(crate) enum TaskResult { |
22 | /// Emitted when we've recursively scanned a source root during the initial | 23 | /// Emitted when we've recursively scanned a source root during the initial |
23 | /// load. | 24 | /// load. |
24 | BulkLoadRoot { root: VfsRoot, files: Vec<(RelativePathBuf, String)> }, | 25 | BulkLoadRoot { root: VfsRoot, files: Vec<(RelativePathBuf, String)> }, |
@@ -46,7 +47,46 @@ enum ChangeKind { | |||
46 | 47 | ||
47 | const WATCHER_DELAY: Duration = Duration::from_millis(250); | 48 | const WATCHER_DELAY: Duration = Duration::from_millis(250); |
48 | 49 | ||
49 | pub(crate) type Worker = thread_worker::Worker<Task, TaskResult>; | 50 | // Like thread::JoinHandle, but joins the thread on drop. |
51 | // | ||
52 | // This is useful because it guarantees the absence of run-away threads, even if | ||
53 | // code panics. This is important, because we might see panics in the test and | ||
54 | // we might be used in an IDE context, where a failed component is just | ||
55 | // restarted. | ||
56 | // | ||
57 | // Because all threads are joined, care must be taken to avoid deadlocks. That | ||
58 | // typically means ensuring that channels are dropped before the threads. | ||
59 | struct ScopedThread(Option<thread::JoinHandle<()>>); | ||
60 | |||
61 | impl ScopedThread { | ||
62 | fn spawn(name: String, f: impl FnOnce() + Send + 'static) -> ScopedThread { | ||
63 | let handle = thread::Builder::new().name(name).spawn(f).unwrap(); | ||
64 | ScopedThread(Some(handle)) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | impl Drop for ScopedThread { | ||
69 | fn drop(&mut self) { | ||
70 | let res = self.0.take().unwrap().join(); | ||
71 | if !thread::panicking() { | ||
72 | res.unwrap(); | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub(crate) struct Worker { | ||
78 | // XXX: field order is significant here. | ||
79 | // | ||
80 | // In Rust, fields are dropped in the declaration order, and we rely on this | ||
81 | // here. We must close sender first, so that the `thread` (who holds the | ||
82 | // opposite side of the channel) noticed shutdown. Then, we must join the | ||
83 | // thread, but we must keep receiver alive so that the thread does not | ||
84 | // panic. | ||
85 | pub(crate) sender: Sender<Task>, | ||
86 | _thread: ScopedThread, | ||
87 | pub(crate) receiver: Receiver<VfsTask>, | ||
88 | } | ||
89 | |||
50 | pub(crate) fn start(roots: Arc<Roots>) -> Worker { | 90 | pub(crate) fn start(roots: Arc<Roots>) -> Worker { |
51 | // This is a pretty elaborate setup of threads & channels! It is | 91 | // This is a pretty elaborate setup of threads & channels! It is |
52 | // explained by the following concerns: | 92 | // explained by the following concerns: |
@@ -55,74 +95,75 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker { | |||
55 | // * we want to read all files from a single thread, to guarantee that | 95 | // * we want to read all files from a single thread, to guarantee that |
56 | // we always get fresher versions and never go back in time. | 96 | // we always get fresher versions and never go back in time. |
57 | // * we want to tear down everything neatly during shutdown. | 97 | // * we want to tear down everything neatly during shutdown. |
58 | Worker::spawn( | 98 | let _thread; |
59 | "vfs", | 99 | // This are the channels we use to communicate with outside world. |
60 | 128, | 100 | // If `input_receiver` is closed we need to tear ourselves down. |
61 | // This are the channels we use to communicate with outside world. | 101 | // `output_sender` should not be closed unless the parent died. |
62 | // If `input_receiver` is closed we need to tear ourselves down. | 102 | let (input_sender, input_receiver) = unbounded(); |
63 | // `output_sender` should not be closed unless the parent died. | 103 | let (output_sender, output_receiver) = unbounded(); |
64 | move |input_receiver, output_sender| { | 104 | |
65 | // Make sure that the destruction order is | 105 | _thread = ScopedThread::spawn("vfs".to_string(), move || { |
66 | // | 106 | // Make sure that the destruction order is |
67 | // * notify_sender | 107 | // |
68 | // * _thread | 108 | // * notify_sender |
69 | // * watcher_sender | 109 | // * _thread |
70 | // | 110 | // * watcher_sender |
71 | // this is required to avoid deadlocks. | 111 | // |
72 | 112 | // this is required to avoid deadlocks. | |
73 | // These are the corresponding crossbeam channels | 113 | |
74 | let (watcher_sender, watcher_receiver) = unbounded(); | 114 | // These are the corresponding crossbeam channels |
75 | let _thread; | 115 | let (watcher_sender, watcher_receiver) = unbounded(); |
76 | { | 116 | let _notify_thread; |
77 | // These are `std` channels notify will send events to | 117 | { |
78 | let (notify_sender, notify_receiver) = mpsc::channel(); | 118 | // These are `std` channels notify will send events to |
79 | 119 | let (notify_sender, notify_receiver) = mpsc::channel(); | |
80 | let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY) | 120 | |
81 | .map_err(|e| log::error!("failed to spawn notify {}", e)) | 121 | let mut watcher = notify::watcher(notify_sender, WATCHER_DELAY) |
82 | .ok(); | 122 | .map_err(|e| log::error!("failed to spawn notify {}", e)) |
83 | // Start a silly thread to transform between two channels | 123 | .ok(); |
84 | _thread = thread_worker::ScopedThread::spawn("notify-convertor", move || { | 124 | // Start a silly thread to transform between two channels |
85 | notify_receiver | 125 | _notify_thread = ScopedThread::spawn("notify-convertor".to_string(), move || { |
86 | .into_iter() | 126 | notify_receiver |
87 | .for_each(|event| convert_notify_event(event, &watcher_sender)) | 127 | .into_iter() |
88 | }); | 128 | .for_each(|event| convert_notify_event(event, &watcher_sender)) |
89 | 129 | }); | |
90 | // Process requests from the called or notifications from | 130 | |
91 | // watcher until the caller says stop. | 131 | // Process requests from the called or notifications from |
92 | loop { | 132 | // watcher until the caller says stop. |
93 | select! { | 133 | loop { |
94 | // Received request from the caller. If this channel is | 134 | select! { |
95 | // closed, we should shutdown everything. | 135 | // Received request from the caller. If this channel is |
96 | recv(input_receiver) -> t => match t { | 136 | // closed, we should shutdown everything. |
97 | Err(RecvError) => { | 137 | recv(input_receiver) -> t => match t { |
98 | drop(input_receiver); | 138 | Err(RecvError) => { |
99 | break | 139 | drop(input_receiver); |
100 | }, | 140 | break |
101 | Ok(Task::AddRoot { root }) => { | ||
102 | watch_root(watcher.as_mut(), &output_sender, &*roots, root); | ||
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 | }, | 141 | }, |
114 | } | 142 | Ok(Task::AddRoot { root }) => { |
143 | watch_root(watcher.as_mut(), &output_sender, &*roots, root); | ||
144 | } | ||
145 | }, | ||
146 | // Watcher send us changes. If **this** channel is | ||
147 | // closed, the watcher has died, which indicates a bug | ||
148 | // -- escalate! | ||
149 | recv(watcher_receiver) -> event => match event { | ||
150 | Err(RecvError) => panic!("watcher is dead"), | ||
151 | Ok((path, change)) => { | ||
152 | handle_change(watcher.as_mut(), &output_sender, &*roots, path, change); | ||
153 | } | ||
154 | }, | ||
115 | } | 155 | } |
116 | } | 156 | } |
117 | // Drain pending events: we are not interested in them anyways! | 157 | } |
118 | watcher_receiver.into_iter().for_each(|_| ()); | 158 | // Drain pending events: we are not interested in them anyways! |
119 | }, | 159 | watcher_receiver.into_iter().for_each(|_| ()); |
120 | ) | 160 | }); |
161 | Worker { sender: input_sender, _thread, receiver: output_receiver } | ||
121 | } | 162 | } |
122 | 163 | ||
123 | fn watch_root( | 164 | fn watch_root( |
124 | watcher: Option<&mut RecommendedWatcher>, | 165 | watcher: Option<&mut RecommendedWatcher>, |
125 | sender: &Sender<TaskResult>, | 166 | sender: &Sender<VfsTask>, |
126 | roots: &Roots, | 167 | roots: &Roots, |
127 | root: VfsRoot, | 168 | root: VfsRoot, |
128 | ) { | 169 | ) { |
@@ -136,7 +177,8 @@ fn watch_root( | |||
136 | Some((path, text)) | 177 | Some((path, text)) |
137 | }) | 178 | }) |
138 | .collect(); | 179 | .collect(); |
139 | sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap(); | 180 | let res = TaskResult::BulkLoadRoot { root, files }; |
181 | sender.send(VfsTask(res)).unwrap(); | ||
140 | log::debug!("... loaded {}", root_path.display()); | 182 | log::debug!("... loaded {}", root_path.display()); |
141 | } | 183 | } |
142 | 184 | ||
@@ -173,7 +215,7 @@ fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeK | |||
173 | 215 | ||
174 | fn handle_change( | 216 | fn handle_change( |
175 | watcher: Option<&mut RecommendedWatcher>, | 217 | watcher: Option<&mut RecommendedWatcher>, |
176 | sender: &Sender<TaskResult>, | 218 | sender: &Sender<VfsTask>, |
177 | roots: &Roots, | 219 | roots: &Roots, |
178 | path: PathBuf, | 220 | path: PathBuf, |
179 | kind: ChangeKind, | 221 | kind: ChangeKind, |
@@ -195,13 +237,15 @@ fn handle_change( | |||
195 | .try_for_each(|rel_path| { | 237 | .try_for_each(|rel_path| { |
196 | let abs_path = rel_path.to_path(&roots.path(root)); | 238 | let abs_path = rel_path.to_path(&roots.path(root)); |
197 | let text = read_to_string(&abs_path); | 239 | let text = read_to_string(&abs_path); |
198 | sender.send(TaskResult::SingleFile { root, path: rel_path, text }) | 240 | let res = TaskResult::SingleFile { root, path: rel_path, text }; |
241 | sender.send(VfsTask(res)) | ||
199 | }) | 242 | }) |
200 | .unwrap() | 243 | .unwrap() |
201 | } | 244 | } |
202 | ChangeKind::Write | ChangeKind::Remove => { | 245 | ChangeKind::Write | ChangeKind::Remove => { |
203 | let text = read_to_string(&path); | 246 | let text = read_to_string(&path); |
204 | sender.send(TaskResult::SingleFile { root, path: rel_path, text }).unwrap(); | 247 | let res = TaskResult::SingleFile { root, path: rel_path, text }; |
248 | sender.send(VfsTask(res)).unwrap(); | ||
205 | } | 249 | } |
206 | } | 250 | } |
207 | } | 251 | } |
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 7f555a3c0..808c138df 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -25,7 +25,6 @@ use std::{ | |||
25 | }; | 25 | }; |
26 | 26 | ||
27 | use crossbeam_channel::Receiver; | 27 | use crossbeam_channel::Receiver; |
28 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; | ||
29 | use relative_path::{RelativePath, RelativePathBuf}; | 28 | use relative_path::{RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 29 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | 30 | ||
@@ -34,14 +33,23 @@ use crate::{ | |||
34 | roots::Roots, | 33 | roots::Roots, |
35 | }; | 34 | }; |
36 | 35 | ||
37 | pub use crate::{ | 36 | pub use crate::roots::VfsRoot; |
38 | io::TaskResult as VfsTask, | 37 | |
39 | roots::VfsRoot, | 38 | /// Opaque wrapper around file-system event. |
40 | }; | 39 | /// |
40 | /// Calling code is expected to just pass `VfsTask` to `handle_task` method. It | ||
41 | /// is exposed as a public API so that the caller can plug vfs events into the | ||
42 | /// main event loop and be notified when changes happen. | ||
43 | pub struct VfsTask(TaskResult); | ||
44 | |||
45 | impl fmt::Debug for VfsTask { | ||
46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
47 | f.write_str("VfsTask { ... }") | ||
48 | } | ||
49 | } | ||
41 | 50 | ||
42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 51 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
43 | pub struct VfsFile(pub RawId); | 52 | pub struct VfsFile(pub u32); |
44 | impl_arena_id!(VfsFile); | ||
45 | 53 | ||
46 | struct VfsFileData { | 54 | struct VfsFileData { |
47 | root: VfsRoot, | 55 | root: VfsRoot, |
@@ -52,8 +60,8 @@ struct VfsFileData { | |||
52 | 60 | ||
53 | pub struct Vfs { | 61 | pub struct Vfs { |
54 | roots: Arc<Roots>, | 62 | roots: Arc<Roots>, |
55 | files: Arena<VfsFile, VfsFileData>, | 63 | files: Vec<VfsFileData>, |
56 | root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>, | 64 | root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, |
57 | pending_changes: Vec<VfsChange>, | 65 | pending_changes: Vec<VfsChange>, |
58 | worker: Worker, | 66 | worker: Worker, |
59 | } | 67 | } |
@@ -68,18 +76,25 @@ impl fmt::Debug for Vfs { | |||
68 | } | 76 | } |
69 | } | 77 | } |
70 | 78 | ||
79 | #[derive(Debug, Clone)] | ||
80 | pub enum VfsChange { | ||
81 | AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> }, | ||
82 | AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> }, | ||
83 | RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf }, | ||
84 | ChangeFile { file: VfsFile, text: Arc<String> }, | ||
85 | } | ||
86 | |||
71 | impl Vfs { | 87 | impl Vfs { |
72 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { | 88 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { |
73 | let roots = Arc::new(Roots::new(roots)); | 89 | let roots = Arc::new(Roots::new(roots)); |
74 | let worker = io::start(Arc::clone(&roots)); | 90 | let worker = io::start(Arc::clone(&roots)); |
75 | let mut root2files = ArenaMap::default(); | 91 | let mut root2files = FxHashMap::default(); |
76 | 92 | ||
77 | for root in roots.iter() { | 93 | for root in roots.iter() { |
78 | root2files.insert(root, Default::default()); | 94 | root2files.insert(root, Default::default()); |
79 | worker.sender().send(io::Task::AddRoot { root }).unwrap(); | 95 | worker.sender.send(io::Task::AddRoot { root }).unwrap(); |
80 | } | 96 | } |
81 | let res = | 97 | let res = Vfs { roots, files: Vec::new(), root2files, worker, pending_changes: Vec::new() }; |
82 | Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; | ||
83 | let vfs_roots = res.roots.iter().collect(); | 98 | let vfs_roots = res.roots.iter().collect(); |
84 | (res, vfs_roots) | 99 | (res, vfs_roots) |
85 | } | 100 | } |
@@ -96,8 +111,8 @@ impl Vfs { | |||
96 | } | 111 | } |
97 | 112 | ||
98 | pub fn file2path(&self, file: VfsFile) -> PathBuf { | 113 | pub fn file2path(&self, file: VfsFile) -> PathBuf { |
99 | let rel_path = &self.files[file].path; | 114 | let rel_path = &self.file(file).path; |
100 | let root_path = &self.roots.path(self.files[file].root); | 115 | let root_path = &self.roots.path(self.file(file).root); |
101 | rel_path.to_path(root_path) | 116 | rel_path.to_path(root_path) |
102 | } | 117 | } |
103 | 118 | ||
@@ -154,23 +169,23 @@ impl Vfs { | |||
154 | mem::replace(&mut self.pending_changes, Vec::new()) | 169 | mem::replace(&mut self.pending_changes, Vec::new()) |
155 | } | 170 | } |
156 | 171 | ||
157 | pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { | 172 | pub fn task_receiver(&self) -> &Receiver<VfsTask> { |
158 | self.worker.receiver() | 173 | &self.worker.receiver |
159 | } | 174 | } |
160 | 175 | ||
161 | pub fn handle_task(&mut self, task: io::TaskResult) { | 176 | pub fn handle_task(&mut self, task: VfsTask) { |
162 | match task { | 177 | match task.0 { |
163 | TaskResult::BulkLoadRoot { root, files } => { | 178 | TaskResult::BulkLoadRoot { root, files } => { |
164 | let mut cur_files = Vec::new(); | 179 | let mut cur_files = Vec::new(); |
165 | // While we were scanning the root in the background, a file might have | 180 | // While we were scanning the root in the background, a file might have |
166 | // been open in the editor, so we need to account for that. | 181 | // been open in the editor, so we need to account for that. |
167 | let existing = self.root2files[root] | 182 | let existing = self.root2files[&root] |
168 | .iter() | 183 | .iter() |
169 | .map(|&file| (self.files[file].path.clone(), file)) | 184 | .map(|&file| (self.file(file).path.clone(), file)) |
170 | .collect::<FxHashMap<_, _>>(); | 185 | .collect::<FxHashMap<_, _>>(); |
171 | for (path, text) in files { | 186 | for (path, text) in files { |
172 | if let Some(&file) = existing.get(&path) { | 187 | if let Some(&file) = existing.get(&path) { |
173 | let text = Arc::clone(&self.files[file].text); | 188 | let text = Arc::clone(&self.file(file).text); |
174 | cur_files.push((file, path, text)); | 189 | cur_files.push((file, path, text)); |
175 | continue; | 190 | continue; |
176 | } | 191 | } |
@@ -184,7 +199,7 @@ impl Vfs { | |||
184 | } | 199 | } |
185 | TaskResult::SingleFile { root, path, text } => { | 200 | TaskResult::SingleFile { root, path, text } => { |
186 | let existing_file = self.find_file(root, &path); | 201 | let existing_file = self.find_file(root, &path); |
187 | if existing_file.map(|file| self.files[file].is_overlayed) == Some(true) { | 202 | if existing_file.map(|file| self.file(file).is_overlayed) == Some(true) { |
188 | return; | 203 | return; |
189 | } | 204 | } |
190 | match (existing_file, text) { | 205 | match (existing_file, text) { |
@@ -240,23 +255,24 @@ impl Vfs { | |||
240 | is_overlayed: bool, | 255 | is_overlayed: bool, |
241 | ) -> VfsFile { | 256 | ) -> VfsFile { |
242 | let data = VfsFileData { root, path, text, is_overlayed }; | 257 | let data = VfsFileData { root, path, text, is_overlayed }; |
243 | let file = self.files.alloc(data); | 258 | let file = VfsFile(self.files.len() as u32); |
244 | self.root2files.get_mut(root).unwrap().insert(file); | 259 | self.files.push(data); |
260 | self.root2files.get_mut(&root).unwrap().insert(file); | ||
245 | file | 261 | file |
246 | } | 262 | } |
247 | 263 | ||
248 | fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { | 264 | fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { |
249 | let mut file_data = &mut self.files[file]; | 265 | let mut file_data = &mut self.file_mut(file); |
250 | file_data.text = new_text; | 266 | file_data.text = new_text; |
251 | file_data.is_overlayed = is_overlayed; | 267 | file_data.is_overlayed = is_overlayed; |
252 | } | 268 | } |
253 | 269 | ||
254 | fn raw_remove_file(&mut self, file: VfsFile) { | 270 | fn raw_remove_file(&mut self, file: VfsFile) { |
255 | // FIXME: use arena with removal | 271 | // FIXME: use arena with removal |
256 | self.files[file].text = Default::default(); | 272 | self.file_mut(file).text = Default::default(); |
257 | self.files[file].path = Default::default(); | 273 | self.file_mut(file).path = Default::default(); |
258 | let root = self.files[file].root; | 274 | let root = self.file(file).root; |
259 | let removed = self.root2files.get_mut(root).unwrap().remove(&file); | 275 | let removed = self.root2files.get_mut(&root).unwrap().remove(&file); |
260 | assert!(removed); | 276 | assert!(removed); |
261 | } | 277 | } |
262 | 278 | ||
@@ -267,14 +283,14 @@ impl Vfs { | |||
267 | } | 283 | } |
268 | 284 | ||
269 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { | 285 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { |
270 | self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path) | 286 | self.root2files[&root].iter().map(|&it| it).find(|&file| self.file(file).path == path) |
271 | } | 287 | } |
272 | } | ||
273 | 288 | ||
274 | #[derive(Debug, Clone)] | 289 | fn file(&self, file: VfsFile) -> &VfsFileData { |
275 | pub enum VfsChange { | 290 | &self.files[file.0 as usize] |
276 | AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> }, | 291 | } |
277 | AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> }, | 292 | |
278 | RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf }, | 293 | fn file_mut(&mut self, file: VfsFile) -> &mut VfsFileData { |
279 | ChangeFile { file: VfsFile, text: Arc<String> }, | 294 | &mut self.files[file.0 as usize] |
295 | } | ||
280 | } | 296 | } |
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs index 5e2776a35..4503458ee 100644 --- a/crates/ra_vfs/src/roots.rs +++ b/crates/ra_vfs/src/roots.rs | |||
@@ -1,16 +1,13 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | iter, | 2 | iter, |
3 | sync::Arc, | ||
4 | path::{Path, PathBuf}, | 3 | path::{Path, PathBuf}, |
5 | }; | 4 | }; |
6 | 5 | ||
7 | use relative_path::{ RelativePath, RelativePathBuf}; | 6 | use relative_path::{ RelativePath, RelativePathBuf}; |
8 | use ra_arena::{impl_arena_id, Arena, RawId}; | ||
9 | 7 | ||
10 | /// VfsRoot identifies a watched directory on the file system. | 8 | /// VfsRoot identifies a watched directory on the file system. |
11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
12 | pub struct VfsRoot(pub RawId); | 10 | pub struct VfsRoot(pub u32); |
13 | impl_arena_id!(VfsRoot); | ||
14 | 11 | ||
15 | /// Describes the contents of a single source root. | 12 | /// Describes the contents of a single source root. |
16 | /// | 13 | /// |
@@ -25,12 +22,12 @@ struct RootData { | |||
25 | } | 22 | } |
26 | 23 | ||
27 | pub(crate) struct Roots { | 24 | pub(crate) struct Roots { |
28 | roots: Arena<VfsRoot, Arc<RootData>>, | 25 | roots: Vec<RootData>, |
29 | } | 26 | } |
30 | 27 | ||
31 | impl Roots { | 28 | impl Roots { |
32 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { | 29 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { |
33 | let mut roots = Arena::default(); | 30 | let mut roots = Vec::new(); |
34 | // A hack to make nesting work. | 31 | // A hack to make nesting work. |
35 | paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len())); | 32 | paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len())); |
36 | paths.dedup(); | 33 | paths.dedup(); |
@@ -38,9 +35,7 @@ impl Roots { | |||
38 | let nested_roots = | 35 | let nested_roots = |
39 | paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>(); | 36 | paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>(); |
40 | 37 | ||
41 | let config = Arc::new(RootData::new(path.clone(), nested_roots)); | 38 | roots.push(RootData::new(path.clone(), nested_roots)); |
42 | |||
43 | roots.alloc(config.clone()); | ||
44 | } | 39 | } |
45 | Roots { roots } | 40 | Roots { roots } |
46 | } | 41 | } |
@@ -54,20 +49,24 @@ impl Roots { | |||
54 | self.roots.len() | 49 | self.roots.len() |
55 | } | 50 | } |
56 | pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a { | 51 | pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a { |
57 | self.roots.iter().map(|(id, _)| id) | 52 | (0..self.roots.len()).into_iter().map(|idx| VfsRoot(idx as u32)) |
58 | } | 53 | } |
59 | pub(crate) fn path(&self, root: VfsRoot) -> &Path { | 54 | pub(crate) fn path(&self, root: VfsRoot) -> &Path { |
60 | self.roots[root].path.as_path() | 55 | self.root(root).path.as_path() |
61 | } | 56 | } |
62 | /// Checks if root contains a path and returns a root-relative path. | 57 | /// Checks if root contains a path and returns a root-relative path. |
63 | pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> { | 58 | pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> { |
64 | let data = &self.roots[root]; | 59 | let data = self.root(root); |
65 | iter::once(&data.path) | 60 | iter::once(&data.path) |
66 | .chain(data.canonical_path.as_ref().into_iter()) | 61 | .chain(data.canonical_path.as_ref().into_iter()) |
67 | .find_map(|base| rel_path(base, path)) | 62 | .find_map(|base| rel_path(base, path)) |
68 | .filter(|path| !data.excluded_dirs.contains(path)) | 63 | .filter(|path| !data.excluded_dirs.contains(path)) |
69 | .filter(|path| !data.is_excluded(path)) | 64 | .filter(|path| !data.is_excluded(path)) |
70 | } | 65 | } |
66 | |||
67 | fn root(&self, root: VfsRoot) -> &RootData { | ||
68 | &self.roots[root.0 as usize] | ||
69 | } | ||
71 | } | 70 | } |
72 | 71 | ||
73 | impl RootData { | 72 | impl RootData { |