diff options
Diffstat (limited to 'crates/ra_lsp_server')
-rw-r--r-- | crates/ra_lsp_server/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 147 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 4 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/path_map.rs | 126 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/server_world.rs | 225 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/vfs.rs | 67 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/main.rs | 3 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/support.rs | 4 |
10 files changed, 169 insertions, 414 deletions
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index ce4f79d46..fc10096e5 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml | |||
@@ -19,12 +19,13 @@ flexi_logger = "0.10.0" | |||
19 | log = "0.4.3" | 19 | log = "0.4.3" |
20 | url_serde = "0.2.0" | 20 | url_serde = "0.2.0" |
21 | languageserver-types = "0.53.0" | 21 | languageserver-types = "0.53.0" |
22 | walkdir = "2.2.0" | 22 | walkdir = "2.2.7" |
23 | im = "12.0.0" | 23 | im = "12.0.0" |
24 | cargo_metadata = "0.6.0" | 24 | cargo_metadata = "0.6.0" |
25 | text_unit = { version = "0.1.2", features = ["serde"] } | 25 | text_unit = { version = "0.1.2", features = ["serde"] } |
26 | smol_str = { version = "0.1.5", features = ["serde"] } | 26 | smol_str = { version = "0.1.5", features = ["serde"] } |
27 | rustc-hash = "1.0" | 27 | rustc-hash = "1.0" |
28 | parking_lot = "0.7.0" | ||
28 | 29 | ||
29 | thread_worker = { path = "../thread_worker" } | 30 | thread_worker = { path = "../thread_worker" } |
30 | ra_syntax = { path = "../ra_syntax" } | 31 | ra_syntax = { path = "../ra_syntax" } |
@@ -32,6 +33,7 @@ ra_editor = { path = "../ra_editor" } | |||
32 | ra_text_edit = { path = "../ra_text_edit" } | 33 | ra_text_edit = { path = "../ra_text_edit" } |
33 | ra_analysis = { path = "../ra_analysis" } | 34 | ra_analysis = { path = "../ra_analysis" } |
34 | gen_lsp_server = { path = "../gen_lsp_server" } | 35 | gen_lsp_server = { path = "../gen_lsp_server" } |
36 | ra_vfs = { path = "../ra_vfs" } | ||
35 | 37 | ||
36 | [dev-dependencies] | 38 | [dev-dependencies] |
37 | tempdir = "0.3.7" | 39 | tempdir = "0.3.7" |
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 1d7258c35..725b1258a 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs | |||
@@ -1,11 +1,9 @@ | |||
1 | mod caps; | 1 | mod caps; |
2 | mod conv; | 2 | mod conv; |
3 | mod main_loop; | 3 | mod main_loop; |
4 | mod path_map; | ||
5 | mod project_model; | 4 | mod project_model; |
6 | pub mod req; | 5 | pub mod req; |
7 | mod server_world; | 6 | mod server_world; |
8 | mod vfs; | ||
9 | 7 | ||
10 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 8 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
11 | pub use crate::{caps::server_capabilities, main_loop::main_loop, main_loop::LspError}; | 9 | pub use crate::{caps::server_capabilities, main_loop::main_loop, main_loop::LspError}; |
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index eab82ee85..d2f16ea97 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -1,7 +1,10 @@ | |||
1 | mod handlers; | 1 | mod handlers; |
2 | mod subscriptions; | 2 | mod subscriptions; |
3 | 3 | ||
4 | use std::path::PathBuf; | 4 | use std::{ |
5 | path::PathBuf, | ||
6 | sync::Arc, | ||
7 | }; | ||
5 | 8 | ||
6 | use crossbeam_channel::{unbounded, select, Receiver, Sender}; | 9 | use crossbeam_channel::{unbounded, select, Receiver, Sender}; |
7 | use gen_lsp_server::{ | 10 | use gen_lsp_server::{ |
@@ -9,8 +12,8 @@ use gen_lsp_server::{ | |||
9 | }; | 12 | }; |
10 | use languageserver_types::NumberOrString; | 13 | use languageserver_types::NumberOrString; |
11 | use ra_analysis::{Canceled, FileId, LibraryData}; | 14 | use ra_analysis::{Canceled, FileId, LibraryData}; |
15 | use ra_vfs::{VfsTask}; | ||
12 | use rayon; | 16 | use rayon; |
13 | use thread_worker::Worker; | ||
14 | use threadpool::ThreadPool; | 17 | use threadpool::ThreadPool; |
15 | use rustc_hash::FxHashSet; | 18 | use rustc_hash::FxHashSet; |
16 | use serde::{de::DeserializeOwned, Serialize}; | 19 | use serde::{de::DeserializeOwned, Serialize}; |
@@ -19,10 +22,9 @@ use failure_derive::Fail; | |||
19 | 22 | ||
20 | use crate::{ | 23 | use crate::{ |
21 | main_loop::subscriptions::Subscriptions, | 24 | main_loop::subscriptions::Subscriptions, |
22 | project_model::{workspace_loader, CargoWorkspace}, | 25 | project_model::{workspace_loader}, |
23 | req, | 26 | req, |
24 | server_world::{ServerWorld, ServerWorldState}, | 27 | server_world::{ServerWorld, ServerWorldState}, |
25 | vfs::{self, FileEvent}, | ||
26 | Result, | 28 | Result, |
27 | }; | 29 | }; |
28 | 30 | ||
@@ -50,32 +52,42 @@ enum Task { | |||
50 | 52 | ||
51 | pub fn main_loop( | 53 | pub fn main_loop( |
52 | internal_mode: bool, | 54 | internal_mode: bool, |
53 | root: PathBuf, | 55 | ws_root: PathBuf, |
54 | publish_decorations: bool, | 56 | publish_decorations: bool, |
55 | msg_receiver: &Receiver<RawMessage>, | 57 | msg_receiver: &Receiver<RawMessage>, |
56 | msg_sender: &Sender<RawMessage>, | 58 | msg_sender: &Sender<RawMessage>, |
57 | ) -> Result<()> { | 59 | ) -> Result<()> { |
58 | let pool = ThreadPool::new(8); | 60 | let pool = ThreadPool::new(8); |
59 | let (task_sender, task_receiver) = unbounded::<Task>(); | 61 | let (task_sender, task_receiver) = unbounded::<Task>(); |
60 | let (fs_worker, fs_watcher) = vfs::roots_loader(); | ||
61 | let (ws_worker, ws_watcher) = workspace_loader(); | 62 | let (ws_worker, ws_watcher) = workspace_loader(); |
62 | 63 | ||
64 | ws_worker.send(ws_root.clone()); | ||
65 | // FIXME: support dynamic workspace loading. | ||
66 | let workspaces = match ws_worker.recv().unwrap() { | ||
67 | Ok(ws) => vec![ws], | ||
68 | Err(e) => { | ||
69 | log::warn!("loading workspace failed: {}", e); | ||
70 | Vec::new() | ||
71 | } | ||
72 | }; | ||
73 | ws_worker.shutdown(); | ||
74 | ws_watcher | ||
75 | .shutdown() | ||
76 | .map_err(|_| format_err!("ws watcher died"))?; | ||
77 | let mut state = ServerWorldState::new(ws_root.clone(), workspaces); | ||
78 | |||
63 | log::info!("server initialized, serving requests"); | 79 | log::info!("server initialized, serving requests"); |
64 | let mut state = ServerWorldState::default(); | ||
65 | 80 | ||
66 | let mut pending_requests = FxHashSet::default(); | 81 | let mut pending_requests = FxHashSet::default(); |
67 | let mut subs = Subscriptions::new(); | 82 | let mut subs = Subscriptions::new(); |
68 | let main_res = main_loop_inner( | 83 | let main_res = main_loop_inner( |
69 | internal_mode, | 84 | internal_mode, |
70 | publish_decorations, | 85 | publish_decorations, |
71 | root, | ||
72 | &pool, | 86 | &pool, |
73 | msg_sender, | 87 | msg_sender, |
74 | msg_receiver, | 88 | msg_receiver, |
75 | task_sender, | 89 | task_sender, |
76 | task_receiver.clone(), | 90 | task_receiver.clone(), |
77 | fs_worker, | ||
78 | ws_worker, | ||
79 | &mut state, | 91 | &mut state, |
80 | &mut pending_requests, | 92 | &mut pending_requests, |
81 | &mut subs, | 93 | &mut subs, |
@@ -88,12 +100,11 @@ pub fn main_loop( | |||
88 | drop(pool); | 100 | drop(pool); |
89 | log::info!("...threadpool has finished"); | 101 | log::info!("...threadpool has finished"); |
90 | 102 | ||
91 | let fs_res = fs_watcher.stop(); | 103 | let vfs = Arc::try_unwrap(state.vfs).expect("all snapshots should be dead"); |
92 | let ws_res = ws_watcher.stop(); | 104 | let vfs_res = vfs.into_inner().shutdown(); |
93 | 105 | ||
94 | main_res?; | 106 | main_res?; |
95 | fs_res.map_err(|_| format_err!("fs watcher died"))?; | 107 | vfs_res.map_err(|_| format_err!("fs watcher died"))?; |
96 | ws_res.map_err(|_| format_err!("ws watcher died"))?; | ||
97 | 108 | ||
98 | Ok(()) | 109 | Ok(()) |
99 | } | 110 | } |
@@ -101,28 +112,22 @@ pub fn main_loop( | |||
101 | fn main_loop_inner( | 112 | fn main_loop_inner( |
102 | internal_mode: bool, | 113 | internal_mode: bool, |
103 | publish_decorations: bool, | 114 | publish_decorations: bool, |
104 | ws_root: PathBuf, | ||
105 | pool: &ThreadPool, | 115 | pool: &ThreadPool, |
106 | msg_sender: &Sender<RawMessage>, | 116 | msg_sender: &Sender<RawMessage>, |
107 | msg_receiver: &Receiver<RawMessage>, | 117 | msg_receiver: &Receiver<RawMessage>, |
108 | task_sender: Sender<Task>, | 118 | task_sender: Sender<Task>, |
109 | task_receiver: Receiver<Task>, | 119 | task_receiver: Receiver<Task>, |
110 | fs_worker: Worker<PathBuf, (PathBuf, Vec<FileEvent>)>, | ||
111 | ws_worker: Worker<PathBuf, Result<CargoWorkspace>>, | ||
112 | state: &mut ServerWorldState, | 120 | state: &mut ServerWorldState, |
113 | pending_requests: &mut FxHashSet<u64>, | 121 | pending_requests: &mut FxHashSet<u64>, |
114 | subs: &mut Subscriptions, | 122 | subs: &mut Subscriptions, |
115 | ) -> Result<()> { | 123 | ) -> Result<()> { |
116 | let (libdata_sender, libdata_receiver) = unbounded(); | 124 | let (libdata_sender, libdata_receiver) = unbounded(); |
117 | ws_worker.send(ws_root.clone()); | ||
118 | fs_worker.send(ws_root.clone()); | ||
119 | loop { | 125 | loop { |
120 | #[derive(Debug)] | 126 | #[derive(Debug)] |
121 | enum Event { | 127 | enum Event { |
122 | Msg(RawMessage), | 128 | Msg(RawMessage), |
123 | Task(Task), | 129 | Task(Task), |
124 | Fs(PathBuf, Vec<FileEvent>), | 130 | Vfs(VfsTask), |
125 | Ws(Result<CargoWorkspace>), | ||
126 | Lib(LibraryData), | 131 | Lib(LibraryData), |
127 | } | 132 | } |
128 | log::trace!("selecting"); | 133 | log::trace!("selecting"); |
@@ -132,77 +137,20 @@ fn main_loop_inner( | |||
132 | None => bail!("client exited without shutdown"), | 137 | None => bail!("client exited without shutdown"), |
133 | }, | 138 | }, |
134 | recv(task_receiver, task) => Event::Task(task.unwrap()), | 139 | recv(task_receiver, task) => Event::Task(task.unwrap()), |
135 | recv(fs_worker.out, events) => match events { | 140 | recv(state.vfs.read().task_receiver(), task) => match task { |
136 | None => bail!("roots watcher died"), | 141 | None => bail!("vfs died"), |
137 | Some((pb, events)) => Event::Fs(pb, events), | 142 | Some(task) => Event::Vfs(task), |
138 | } | ||
139 | recv(ws_worker.out, ws) => match ws { | ||
140 | None => bail!("workspace watcher died"), | ||
141 | Some(ws) => Event::Ws(ws), | ||
142 | } | 143 | } |
143 | recv(libdata_receiver, data) => Event::Lib(data.unwrap()) | 144 | recv(libdata_receiver, data) => Event::Lib(data.unwrap()) |
144 | }; | 145 | }; |
146 | log::info!("{:?}", event); | ||
145 | let mut state_changed = false; | 147 | let mut state_changed = false; |
146 | match event { | 148 | match event { |
147 | Event::Task(task) => on_task(task, msg_sender, pending_requests), | 149 | Event::Task(task) => on_task(task, msg_sender, pending_requests), |
148 | Event::Fs(root, events) => { | 150 | Event::Vfs(task) => { |
149 | log::info!("fs change, {}, {} events", root.display(), events.len()); | 151 | state.vfs.write().handle_task(task); |
150 | if root == ws_root { | ||
151 | state.apply_fs_changes(events); | ||
152 | } else { | ||
153 | let (files, resolver) = state.events_to_files(events); | ||
154 | let sender = libdata_sender.clone(); | ||
155 | pool.execute(move || { | ||
156 | let start = ::std::time::Instant::now(); | ||
157 | log::info!("indexing {} ... ", root.display()); | ||
158 | let data = LibraryData::prepare(files, resolver); | ||
159 | log::info!("indexed {:?} {}", start.elapsed(), root.display()); | ||
160 | sender.send(data); | ||
161 | }); | ||
162 | } | ||
163 | state_changed = true; | 152 | state_changed = true; |
164 | } | 153 | } |
165 | Event::Ws(ws) => match ws { | ||
166 | Ok(ws) => { | ||
167 | let workspaces = vec![ws]; | ||
168 | feedback(internal_mode, "workspace loaded", msg_sender); | ||
169 | for ws in workspaces.iter() { | ||
170 | // Add each library as constant input. If library is | ||
171 | // within the workspace, don't treat it as a library. | ||
172 | // | ||
173 | // HACK: If source roots are nested, pick the outer one. | ||
174 | |||
175 | let mut roots = ws | ||
176 | .packages() | ||
177 | .filter(|pkg| !pkg.is_member(ws)) | ||
178 | .filter_map(|pkg| { | ||
179 | let root = pkg.root(ws).to_path_buf(); | ||
180 | if root.starts_with(&ws_root) { | ||
181 | None | ||
182 | } else { | ||
183 | Some(root) | ||
184 | } | ||
185 | }) | ||
186 | .collect::<Vec<_>>(); | ||
187 | roots.sort_by_key(|it| it.as_os_str().len()); | ||
188 | let unique = roots | ||
189 | .iter() | ||
190 | .enumerate() | ||
191 | .filter(|&(idx, long)| { | ||
192 | !roots[..idx].iter().any(|short| long.starts_with(short)) | ||
193 | }) | ||
194 | .map(|(_idx, root)| root); | ||
195 | |||
196 | for root in unique { | ||
197 | log::debug!("sending root, {}", root.display()); | ||
198 | fs_worker.send(root.to_owned()); | ||
199 | } | ||
200 | } | ||
201 | state.set_workspaces(workspaces); | ||
202 | state_changed = true; | ||
203 | } | ||
204 | Err(e) => log::warn!("loading workspace failed: {}", e), | ||
205 | }, | ||
206 | Event::Lib(lib) => { | 154 | Event::Lib(lib) => { |
207 | feedback(internal_mode, "library loaded", msg_sender); | 155 | feedback(internal_mode, "library loaded", msg_sender); |
208 | state.add_lib(lib); | 156 | state.add_lib(lib); |
@@ -234,6 +182,21 @@ fn main_loop_inner( | |||
234 | }, | 182 | }, |
235 | }; | 183 | }; |
236 | 184 | ||
185 | for lib in state.process_changes() { | ||
186 | let (root, files) = lib; | ||
187 | let sender = libdata_sender.clone(); | ||
188 | pool.execute(move || { | ||
189 | let start = ::std::time::Instant::now(); | ||
190 | log::info!("indexing {:?} ... ", root); | ||
191 | let data = LibraryData::prepare(root, files); | ||
192 | log::info!("indexed {:?} {:?}", start.elapsed(), root); | ||
193 | sender.send(data); | ||
194 | }); | ||
195 | } | ||
196 | if state.roots_to_scan == 0 { | ||
197 | feedback(internal_mode, "workspace loaded", msg_sender); | ||
198 | } | ||
199 | |||
237 | if state_changed { | 200 | if state_changed { |
238 | update_file_notifications_on_threadpool( | 201 | update_file_notifications_on_threadpool( |
239 | pool, | 202 | pool, |
@@ -336,8 +299,13 @@ fn on_notification( | |||
336 | let path = uri | 299 | let path = uri |
337 | .to_file_path() | 300 | .to_file_path() |
338 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | 301 | .map_err(|()| format_err!("invalid uri: {}", uri))?; |
339 | let file_id = state.add_mem_file(path, params.text_document.text); | 302 | if let Some(file_id) = state |
340 | subs.add_sub(file_id); | 303 | .vfs |
304 | .write() | ||
305 | .add_file_overlay(&path, params.text_document.text) | ||
306 | { | ||
307 | subs.add_sub(FileId(file_id.0)); | ||
308 | } | ||
341 | return Ok(()); | 309 | return Ok(()); |
342 | } | 310 | } |
343 | Err(not) => not, | 311 | Err(not) => not, |
@@ -353,7 +321,7 @@ fn on_notification( | |||
353 | .pop() | 321 | .pop() |
354 | .ok_or_else(|| format_err!("empty changes"))? | 322 | .ok_or_else(|| format_err!("empty changes"))? |
355 | .text; | 323 | .text; |
356 | state.change_mem_file(path.as_path(), text)?; | 324 | state.vfs.write().change_file_overlay(path.as_path(), text); |
357 | return Ok(()); | 325 | return Ok(()); |
358 | } | 326 | } |
359 | Err(not) => not, | 327 | Err(not) => not, |
@@ -364,8 +332,9 @@ fn on_notification( | |||
364 | let path = uri | 332 | let path = uri |
365 | .to_file_path() | 333 | .to_file_path() |
366 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | 334 | .map_err(|()| format_err!("invalid uri: {}", uri))?; |
367 | let file_id = state.remove_mem_file(path.as_path())?; | 335 | if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { |
368 | subs.remove_sub(file_id); | 336 | subs.remove_sub(FileId(file_id.0)); |
337 | } | ||
369 | let params = req::PublishDiagnosticsParams { | 338 | let params = req::PublishDiagnosticsParams { |
370 | uri, | 339 | uri, |
371 | diagnostics: Vec::new(), | 340 | diagnostics: Vec::new(), |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index acca480c7..572ae7fb5 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -326,9 +326,9 @@ pub fn handle_runnables( | |||
326 | None => return Ok(None), | 326 | None => return Ok(None), |
327 | }; | 327 | }; |
328 | let file_id = world.analysis().crate_root(crate_id)?; | 328 | let file_id = world.analysis().crate_root(crate_id)?; |
329 | let path = world.path_map.get_path(file_id); | 329 | let path = world.vfs.read().file2path(ra_vfs::VfsFile(file_id.0)); |
330 | let res = world.workspaces.iter().find_map(|ws| { | 330 | let res = world.workspaces.iter().find_map(|ws| { |
331 | let tgt = ws.target_by_root(path)?; | 331 | let tgt = ws.target_by_root(&path)?; |
332 | let res = CargoTargetSpec { | 332 | let res = CargoTargetSpec { |
333 | package: tgt.package(ws).name(ws).to_string(), | 333 | package: tgt.package(ws).name(ws).to_string(), |
334 | target: tgt.name(ws).to_string(), | 334 | target: tgt.name(ws).to_string(), |
diff --git a/crates/ra_lsp_server/src/path_map.rs b/crates/ra_lsp_server/src/path_map.rs deleted file mode 100644 index 02e54629c..000000000 --- a/crates/ra_lsp_server/src/path_map.rs +++ /dev/null | |||
@@ -1,126 +0,0 @@ | |||
1 | use std::{ | ||
2 | fmt, | ||
3 | path::{Component, Path, PathBuf}, | ||
4 | }; | ||
5 | |||
6 | use im; | ||
7 | use ra_analysis::{FileId, FileResolver}; | ||
8 | use relative_path::RelativePath; | ||
9 | |||
10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
11 | pub enum Root { | ||
12 | Workspace, | ||
13 | Lib, | ||
14 | } | ||
15 | |||
16 | #[derive(Default, Clone)] | ||
17 | pub struct PathMap { | ||
18 | next_id: u32, | ||
19 | path2id: im::HashMap<PathBuf, FileId>, | ||
20 | id2path: im::HashMap<FileId, PathBuf>, | ||
21 | id2root: im::HashMap<FileId, Root>, | ||
22 | } | ||
23 | |||
24 | impl fmt::Debug for PathMap { | ||
25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
26 | f.write_str("PathMap { ... }") | ||
27 | } | ||
28 | } | ||
29 | |||
30 | impl PathMap { | ||
31 | pub fn get_or_insert(&mut self, path: PathBuf, root: Root) -> (bool, FileId) { | ||
32 | let mut inserted = false; | ||
33 | let file_id = self | ||
34 | .path2id | ||
35 | .get(path.as_path()) | ||
36 | .map(|&id| id) | ||
37 | .unwrap_or_else(|| { | ||
38 | inserted = true; | ||
39 | let id = self.new_file_id(); | ||
40 | self.insert(path, id, root); | ||
41 | id | ||
42 | }); | ||
43 | (inserted, file_id) | ||
44 | } | ||
45 | pub fn get_id(&self, path: &Path) -> Option<FileId> { | ||
46 | self.path2id.get(path).cloned() | ||
47 | } | ||
48 | pub fn get_path(&self, file_id: FileId) -> &Path { | ||
49 | self.id2path.get(&file_id).unwrap().as_path() | ||
50 | } | ||
51 | pub fn get_root(&self, file_id: FileId) -> Root { | ||
52 | self.id2root[&file_id] | ||
53 | } | ||
54 | fn insert(&mut self, path: PathBuf, file_id: FileId, root: Root) { | ||
55 | self.path2id.insert(path.clone(), file_id); | ||
56 | self.id2path.insert(file_id, path.clone()); | ||
57 | self.id2root.insert(file_id, root); | ||
58 | } | ||
59 | |||
60 | fn new_file_id(&mut self) -> FileId { | ||
61 | let id = FileId(self.next_id); | ||
62 | self.next_id += 1; | ||
63 | id | ||
64 | } | ||
65 | } | ||
66 | |||
67 | impl FileResolver for PathMap { | ||
68 | fn file_stem(&self, file_id: FileId) -> String { | ||
69 | self.get_path(file_id) | ||
70 | .file_stem() | ||
71 | .unwrap() | ||
72 | .to_str() | ||
73 | .unwrap() | ||
74 | .to_string() | ||
75 | } | ||
76 | |||
77 | fn resolve(&self, file_id: FileId, path: &RelativePath) -> Option<FileId> { | ||
78 | let path = path.to_path(&self.get_path(file_id)); | ||
79 | let path = normalize(&path); | ||
80 | self.get_id(&path) | ||
81 | } | ||
82 | |||
83 | fn debug_path(&self, file_id: FileId) -> Option<PathBuf> { | ||
84 | Some(self.get_path(file_id).to_owned()) | ||
85 | } | ||
86 | } | ||
87 | |||
88 | fn normalize(path: &Path) -> PathBuf { | ||
89 | let mut components = path.components().peekable(); | ||
90 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
91 | components.next(); | ||
92 | PathBuf::from(c.as_os_str()) | ||
93 | } else { | ||
94 | PathBuf::new() | ||
95 | }; | ||
96 | |||
97 | for component in components { | ||
98 | match component { | ||
99 | Component::Prefix(..) => unreachable!(), | ||
100 | Component::RootDir => { | ||
101 | ret.push(component.as_os_str()); | ||
102 | } | ||
103 | Component::CurDir => {} | ||
104 | Component::ParentDir => { | ||
105 | ret.pop(); | ||
106 | } | ||
107 | Component::Normal(c) => { | ||
108 | ret.push(c); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | ret | ||
113 | } | ||
114 | |||
115 | #[cfg(test)] | ||
116 | mod test { | ||
117 | use super::*; | ||
118 | |||
119 | #[test] | ||
120 | fn test_resolve() { | ||
121 | let mut m = PathMap::default(); | ||
122 | let (_, id1) = m.get_or_insert(PathBuf::from("/foo"), Root::Workspace); | ||
123 | let (_, id2) = m.get_or_insert(PathBuf::from("/foo/bar.rs"), Root::Workspace); | ||
124 | assert_eq!(m.resolve(id1, &RelativePath::new("bar.rs")), Some(id2),) | ||
125 | } | ||
126 | } | ||
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index b881f8b6f..5852a157d 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs | |||
@@ -69,6 +69,7 @@ impl Package { | |||
69 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | 69 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { |
70 | ws.pkg(self).targets.iter().cloned() | 70 | ws.pkg(self).targets.iter().cloned() |
71 | } | 71 | } |
72 | #[allow(unused)] | ||
72 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | 73 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { |
73 | ws.pkg(self).is_member | 74 | ws.pkg(self).is_member |
74 | } | 75 | } |
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs index ab4c2c8aa..785877c4b 100644 --- a/crates/ra_lsp_server/src/server_world.rs +++ b/crates/ra_lsp_server/src/server_world.rs | |||
@@ -1,154 +1,66 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | fs, | 2 | path::{PathBuf}, |
3 | path::{Path, PathBuf}, | ||
4 | sync::Arc, | 3 | sync::Arc, |
5 | }; | 4 | }; |
6 | 5 | ||
7 | use languageserver_types::Url; | 6 | use languageserver_types::Url; |
8 | use ra_analysis::{ | 7 | use ra_analysis::{ |
9 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FileResolver, LibraryData, | 8 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, |
9 | SourceRootId | ||
10 | }; | 10 | }; |
11 | use ra_vfs::{Vfs, VfsChange, VfsFile}; | ||
11 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
12 | use failure::{bail, format_err}; | 13 | use relative_path::RelativePathBuf; |
14 | use parking_lot::RwLock; | ||
15 | use failure::{format_err}; | ||
13 | 16 | ||
14 | use crate::{ | 17 | use crate::{ |
15 | path_map::{PathMap, Root}, | ||
16 | project_model::{CargoWorkspace, TargetKind}, | 18 | project_model::{CargoWorkspace, TargetKind}, |
17 | vfs::{FileEvent, FileEventKind}, | ||
18 | Result, | 19 | Result, |
19 | }; | 20 | }; |
20 | 21 | ||
21 | #[derive(Debug, Default)] | 22 | #[derive(Debug)] |
22 | pub struct ServerWorldState { | 23 | pub struct ServerWorldState { |
24 | pub roots_to_scan: usize, | ||
25 | pub root: PathBuf, | ||
23 | pub workspaces: Arc<Vec<CargoWorkspace>>, | 26 | pub workspaces: Arc<Vec<CargoWorkspace>>, |
24 | pub analysis_host: AnalysisHost, | 27 | pub analysis_host: AnalysisHost, |
25 | pub path_map: PathMap, | 28 | pub vfs: Arc<RwLock<Vfs>>, |
26 | pub mem_map: FxHashMap<FileId, Option<String>>, | ||
27 | } | 29 | } |
28 | 30 | ||
29 | pub struct ServerWorld { | 31 | pub struct ServerWorld { |
30 | pub workspaces: Arc<Vec<CargoWorkspace>>, | 32 | pub workspaces: Arc<Vec<CargoWorkspace>>, |
31 | pub analysis: Analysis, | 33 | pub analysis: Analysis, |
32 | pub path_map: PathMap, | 34 | pub vfs: Arc<RwLock<Vfs>>, |
33 | } | 35 | } |
34 | 36 | ||
35 | impl ServerWorldState { | 37 | impl ServerWorldState { |
36 | pub fn apply_fs_changes(&mut self, events: Vec<FileEvent>) { | 38 | pub fn new(root: PathBuf, workspaces: Vec<CargoWorkspace>) -> ServerWorldState { |
37 | let mut change = AnalysisChange::new(); | 39 | let mut change = AnalysisChange::new(); |
38 | let mut inserted = false; | ||
39 | { | ||
40 | let pm = &mut self.path_map; | ||
41 | let mm = &mut self.mem_map; | ||
42 | events | ||
43 | .into_iter() | ||
44 | .map(|event| { | ||
45 | let text = match event.kind { | ||
46 | FileEventKind::Add(text) => text, | ||
47 | }; | ||
48 | (event.path, text) | ||
49 | }) | ||
50 | .map(|(path, text)| { | ||
51 | let (ins, file_id) = pm.get_or_insert(path, Root::Workspace); | ||
52 | inserted |= ins; | ||
53 | (file_id, text) | ||
54 | }) | ||
55 | .filter_map(|(file_id, text)| { | ||
56 | if mm.contains_key(&file_id) { | ||
57 | mm.insert(file_id, Some(text)); | ||
58 | None | ||
59 | } else { | ||
60 | Some((file_id, text)) | ||
61 | } | ||
62 | }) | ||
63 | .for_each(|(file_id, text)| change.add_file(file_id, text)); | ||
64 | } | ||
65 | if inserted { | ||
66 | change.set_file_resolver(Arc::new(self.path_map.clone())) | ||
67 | } | ||
68 | self.analysis_host.apply_change(change); | ||
69 | } | ||
70 | pub fn events_to_files( | ||
71 | &mut self, | ||
72 | events: Vec<FileEvent>, | ||
73 | ) -> (Vec<(FileId, String)>, Arc<FileResolver>) { | ||
74 | let files = { | ||
75 | let pm = &mut self.path_map; | ||
76 | events | ||
77 | .into_iter() | ||
78 | .map(|event| { | ||
79 | let FileEventKind::Add(text) = event.kind; | ||
80 | (event.path, text) | ||
81 | }) | ||
82 | .map(|(path, text)| (pm.get_or_insert(path, Root::Lib).1, text)) | ||
83 | .collect() | ||
84 | }; | ||
85 | let resolver = Arc::new(self.path_map.clone()); | ||
86 | (files, resolver) | ||
87 | } | ||
88 | pub fn add_lib(&mut self, data: LibraryData) { | ||
89 | let mut change = AnalysisChange::new(); | ||
90 | change.add_library(data); | ||
91 | self.analysis_host.apply_change(change); | ||
92 | } | ||
93 | 40 | ||
94 | pub fn add_mem_file(&mut self, path: PathBuf, text: String) -> FileId { | 41 | let mut roots = Vec::new(); |
95 | let (inserted, file_id) = self.path_map.get_or_insert(path, Root::Workspace); | 42 | roots.push(root.clone()); |
96 | if self.path_map.get_root(file_id) != Root::Lib { | 43 | for ws in workspaces.iter() { |
97 | let mut change = AnalysisChange::new(); | 44 | for pkg in ws.packages() { |
98 | if inserted { | 45 | roots.push(pkg.root(&ws).to_path_buf()); |
99 | change.add_file(file_id, text); | ||
100 | change.set_file_resolver(Arc::new(self.path_map.clone())); | ||
101 | } else { | ||
102 | change.change_file(file_id, text); | ||
103 | } | 46 | } |
104 | self.analysis_host.apply_change(change); | ||
105 | } | 47 | } |
106 | self.mem_map.insert(file_id, None); | 48 | let roots_to_scan = roots.len(); |
107 | file_id | 49 | let (mut vfs, roots) = Vfs::new(roots); |
108 | } | 50 | for r in roots { |
109 | 51 | let is_local = vfs.root2path(r).starts_with(&root); | |
110 | pub fn change_mem_file(&mut self, path: &Path, text: String) -> Result<()> { | 52 | change.add_root(SourceRootId(r.0), is_local); |
111 | let file_id = self | ||
112 | .path_map | ||
113 | .get_id(path) | ||
114 | .ok_or_else(|| format_err!("change to unknown file: {}", path.display()))?; | ||
115 | if self.path_map.get_root(file_id) != Root::Lib { | ||
116 | let mut change = AnalysisChange::new(); | ||
117 | change.change_file(file_id, text); | ||
118 | self.analysis_host.apply_change(change); | ||
119 | } | 53 | } |
120 | Ok(()) | ||
121 | } | ||
122 | 54 | ||
123 | pub fn remove_mem_file(&mut self, path: &Path) -> Result<FileId> { | ||
124 | let file_id = self | ||
125 | .path_map | ||
126 | .get_id(path) | ||
127 | .ok_or_else(|| format_err!("change to unknown file: {}", path.display()))?; | ||
128 | match self.mem_map.remove(&file_id) { | ||
129 | Some(_) => (), | ||
130 | None => bail!("unmatched close notification"), | ||
131 | }; | ||
132 | // Do this via file watcher ideally. | ||
133 | let text = fs::read_to_string(path).ok(); | ||
134 | if self.path_map.get_root(file_id) != Root::Lib { | ||
135 | let mut change = AnalysisChange::new(); | ||
136 | if let Some(text) = text { | ||
137 | change.change_file(file_id, text); | ||
138 | } | ||
139 | self.analysis_host.apply_change(change); | ||
140 | } | ||
141 | Ok(file_id) | ||
142 | } | ||
143 | pub fn set_workspaces(&mut self, ws: Vec<CargoWorkspace>) { | ||
144 | let mut crate_graph = CrateGraph::default(); | 55 | let mut crate_graph = CrateGraph::default(); |
145 | let mut pkg_to_lib_crate = FxHashMap::default(); | 56 | let mut pkg_to_lib_crate = FxHashMap::default(); |
146 | let mut pkg_crates = FxHashMap::default(); | 57 | let mut pkg_crates = FxHashMap::default(); |
147 | for ws in ws.iter() { | 58 | for ws in workspaces.iter() { |
148 | for pkg in ws.packages() { | 59 | for pkg in ws.packages() { |
149 | for tgt in pkg.targets(ws) { | 60 | for tgt in pkg.targets(ws) { |
150 | let root = tgt.root(ws); | 61 | let root = tgt.root(ws); |
151 | if let Some(file_id) = self.path_map.get_id(root) { | 62 | if let Some(file_id) = vfs.load(root) { |
63 | let file_id = FileId(file_id.0); | ||
152 | let crate_id = crate_graph.add_crate_root(file_id); | 64 | let crate_id = crate_graph.add_crate_root(file_id); |
153 | if tgt.kind(ws) == TargetKind::Lib { | 65 | if tgt.kind(ws) == TargetKind::Lib { |
154 | pkg_to_lib_crate.insert(pkg, crate_id); | 66 | pkg_to_lib_crate.insert(pkg, crate_id); |
@@ -170,16 +82,80 @@ impl ServerWorldState { | |||
170 | } | 82 | } |
171 | } | 83 | } |
172 | } | 84 | } |
173 | self.workspaces = Arc::new(ws); | ||
174 | let mut change = AnalysisChange::new(); | ||
175 | change.set_crate_graph(crate_graph); | 85 | change.set_crate_graph(crate_graph); |
86 | |||
87 | let mut analysis_host = AnalysisHost::default(); | ||
88 | analysis_host.apply_change(change); | ||
89 | ServerWorldState { | ||
90 | roots_to_scan, | ||
91 | root, | ||
92 | workspaces: Arc::new(workspaces), | ||
93 | analysis_host, | ||
94 | vfs: Arc::new(RwLock::new(vfs)), | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /// Returns a vec of libraries | ||
99 | /// FIXME: better API here | ||
100 | pub fn process_changes( | ||
101 | &mut self, | ||
102 | ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> { | ||
103 | let changes = self.vfs.write().commit_changes(); | ||
104 | if changes.is_empty() { | ||
105 | return Vec::new(); | ||
106 | } | ||
107 | let mut libs = Vec::new(); | ||
108 | let mut change = AnalysisChange::new(); | ||
109 | for c in changes { | ||
110 | log::info!("vfs change {:?}", c); | ||
111 | match c { | ||
112 | VfsChange::AddRoot { root, files } => { | ||
113 | let root_path = self.vfs.read().root2path(root); | ||
114 | if root_path.starts_with(&self.root) { | ||
115 | self.roots_to_scan -= 1; | ||
116 | for (file, path, text) in files { | ||
117 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | ||
118 | } | ||
119 | } else { | ||
120 | let files = files | ||
121 | .into_iter() | ||
122 | .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text)) | ||
123 | .collect(); | ||
124 | libs.push((SourceRootId(root.0), files)); | ||
125 | } | ||
126 | } | ||
127 | VfsChange::AddFile { | ||
128 | root, | ||
129 | file, | ||
130 | path, | ||
131 | text, | ||
132 | } => { | ||
133 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | ||
134 | } | ||
135 | VfsChange::RemoveFile { root, file, path } => { | ||
136 | change.remove_file(SourceRootId(root.0), FileId(file.0), path) | ||
137 | } | ||
138 | VfsChange::ChangeFile { file, text } => { | ||
139 | change.change_file(FileId(file.0), text); | ||
140 | } | ||
141 | } | ||
142 | } | ||
176 | self.analysis_host.apply_change(change); | 143 | self.analysis_host.apply_change(change); |
144 | libs | ||
177 | } | 145 | } |
146 | |||
147 | pub fn add_lib(&mut self, data: LibraryData) { | ||
148 | self.roots_to_scan -= 1; | ||
149 | let mut change = AnalysisChange::new(); | ||
150 | change.add_library(data); | ||
151 | self.analysis_host.apply_change(change); | ||
152 | } | ||
153 | |||
178 | pub fn snapshot(&self) -> ServerWorld { | 154 | pub fn snapshot(&self) -> ServerWorld { |
179 | ServerWorld { | 155 | ServerWorld { |
180 | workspaces: Arc::clone(&self.workspaces), | 156 | workspaces: Arc::clone(&self.workspaces), |
181 | analysis: self.analysis_host.analysis(), | 157 | analysis: self.analysis_host.analysis(), |
182 | path_map: self.path_map.clone(), | 158 | vfs: Arc::clone(&self.vfs), |
183 | } | 159 | } |
184 | } | 160 | } |
185 | } | 161 | } |
@@ -193,15 +169,18 @@ impl ServerWorld { | |||
193 | let path = uri | 169 | let path = uri |
194 | .to_file_path() | 170 | .to_file_path() |
195 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | 171 | .map_err(|()| format_err!("invalid uri: {}", uri))?; |
196 | self.path_map | 172 | let file = self |
197 | .get_id(&path) | 173 | .vfs |
198 | .ok_or_else(|| format_err!("unknown file: {}", path.display())) | 174 | .read() |
175 | .path2file(&path) | ||
176 | .ok_or_else(|| format_err!("unknown file: {}", path.display()))?; | ||
177 | Ok(FileId(file.0)) | ||
199 | } | 178 | } |
200 | 179 | ||
201 | pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { | 180 | pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { |
202 | let path = self.path_map.get_path(id); | 181 | let path = self.vfs.read().file2path(VfsFile(id.0)); |
203 | let url = Url::from_file_path(path) | 182 | let url = Url::from_file_path(&path) |
204 | .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?; | 183 | .map_err(|_| format_err!("can't convert path to url: {}", path.display()))?; |
205 | Ok(url) | 184 | Ok(url) |
206 | } | 185 | } |
207 | } | 186 | } |
diff --git a/crates/ra_lsp_server/src/vfs.rs b/crates/ra_lsp_server/src/vfs.rs deleted file mode 100644 index fcf7693d8..000000000 --- a/crates/ra_lsp_server/src/vfs.rs +++ /dev/null | |||
@@ -1,67 +0,0 @@ | |||
1 | use std::{ | ||
2 | fs, | ||
3 | path::{Path, PathBuf}, | ||
4 | }; | ||
5 | |||
6 | use walkdir::WalkDir; | ||
7 | use thread_worker::{WorkerHandle, Worker}; | ||
8 | |||
9 | #[derive(Debug)] | ||
10 | pub struct FileEvent { | ||
11 | pub path: PathBuf, | ||
12 | pub kind: FileEventKind, | ||
13 | } | ||
14 | |||
15 | #[derive(Debug)] | ||
16 | pub enum FileEventKind { | ||
17 | Add(String), | ||
18 | } | ||
19 | |||
20 | pub fn roots_loader() -> (Worker<PathBuf, (PathBuf, Vec<FileEvent>)>, WorkerHandle) { | ||
21 | thread_worker::spawn::<PathBuf, (PathBuf, Vec<FileEvent>), _>( | ||
22 | "roots loader", | ||
23 | 128, | ||
24 | |input_receiver, output_sender| { | ||
25 | input_receiver | ||
26 | .map(|path| { | ||
27 | log::debug!("loading {} ...", path.as_path().display()); | ||
28 | let events = load_root(path.as_path()); | ||
29 | log::debug!("... loaded {}", path.as_path().display()); | ||
30 | (path, events) | ||
31 | }) | ||
32 | .for_each(|it| output_sender.send(it)) | ||
33 | }, | ||
34 | ) | ||
35 | } | ||
36 | |||
37 | fn load_root(path: &Path) -> Vec<FileEvent> { | ||
38 | let mut res = Vec::new(); | ||
39 | for entry in WalkDir::new(path) { | ||
40 | let entry = match entry { | ||
41 | Ok(entry) => entry, | ||
42 | Err(e) => { | ||
43 | log::warn!("watcher error: {}", e); | ||
44 | continue; | ||
45 | } | ||
46 | }; | ||
47 | if !entry.file_type().is_file() { | ||
48 | continue; | ||
49 | } | ||
50 | let path = entry.path(); | ||
51 | if path.extension().and_then(|os| os.to_str()) != Some("rs") { | ||
52 | continue; | ||
53 | } | ||
54 | let text = match fs::read_to_string(path) { | ||
55 | Ok(text) => text, | ||
56 | Err(e) => { | ||
57 | log::warn!("watcher error: {}", e); | ||
58 | continue; | ||
59 | } | ||
60 | }; | ||
61 | res.push(FileEvent { | ||
62 | path: path.to_owned(), | ||
63 | kind: FileEventKind::Add(text), | ||
64 | }) | ||
65 | } | ||
66 | res | ||
67 | } | ||
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs index 26f5e3f20..029a55d40 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs | |||
@@ -1,9 +1,7 @@ | |||
1 | mod support; | 1 | mod support; |
2 | 2 | ||
3 | use serde_json::json; | 3 | use serde_json::json; |
4 | |||
5 | use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams}; | 4 | use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams}; |
6 | |||
7 | use languageserver_types::{Position, Range, CodeActionContext}; | 5 | use languageserver_types::{Position, Range, CodeActionContext}; |
8 | 6 | ||
9 | use crate::support::project; | 7 | use crate::support::project; |
@@ -20,6 +18,7 @@ fn foo() { | |||
20 | } | 18 | } |
21 | ", | 19 | ", |
22 | ); | 20 | ); |
21 | server.wait_for_feedback("workspace loaded"); | ||
23 | server.request::<Runnables>( | 22 | server.request::<Runnables>( |
24 | RunnablesParams { | 23 | RunnablesParams { |
25 | text_document: server.doc_id("lib.rs"), | 24 | text_document: server.doc_id("lib.rs"), |
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs index 07a878a26..c14d287ca 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs | |||
@@ -174,11 +174,11 @@ impl Server { | |||
174 | impl Drop for Server { | 174 | impl Drop for Server { |
175 | fn drop(&mut self) { | 175 | fn drop(&mut self) { |
176 | self.send_request::<Shutdown>(666, ()); | 176 | self.send_request::<Shutdown>(666, ()); |
177 | let receiver = self.worker.take().unwrap().stop(); | 177 | let receiver = self.worker.take().unwrap().shutdown(); |
178 | while let Some(msg) = recv_timeout(&receiver) { | 178 | while let Some(msg) = recv_timeout(&receiver) { |
179 | drop(msg); | 179 | drop(msg); |
180 | } | 180 | } |
181 | self.watcher.take().unwrap().stop().unwrap(); | 181 | self.watcher.take().unwrap().shutdown().unwrap(); |
182 | } | 182 | } |
183 | } | 183 | } |
184 | 184 | ||