diff options
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop.rs')
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs new file mode 100644 index 000000000..0e1878906 --- /dev/null +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -0,0 +1,495 @@ | |||
1 | mod handlers; | ||
2 | mod subscriptions; | ||
3 | |||
4 | use std::path::PathBuf; | ||
5 | |||
6 | use crossbeam_channel::{unbounded, select, Receiver, Sender}; | ||
7 | use gen_lsp_server::{ | ||
8 | handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, | ||
9 | }; | ||
10 | use languageserver_types::NumberOrString; | ||
11 | use ra_analysis::{Canceled, FileId, LibraryData}; | ||
12 | use rayon::{self, ThreadPool}; | ||
13 | use rustc_hash::FxHashSet; | ||
14 | use serde::{de::DeserializeOwned, Serialize}; | ||
15 | use failure::{format_err, bail}; | ||
16 | use failure_derive::Fail; | ||
17 | |||
18 | use crate::{ | ||
19 | main_loop::subscriptions::Subscriptions, | ||
20 | project_model::{workspace_loader, CargoWorkspace}, | ||
21 | req, | ||
22 | server_world::{ServerWorld, ServerWorldState}, | ||
23 | thread_watcher::Worker, | ||
24 | vfs::{self, FileEvent}, | ||
25 | Result, | ||
26 | }; | ||
27 | |||
28 | #[derive(Debug, Fail)] | ||
29 | #[fail( | ||
30 | display = "Language Server request failed with {}. ({})", | ||
31 | code, message | ||
32 | )] | ||
33 | pub struct LspError { | ||
34 | pub code: i32, | ||
35 | pub message: String, | ||
36 | } | ||
37 | |||
38 | impl LspError { | ||
39 | pub fn new(code: i32, message: String) -> LspError { | ||
40 | LspError { code, message } | ||
41 | } | ||
42 | } | ||
43 | |||
44 | #[derive(Debug)] | ||
45 | enum Task { | ||
46 | Respond(RawResponse), | ||
47 | Notify(RawNotification), | ||
48 | } | ||
49 | |||
50 | pub fn main_loop( | ||
51 | internal_mode: bool, | ||
52 | root: PathBuf, | ||
53 | publish_decorations: bool, | ||
54 | msg_receiver: &Receiver<RawMessage>, | ||
55 | msg_sender: &Sender<RawMessage>, | ||
56 | ) -> Result<()> { | ||
57 | let pool = rayon::ThreadPoolBuilder::new() | ||
58 | .num_threads(4) | ||
59 | .panic_handler(|_| log::error!("thread panicked :(")) | ||
60 | .build() | ||
61 | .unwrap(); | ||
62 | let (task_sender, task_receiver) = unbounded::<Task>(); | ||
63 | let (fs_worker, fs_watcher) = vfs::roots_loader(); | ||
64 | let (ws_worker, ws_watcher) = workspace_loader(); | ||
65 | |||
66 | log::info!("server initialized, serving requests"); | ||
67 | let mut state = ServerWorldState::default(); | ||
68 | |||
69 | let mut pending_requests = FxHashSet::default(); | ||
70 | let mut subs = Subscriptions::new(); | ||
71 | let main_res = main_loop_inner( | ||
72 | internal_mode, | ||
73 | publish_decorations, | ||
74 | root, | ||
75 | &pool, | ||
76 | msg_sender, | ||
77 | msg_receiver, | ||
78 | task_sender, | ||
79 | task_receiver.clone(), | ||
80 | fs_worker, | ||
81 | ws_worker, | ||
82 | &mut state, | ||
83 | &mut pending_requests, | ||
84 | &mut subs, | ||
85 | ); | ||
86 | |||
87 | log::info!("waiting for tasks to finish..."); | ||
88 | task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests)); | ||
89 | log::info!("...tasks have finished"); | ||
90 | log::info!("joining threadpool..."); | ||
91 | drop(pool); | ||
92 | log::info!("...threadpool has finished"); | ||
93 | |||
94 | let fs_res = fs_watcher.stop(); | ||
95 | let ws_res = ws_watcher.stop(); | ||
96 | |||
97 | main_res?; | ||
98 | fs_res?; | ||
99 | ws_res?; | ||
100 | |||
101 | Ok(()) | ||
102 | } | ||
103 | |||
104 | fn main_loop_inner( | ||
105 | internal_mode: bool, | ||
106 | publish_decorations: bool, | ||
107 | ws_root: PathBuf, | ||
108 | pool: &ThreadPool, | ||
109 | msg_sender: &Sender<RawMessage>, | ||
110 | msg_receiver: &Receiver<RawMessage>, | ||
111 | task_sender: Sender<Task>, | ||
112 | task_receiver: Receiver<Task>, | ||
113 | fs_worker: Worker<PathBuf, (PathBuf, Vec<FileEvent>)>, | ||
114 | ws_worker: Worker<PathBuf, Result<CargoWorkspace>>, | ||
115 | state: &mut ServerWorldState, | ||
116 | pending_requests: &mut FxHashSet<u64>, | ||
117 | subs: &mut Subscriptions, | ||
118 | ) -> Result<()> { | ||
119 | let (libdata_sender, libdata_receiver) = unbounded(); | ||
120 | ws_worker.send(ws_root.clone()); | ||
121 | fs_worker.send(ws_root.clone()); | ||
122 | loop { | ||
123 | #[derive(Debug)] | ||
124 | enum Event { | ||
125 | Msg(RawMessage), | ||
126 | Task(Task), | ||
127 | Fs(PathBuf, Vec<FileEvent>), | ||
128 | Ws(Result<CargoWorkspace>), | ||
129 | Lib(LibraryData), | ||
130 | } | ||
131 | log::trace!("selecting"); | ||
132 | let event = select! { | ||
133 | recv(msg_receiver, msg) => match msg { | ||
134 | Some(msg) => Event::Msg(msg), | ||
135 | None => bail!("client exited without shutdown"), | ||
136 | }, | ||
137 | recv(task_receiver, task) => Event::Task(task.unwrap()), | ||
138 | recv(fs_worker.out, events) => match events { | ||
139 | None => bail!("roots watcher died"), | ||
140 | Some((pb, events)) => Event::Fs(pb, events), | ||
141 | } | ||
142 | recv(ws_worker.out, ws) => match ws { | ||
143 | None => bail!("workspace watcher died"), | ||
144 | Some(ws) => Event::Ws(ws), | ||
145 | } | ||
146 | recv(libdata_receiver, data) => Event::Lib(data.unwrap()) | ||
147 | }; | ||
148 | let mut state_changed = false; | ||
149 | match event { | ||
150 | Event::Task(task) => on_task(task, msg_sender, pending_requests), | ||
151 | Event::Fs(root, events) => { | ||
152 | log::info!("fs change, {}, {} events", root.display(), events.len()); | ||
153 | if root == ws_root { | ||
154 | state.apply_fs_changes(events); | ||
155 | } else { | ||
156 | let (files, resolver) = state.events_to_files(events); | ||
157 | let sender = libdata_sender.clone(); | ||
158 | pool.spawn(move || { | ||
159 | let start = ::std::time::Instant::now(); | ||
160 | log::info!("indexing {} ... ", root.display()); | ||
161 | let data = LibraryData::prepare(files, resolver); | ||
162 | log::info!("indexed {:?} {}", start.elapsed(), root.display()); | ||
163 | sender.send(data); | ||
164 | }); | ||
165 | } | ||
166 | state_changed = true; | ||
167 | } | ||
168 | Event::Ws(ws) => match ws { | ||
169 | Ok(ws) => { | ||
170 | let workspaces = vec![ws]; | ||
171 | feedback(internal_mode, "workspace loaded", msg_sender); | ||
172 | for ws in workspaces.iter() { | ||
173 | // Add each library as constant input. If library is | ||
174 | // within the workspace, don't treat it as a library. | ||
175 | // | ||
176 | // HACK: If source roots are nested, pick the outer one. | ||
177 | |||
178 | let mut roots = ws | ||
179 | .packages() | ||
180 | .filter(|pkg| !pkg.is_member(ws)) | ||
181 | .filter_map(|pkg| { | ||
182 | let root = pkg.root(ws).to_path_buf(); | ||
183 | if root.starts_with(&ws_root) { | ||
184 | None | ||
185 | } else { | ||
186 | Some(root) | ||
187 | } | ||
188 | }) | ||
189 | .collect::<Vec<_>>(); | ||
190 | roots.sort_by_key(|it| it.as_os_str().len()); | ||
191 | let unique = roots | ||
192 | .iter() | ||
193 | .enumerate() | ||
194 | .filter(|&(idx, long)| { | ||
195 | !roots[..idx].iter().any(|short| long.starts_with(short)) | ||
196 | }) | ||
197 | .map(|(_idx, root)| root); | ||
198 | |||
199 | for root in unique { | ||
200 | log::debug!("sending root, {}", root.display()); | ||
201 | fs_worker.send(root.to_owned()); | ||
202 | } | ||
203 | } | ||
204 | state.set_workspaces(workspaces); | ||
205 | state_changed = true; | ||
206 | } | ||
207 | Err(e) => log::warn!("loading workspace failed: {}", e), | ||
208 | }, | ||
209 | Event::Lib(lib) => { | ||
210 | feedback(internal_mode, "library loaded", msg_sender); | ||
211 | state.add_lib(lib); | ||
212 | } | ||
213 | Event::Msg(msg) => match msg { | ||
214 | RawMessage::Request(req) => { | ||
215 | let req = match handle_shutdown(req, msg_sender) { | ||
216 | Some(req) => req, | ||
217 | None => return Ok(()), | ||
218 | }; | ||
219 | match on_request(state, pending_requests, pool, &task_sender, req)? { | ||
220 | None => (), | ||
221 | Some(req) => { | ||
222 | log::error!("unknown request: {:?}", req); | ||
223 | let resp = RawResponse::err( | ||
224 | req.id, | ||
225 | ErrorCode::MethodNotFound as i32, | ||
226 | "unknown request".to_string(), | ||
227 | ); | ||
228 | msg_sender.send(RawMessage::Response(resp)) | ||
229 | } | ||
230 | } | ||
231 | } | ||
232 | RawMessage::Notification(not) => { | ||
233 | on_notification(msg_sender, state, pending_requests, subs, not)?; | ||
234 | state_changed = true; | ||
235 | } | ||
236 | RawMessage::Response(resp) => log::error!("unexpected response: {:?}", resp), | ||
237 | }, | ||
238 | }; | ||
239 | |||
240 | if state_changed { | ||
241 | update_file_notifications_on_threadpool( | ||
242 | pool, | ||
243 | state.snapshot(), | ||
244 | publish_decorations, | ||
245 | task_sender.clone(), | ||
246 | subs.subscriptions(), | ||
247 | ) | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | |||
252 | fn on_task(task: Task, msg_sender: &Sender<RawMessage>, pending_requests: &mut FxHashSet<u64>) { | ||
253 | match task { | ||
254 | Task::Respond(response) => { | ||
255 | if pending_requests.remove(&response.id) { | ||
256 | msg_sender.send(RawMessage::Response(response)) | ||
257 | } | ||
258 | } | ||
259 | Task::Notify(n) => msg_sender.send(RawMessage::Notification(n)), | ||
260 | } | ||
261 | } | ||
262 | |||
263 | fn on_request( | ||
264 | world: &mut ServerWorldState, | ||
265 | pending_requests: &mut FxHashSet<u64>, | ||
266 | pool: &ThreadPool, | ||
267 | sender: &Sender<Task>, | ||
268 | req: RawRequest, | ||
269 | ) -> Result<Option<RawRequest>> { | ||
270 | let mut pool_dispatcher = PoolDispatcher { | ||
271 | req: Some(req), | ||
272 | res: None, | ||
273 | pool, | ||
274 | world, | ||
275 | sender, | ||
276 | }; | ||
277 | let req = pool_dispatcher | ||
278 | .on::<req::SyntaxTree>(handlers::handle_syntax_tree)? | ||
279 | .on::<req::ExtendSelection>(handlers::handle_extend_selection)? | ||
280 | .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)? | ||
281 | .on::<req::JoinLines>(handlers::handle_join_lines)? | ||
282 | .on::<req::OnEnter>(handlers::handle_on_enter)? | ||
283 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? | ||
284 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? | ||
285 | .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? | ||
286 | .on::<req::GotoDefinition>(handlers::handle_goto_definition)? | ||
287 | .on::<req::ParentModule>(handlers::handle_parent_module)? | ||
288 | .on::<req::Runnables>(handlers::handle_runnables)? | ||
289 | .on::<req::DecorationsRequest>(handlers::handle_decorations)? | ||
290 | .on::<req::Completion>(handlers::handle_completion)? | ||
291 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? | ||
292 | .on::<req::FoldingRangeRequest>(handlers::handle_folding_range)? | ||
293 | .on::<req::SignatureHelpRequest>(handlers::handle_signature_help)? | ||
294 | .on::<req::HoverRequest>(handlers::handle_hover)? | ||
295 | .on::<req::PrepareRenameRequest>(handlers::handle_prepare_rename)? | ||
296 | .on::<req::Rename>(handlers::handle_rename)? | ||
297 | .on::<req::References>(handlers::handle_references)? | ||
298 | .finish(); | ||
299 | match req { | ||
300 | Ok(id) => { | ||
301 | let inserted = pending_requests.insert(id); | ||
302 | assert!(inserted, "duplicate request: {}", id); | ||
303 | Ok(None) | ||
304 | } | ||
305 | Err(req) => Ok(Some(req)), | ||
306 | } | ||
307 | } | ||
308 | |||
309 | fn on_notification( | ||
310 | msg_sender: &Sender<RawMessage>, | ||
311 | state: &mut ServerWorldState, | ||
312 | pending_requests: &mut FxHashSet<u64>, | ||
313 | subs: &mut Subscriptions, | ||
314 | not: RawNotification, | ||
315 | ) -> Result<()> { | ||
316 | let not = match not.cast::<req::Cancel>() { | ||
317 | Ok(params) => { | ||
318 | let id = match params.id { | ||
319 | NumberOrString::Number(id) => id, | ||
320 | NumberOrString::String(id) => { | ||
321 | panic!("string id's not supported: {:?}", id); | ||
322 | } | ||
323 | }; | ||
324 | pending_requests.remove(&id); | ||
325 | return Ok(()); | ||
326 | } | ||
327 | Err(not) => not, | ||
328 | }; | ||
329 | let not = match not.cast::<req::DidOpenTextDocument>() { | ||
330 | Ok(params) => { | ||
331 | let uri = params.text_document.uri; | ||
332 | let path = uri | ||
333 | .to_file_path() | ||
334 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
335 | let file_id = state.add_mem_file(path, params.text_document.text); | ||
336 | subs.add_sub(file_id); | ||
337 | return Ok(()); | ||
338 | } | ||
339 | Err(not) => not, | ||
340 | }; | ||
341 | let not = match not.cast::<req::DidChangeTextDocument>() { | ||
342 | Ok(mut params) => { | ||
343 | let uri = params.text_document.uri; | ||
344 | let path = uri | ||
345 | .to_file_path() | ||
346 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
347 | let text = params | ||
348 | .content_changes | ||
349 | .pop() | ||
350 | .ok_or_else(|| format_err!("empty changes"))? | ||
351 | .text; | ||
352 | state.change_mem_file(path.as_path(), text)?; | ||
353 | return Ok(()); | ||
354 | } | ||
355 | Err(not) => not, | ||
356 | }; | ||
357 | let not = match not.cast::<req::DidCloseTextDocument>() { | ||
358 | Ok(params) => { | ||
359 | let uri = params.text_document.uri; | ||
360 | let path = uri | ||
361 | .to_file_path() | ||
362 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
363 | let file_id = state.remove_mem_file(path.as_path())?; | ||
364 | subs.remove_sub(file_id); | ||
365 | let params = req::PublishDiagnosticsParams { | ||
366 | uri, | ||
367 | diagnostics: Vec::new(), | ||
368 | }; | ||
369 | let not = RawNotification::new::<req::PublishDiagnostics>(¶ms); | ||
370 | msg_sender.send(RawMessage::Notification(not)); | ||
371 | return Ok(()); | ||
372 | } | ||
373 | Err(not) => not, | ||
374 | }; | ||
375 | log::error!("unhandled notification: {:?}", not); | ||
376 | Ok(()) | ||
377 | } | ||
378 | |||
379 | struct PoolDispatcher<'a> { | ||
380 | req: Option<RawRequest>, | ||
381 | res: Option<u64>, | ||
382 | pool: &'a ThreadPool, | ||
383 | world: &'a ServerWorldState, | ||
384 | sender: &'a Sender<Task>, | ||
385 | } | ||
386 | |||
387 | impl<'a> PoolDispatcher<'a> { | ||
388 | fn on<'b, R>( | ||
389 | &'b mut self, | ||
390 | f: fn(ServerWorld, R::Params) -> Result<R::Result>, | ||
391 | ) -> Result<&'b mut Self> | ||
392 | where | ||
393 | R: req::Request, | ||
394 | R::Params: DeserializeOwned + Send + 'static, | ||
395 | R::Result: Serialize + 'static, | ||
396 | { | ||
397 | let req = match self.req.take() { | ||
398 | None => return Ok(self), | ||
399 | Some(req) => req, | ||
400 | }; | ||
401 | match req.cast::<R>() { | ||
402 | Ok((id, params)) => { | ||
403 | let world = self.world.snapshot(); | ||
404 | let sender = self.sender.clone(); | ||
405 | self.pool.spawn(move || { | ||
406 | let resp = match f(world, params) { | ||
407 | Ok(resp) => RawResponse::ok::<R>(id, &resp), | ||
408 | Err(e) => match e.downcast::<LspError>() { | ||
409 | Ok(lsp_error) => { | ||
410 | RawResponse::err(id, lsp_error.code, lsp_error.message) | ||
411 | } | ||
412 | Err(e) => { | ||
413 | if is_canceled(&e) { | ||
414 | RawResponse::err( | ||
415 | id, | ||
416 | ErrorCode::RequestCancelled as i32, | ||
417 | e.to_string(), | ||
418 | ) | ||
419 | } else { | ||
420 | RawResponse::err( | ||
421 | id, | ||
422 | ErrorCode::InternalError as i32, | ||
423 | format!("{}\n{}", e, e.backtrace()), | ||
424 | ) | ||
425 | } | ||
426 | } | ||
427 | }, | ||
428 | }; | ||
429 | let task = Task::Respond(resp); | ||
430 | sender.send(task); | ||
431 | }); | ||
432 | self.res = Some(id); | ||
433 | } | ||
434 | Err(req) => self.req = Some(req), | ||
435 | } | ||
436 | Ok(self) | ||
437 | } | ||
438 | |||
439 | fn finish(&mut self) -> ::std::result::Result<u64, RawRequest> { | ||
440 | match (self.res.take(), self.req.take()) { | ||
441 | (Some(res), None) => Ok(res), | ||
442 | (None, Some(req)) => Err(req), | ||
443 | _ => unreachable!(), | ||
444 | } | ||
445 | } | ||
446 | } | ||
447 | |||
448 | fn update_file_notifications_on_threadpool( | ||
449 | pool: &ThreadPool, | ||
450 | world: ServerWorld, | ||
451 | publish_decorations: bool, | ||
452 | sender: Sender<Task>, | ||
453 | subscriptions: Vec<FileId>, | ||
454 | ) { | ||
455 | pool.spawn(move || { | ||
456 | for file_id in subscriptions { | ||
457 | match handlers::publish_diagnostics(&world, file_id) { | ||
458 | Err(e) => { | ||
459 | if !is_canceled(&e) { | ||
460 | log::error!("failed to compute diagnostics: {:?}", e); | ||
461 | } | ||
462 | } | ||
463 | Ok(params) => { | ||
464 | let not = RawNotification::new::<req::PublishDiagnostics>(¶ms); | ||
465 | sender.send(Task::Notify(not)); | ||
466 | } | ||
467 | } | ||
468 | if publish_decorations { | ||
469 | match handlers::publish_decorations(&world, file_id) { | ||
470 | Err(e) => { | ||
471 | if !is_canceled(&e) { | ||
472 | log::error!("failed to compute decorations: {:?}", e); | ||
473 | } | ||
474 | } | ||
475 | Ok(params) => { | ||
476 | let not = RawNotification::new::<req::PublishDecorations>(¶ms); | ||
477 | sender.send(Task::Notify(not)) | ||
478 | } | ||
479 | } | ||
480 | } | ||
481 | } | ||
482 | }); | ||
483 | } | ||
484 | |||
485 | fn feedback(intrnal_mode: bool, msg: &str, sender: &Sender<RawMessage>) { | ||
486 | if !intrnal_mode { | ||
487 | return; | ||
488 | } | ||
489 | let not = RawNotification::new::<req::InternalFeedback>(&msg.to_string()); | ||
490 | sender.send(RawMessage::Notification(not)); | ||
491 | } | ||
492 | |||
493 | fn is_canceled(e: &failure::Error) -> bool { | ||
494 | e.downcast_ref::<Canceled>().is_some() | ||
495 | } | ||