diff options
author | Aleksey Kladov <[email protected]> | 2018-09-16 10:54:24 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-09-16 11:07:39 +0100 |
commit | b5021411a84822cb3f1e3aeffad9550dd15bdeb6 (patch) | |
tree | 9dca564f8e51b298dced01c4ce669c756dce3142 /crates/ra_lsp_server/src/main_loop | |
parent | ba0bfeee12e19da40b5eabc8d0408639af10e96f (diff) |
rename all things
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop')
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 436 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/mod.rs | 419 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/subscriptions.rs | 21 |
3 files changed, 876 insertions, 0 deletions
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs new file mode 100644 index 000000000..568f5344c --- /dev/null +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -0,0 +1,436 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use languageserver_types::{ | ||
4 | Diagnostic, DiagnosticSeverity, DocumentSymbol, | ||
5 | Command, TextDocumentIdentifier, | ||
6 | SymbolInformation, Position, Location, TextEdit, | ||
7 | CompletionItem, InsertTextFormat, CompletionItemKind, | ||
8 | }; | ||
9 | use serde_json::to_value; | ||
10 | use ra_analysis::{Query, FileId, RunnableKind, JobToken}; | ||
11 | use ra_syntax::{ | ||
12 | text_utils::contains_offset_nonstrict, | ||
13 | }; | ||
14 | |||
15 | use ::{ | ||
16 | req::{self, Decoration}, Result, | ||
17 | conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location}, | ||
18 | server_world::ServerWorld, | ||
19 | project_model::TargetKind, | ||
20 | }; | ||
21 | |||
22 | pub fn handle_syntax_tree( | ||
23 | world: ServerWorld, | ||
24 | params: req::SyntaxTreeParams, | ||
25 | _token: JobToken, | ||
26 | ) -> Result<String> { | ||
27 | let id = params.text_document.try_conv_with(&world)?; | ||
28 | let res = world.analysis().syntax_tree(id); | ||
29 | Ok(res) | ||
30 | } | ||
31 | |||
32 | pub fn handle_extend_selection( | ||
33 | world: ServerWorld, | ||
34 | params: req::ExtendSelectionParams, | ||
35 | _token: JobToken, | ||
36 | ) -> Result<req::ExtendSelectionResult> { | ||
37 | let file_id = params.text_document.try_conv_with(&world)?; | ||
38 | let file = world.analysis().file_syntax(file_id); | ||
39 | let line_index = world.analysis().file_line_index(file_id); | ||
40 | let selections = params.selections.into_iter() | ||
41 | .map_conv_with(&line_index) | ||
42 | .map(|r| world.analysis().extend_selection(&file, r)) | ||
43 | .map_conv_with(&line_index) | ||
44 | .collect(); | ||
45 | Ok(req::ExtendSelectionResult { selections }) | ||
46 | } | ||
47 | |||
48 | pub fn handle_find_matching_brace( | ||
49 | world: ServerWorld, | ||
50 | params: req::FindMatchingBraceParams, | ||
51 | _token: JobToken, | ||
52 | ) -> Result<Vec<Position>> { | ||
53 | let file_id = params.text_document.try_conv_with(&world)?; | ||
54 | let file = world.analysis().file_syntax(file_id); | ||
55 | let line_index = world.analysis().file_line_index(file_id); | ||
56 | let res = params.offsets | ||
57 | .into_iter() | ||
58 | .map_conv_with(&line_index) | ||
59 | .map(|offset| { | ||
60 | world.analysis().matching_brace(&file, offset).unwrap_or(offset) | ||
61 | }) | ||
62 | .map_conv_with(&line_index) | ||
63 | .collect(); | ||
64 | Ok(res) | ||
65 | } | ||
66 | |||
67 | pub fn handle_join_lines( | ||
68 | world: ServerWorld, | ||
69 | params: req::JoinLinesParams, | ||
70 | _token: JobToken, | ||
71 | ) -> Result<req::SourceChange> { | ||
72 | let file_id = params.text_document.try_conv_with(&world)?; | ||
73 | let line_index = world.analysis().file_line_index(file_id); | ||
74 | let range = params.range.conv_with(&line_index); | ||
75 | world.analysis().join_lines(file_id, range) | ||
76 | .try_conv_with(&world) | ||
77 | } | ||
78 | |||
79 | pub fn handle_on_type_formatting( | ||
80 | world: ServerWorld, | ||
81 | params: req::DocumentOnTypeFormattingParams, | ||
82 | _token: JobToken, | ||
83 | ) -> Result<Option<Vec<TextEdit>>> { | ||
84 | if params.ch != "=" { | ||
85 | return Ok(None); | ||
86 | } | ||
87 | |||
88 | let file_id = params.text_document.try_conv_with(&world)?; | ||
89 | let line_index = world.analysis().file_line_index(file_id); | ||
90 | let offset = params.position.conv_with(&line_index); | ||
91 | let edits = match world.analysis().on_eq_typed(file_id, offset) { | ||
92 | None => return Ok(None), | ||
93 | Some(mut action) => action.source_file_edits.pop().unwrap().edits, | ||
94 | }; | ||
95 | let edits = edits.into_iter().map_conv_with(&line_index).collect(); | ||
96 | Ok(Some(edits)) | ||
97 | } | ||
98 | |||
99 | pub fn handle_document_symbol( | ||
100 | world: ServerWorld, | ||
101 | params: req::DocumentSymbolParams, | ||
102 | _token: JobToken, | ||
103 | ) -> Result<Option<req::DocumentSymbolResponse>> { | ||
104 | let file_id = params.text_document.try_conv_with(&world)?; | ||
105 | let line_index = world.analysis().file_line_index(file_id); | ||
106 | |||
107 | let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); | ||
108 | |||
109 | for symbol in world.analysis().file_structure(file_id) { | ||
110 | let doc_symbol = DocumentSymbol { | ||
111 | name: symbol.label, | ||
112 | detail: Some("".to_string()), | ||
113 | kind: symbol.kind.conv(), | ||
114 | deprecated: None, | ||
115 | range: symbol.node_range.conv_with(&line_index), | ||
116 | selection_range: symbol.navigation_range.conv_with(&line_index), | ||
117 | children: None, | ||
118 | }; | ||
119 | parents.push((doc_symbol, symbol.parent)); | ||
120 | } | ||
121 | let mut res = Vec::new(); | ||
122 | while let Some((node, parent)) = parents.pop() { | ||
123 | match parent { | ||
124 | None => res.push(node), | ||
125 | Some(i) => { | ||
126 | let children = &mut parents[i].0.children; | ||
127 | if children.is_none() { | ||
128 | *children = Some(Vec::new()); | ||
129 | } | ||
130 | children.as_mut().unwrap().push(node); | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | Ok(Some(req::DocumentSymbolResponse::Nested(res))) | ||
136 | } | ||
137 | |||
138 | pub fn handle_workspace_symbol( | ||
139 | world: ServerWorld, | ||
140 | params: req::WorkspaceSymbolParams, | ||
141 | token: JobToken, | ||
142 | ) -> Result<Option<Vec<SymbolInformation>>> { | ||
143 | let all_symbols = params.query.contains("#"); | ||
144 | let libs = params.query.contains("*"); | ||
145 | let query = { | ||
146 | let query: String = params.query.chars() | ||
147 | .filter(|&c| c != '#' && c != '*') | ||
148 | .collect(); | ||
149 | let mut q = Query::new(query); | ||
150 | if !all_symbols { | ||
151 | q.only_types(); | ||
152 | } | ||
153 | if libs { | ||
154 | q.libs(); | ||
155 | } | ||
156 | q.limit(128); | ||
157 | q | ||
158 | }; | ||
159 | let mut res = exec_query(&world, query, &token)?; | ||
160 | if res.is_empty() && !all_symbols { | ||
161 | let mut query = Query::new(params.query); | ||
162 | query.limit(128); | ||
163 | res = exec_query(&world, query, &token)?; | ||
164 | } | ||
165 | |||
166 | return Ok(Some(res)); | ||
167 | |||
168 | fn exec_query(world: &ServerWorld, query: Query, token: &JobToken) -> Result<Vec<SymbolInformation>> { | ||
169 | let mut res = Vec::new(); | ||
170 | for (file_id, symbol) in world.analysis().symbol_search(query, token) { | ||
171 | let line_index = world.analysis().file_line_index(file_id); | ||
172 | let info = SymbolInformation { | ||
173 | name: symbol.name.to_string(), | ||
174 | kind: symbol.kind.conv(), | ||
175 | location: to_location( | ||
176 | file_id, symbol.node_range, | ||
177 | world, &line_index | ||
178 | )?, | ||
179 | container_name: None, | ||
180 | }; | ||
181 | res.push(info); | ||
182 | }; | ||
183 | Ok(res) | ||
184 | } | ||
185 | } | ||
186 | |||
187 | pub fn handle_goto_definition( | ||
188 | world: ServerWorld, | ||
189 | params: req::TextDocumentPositionParams, | ||
190 | token: JobToken, | ||
191 | ) -> Result<Option<req::GotoDefinitionResponse>> { | ||
192 | let file_id = params.text_document.try_conv_with(&world)?; | ||
193 | let line_index = world.analysis().file_line_index(file_id); | ||
194 | let offset = params.position.conv_with(&line_index); | ||
195 | let mut res = Vec::new(); | ||
196 | for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset, &token) { | ||
197 | let line_index = world.analysis().file_line_index(file_id); | ||
198 | let location = to_location( | ||
199 | file_id, symbol.node_range, | ||
200 | &world, &line_index, | ||
201 | )?; | ||
202 | res.push(location) | ||
203 | } | ||
204 | Ok(Some(req::GotoDefinitionResponse::Array(res))) | ||
205 | } | ||
206 | |||
207 | pub fn handle_parent_module( | ||
208 | world: ServerWorld, | ||
209 | params: TextDocumentIdentifier, | ||
210 | _token: JobToken, | ||
211 | ) -> Result<Vec<Location>> { | ||
212 | let file_id = params.try_conv_with(&world)?; | ||
213 | let mut res = Vec::new(); | ||
214 | for (file_id, symbol) in world.analysis().parent_module(file_id) { | ||
215 | let line_index = world.analysis().file_line_index(file_id); | ||
216 | let location = to_location( | ||
217 | file_id, symbol.node_range, | ||
218 | &world, &line_index | ||
219 | )?; | ||
220 | res.push(location); | ||
221 | } | ||
222 | Ok(res) | ||
223 | } | ||
224 | |||
225 | pub fn handle_runnables( | ||
226 | world: ServerWorld, | ||
227 | params: req::RunnablesParams, | ||
228 | _token: JobToken, | ||
229 | ) -> Result<Vec<req::Runnable>> { | ||
230 | let file_id = params.text_document.try_conv_with(&world)?; | ||
231 | let line_index = world.analysis().file_line_index(file_id); | ||
232 | let offset = params.position.map(|it| it.conv_with(&line_index)); | ||
233 | let mut res = Vec::new(); | ||
234 | for runnable in world.analysis().runnables(file_id) { | ||
235 | if let Some(offset) = offset { | ||
236 | if !contains_offset_nonstrict(runnable.range, offset) { | ||
237 | continue; | ||
238 | } | ||
239 | } | ||
240 | |||
241 | let args = runnable_args(&world, file_id, &runnable.kind); | ||
242 | |||
243 | let r = req::Runnable { | ||
244 | range: runnable.range.conv_with(&line_index), | ||
245 | label: match &runnable.kind { | ||
246 | RunnableKind::Test { name } => | ||
247 | format!("test {}", name), | ||
248 | RunnableKind::Bin => | ||
249 | "run binary".to_string(), | ||
250 | }, | ||
251 | bin: "cargo".to_string(), | ||
252 | args, | ||
253 | env: { | ||
254 | let mut m = HashMap::new(); | ||
255 | m.insert( | ||
256 | "RUST_BACKTRACE".to_string(), | ||
257 | "short".to_string(), | ||
258 | ); | ||
259 | m | ||
260 | } | ||
261 | }; | ||
262 | res.push(r); | ||
263 | } | ||
264 | return Ok(res); | ||
265 | |||
266 | fn runnable_args(world: &ServerWorld, file_id: FileId, kind: &RunnableKind) -> Vec<String> { | ||
267 | let spec = if let Some(&crate_id) = world.analysis().crate_for(file_id).first() { | ||
268 | let file_id = world.analysis().crate_root(crate_id); | ||
269 | let path = world.path_map.get_path(file_id); | ||
270 | world.workspaces.iter() | ||
271 | .filter_map(|ws| { | ||
272 | let tgt = ws.target_by_root(path)?; | ||
273 | Some((tgt.package(ws).name(ws).clone(), tgt.name(ws).clone(), tgt.kind(ws))) | ||
274 | }) | ||
275 | .next() | ||
276 | } else { | ||
277 | None | ||
278 | }; | ||
279 | let mut res = Vec::new(); | ||
280 | match kind { | ||
281 | RunnableKind::Test { name } => { | ||
282 | res.push("test".to_string()); | ||
283 | if let Some((pkg_name, tgt_name, tgt_kind)) = spec { | ||
284 | spec_args(pkg_name, tgt_name, tgt_kind, &mut res); | ||
285 | } | ||
286 | res.push("--".to_string()); | ||
287 | res.push(name.to_string()); | ||
288 | res.push("--nocapture".to_string()); | ||
289 | } | ||
290 | RunnableKind::Bin => { | ||
291 | res.push("run".to_string()); | ||
292 | if let Some((pkg_name, tgt_name, tgt_kind)) = spec { | ||
293 | spec_args(pkg_name, tgt_name, tgt_kind, &mut res); | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | res | ||
298 | } | ||
299 | |||
300 | fn spec_args(pkg_name: &str, tgt_name: &str, tgt_kind: TargetKind, buf: &mut Vec<String>) { | ||
301 | buf.push("--package".to_string()); | ||
302 | buf.push(pkg_name.to_string()); | ||
303 | match tgt_kind { | ||
304 | TargetKind::Bin => { | ||
305 | buf.push("--bin".to_string()); | ||
306 | buf.push(tgt_name.to_string()); | ||
307 | } | ||
308 | TargetKind::Test => { | ||
309 | buf.push("--test".to_string()); | ||
310 | buf.push(tgt_name.to_string()); | ||
311 | } | ||
312 | TargetKind::Bench => { | ||
313 | buf.push("--bench".to_string()); | ||
314 | buf.push(tgt_name.to_string()); | ||
315 | } | ||
316 | TargetKind::Example => { | ||
317 | buf.push("--example".to_string()); | ||
318 | buf.push(tgt_name.to_string()); | ||
319 | } | ||
320 | TargetKind::Lib => { | ||
321 | buf.push("--lib".to_string()); | ||
322 | } | ||
323 | TargetKind::Other => (), | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | |||
328 | pub fn handle_decorations( | ||
329 | world: ServerWorld, | ||
330 | params: TextDocumentIdentifier, | ||
331 | _token: JobToken, | ||
332 | ) -> Result<Vec<Decoration>> { | ||
333 | let file_id = params.try_conv_with(&world)?; | ||
334 | Ok(highlight(&world, file_id)) | ||
335 | } | ||
336 | |||
337 | pub fn handle_completion( | ||
338 | world: ServerWorld, | ||
339 | params: req::CompletionParams, | ||
340 | _token: JobToken, | ||
341 | ) -> Result<Option<req::CompletionResponse>> { | ||
342 | let file_id = params.text_document.try_conv_with(&world)?; | ||
343 | let line_index = world.analysis().file_line_index(file_id); | ||
344 | let offset = params.position.conv_with(&line_index); | ||
345 | let items = match world.analysis().completions(file_id, offset) { | ||
346 | None => return Ok(None), | ||
347 | Some(items) => items, | ||
348 | }; | ||
349 | let items = items.into_iter() | ||
350 | .map(|item| { | ||
351 | let mut res = CompletionItem { | ||
352 | label: item.label, | ||
353 | filter_text: item.lookup, | ||
354 | .. Default::default() | ||
355 | }; | ||
356 | if let Some(snip) = item.snippet { | ||
357 | res.insert_text = Some(snip); | ||
358 | res.insert_text_format = Some(InsertTextFormat::Snippet); | ||
359 | res.kind = Some(CompletionItemKind::Keyword); | ||
360 | }; | ||
361 | res | ||
362 | }) | ||
363 | .collect(); | ||
364 | |||
365 | Ok(Some(req::CompletionResponse::Array(items))) | ||
366 | } | ||
367 | |||
368 | pub fn handle_code_action( | ||
369 | world: ServerWorld, | ||
370 | params: req::CodeActionParams, | ||
371 | _token: JobToken, | ||
372 | ) -> Result<Option<Vec<Command>>> { | ||
373 | let file_id = params.text_document.try_conv_with(&world)?; | ||
374 | let line_index = world.analysis().file_line_index(file_id); | ||
375 | let range = params.range.conv_with(&line_index); | ||
376 | |||
377 | let assists = world.analysis().assists(file_id, range).into_iter(); | ||
378 | let fixes = world.analysis().diagnostics(file_id).into_iter() | ||
379 | .filter_map(|d| Some((d.range, d.fix?))) | ||
380 | .filter(|(range, _fix)| contains_offset_nonstrict(*range, range.start())) | ||
381 | .map(|(_range, fix)| fix); | ||
382 | |||
383 | let mut res = Vec::new(); | ||
384 | for source_edit in assists.chain(fixes) { | ||
385 | let title = source_edit.label.clone(); | ||
386 | let edit = source_edit.try_conv_with(&world)?; | ||
387 | let cmd = Command { | ||
388 | title, | ||
389 | command: "libsyntax-rust.applySourceChange".to_string(), | ||
390 | arguments: Some(vec![to_value(edit).unwrap()]), | ||
391 | }; | ||
392 | res.push(cmd); | ||
393 | } | ||
394 | |||
395 | Ok(Some(res)) | ||
396 | } | ||
397 | |||
398 | pub fn publish_diagnostics( | ||
399 | world: ServerWorld, | ||
400 | file_id: FileId, | ||
401 | ) -> Result<req::PublishDiagnosticsParams> { | ||
402 | let uri = world.file_id_to_uri(file_id)?; | ||
403 | let line_index = world.analysis().file_line_index(file_id); | ||
404 | let diagnostics = world.analysis().diagnostics(file_id) | ||
405 | .into_iter() | ||
406 | .map(|d| Diagnostic { | ||
407 | range: d.range.conv_with(&line_index), | ||
408 | severity: Some(DiagnosticSeverity::Error), | ||
409 | code: None, | ||
410 | source: Some("libsyntax2".to_string()), | ||
411 | message: d.message, | ||
412 | related_information: None, | ||
413 | }).collect(); | ||
414 | Ok(req::PublishDiagnosticsParams { uri, diagnostics }) | ||
415 | } | ||
416 | |||
417 | pub fn publish_decorations( | ||
418 | world: ServerWorld, | ||
419 | file_id: FileId, | ||
420 | ) -> Result<req::PublishDecorationsParams> { | ||
421 | let uri = world.file_id_to_uri(file_id)?; | ||
422 | Ok(req::PublishDecorationsParams { | ||
423 | uri, | ||
424 | decorations: highlight(&world, file_id), | ||
425 | }) | ||
426 | } | ||
427 | |||
428 | fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> { | ||
429 | let line_index = world.analysis().file_line_index(file_id); | ||
430 | world.analysis().highlight(file_id) | ||
431 | .into_iter() | ||
432 | .map(|h| Decoration { | ||
433 | range: h.range.conv_with(&line_index), | ||
434 | tag: h.tag, | ||
435 | }).collect() | ||
436 | } | ||
diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs new file mode 100644 index 000000000..2b2279e97 --- /dev/null +++ b/crates/ra_lsp_server/src/main_loop/mod.rs | |||
@@ -0,0 +1,419 @@ | |||
1 | mod handlers; | ||
2 | mod subscriptions; | ||
3 | |||
4 | use std::{ | ||
5 | path::PathBuf, | ||
6 | collections::{HashMap}, | ||
7 | }; | ||
8 | |||
9 | use serde::{Serialize, de::DeserializeOwned}; | ||
10 | use crossbeam_channel::{unbounded, Sender, Receiver}; | ||
11 | use rayon::{self, ThreadPool}; | ||
12 | use languageserver_types::{NumberOrString}; | ||
13 | use ra_analysis::{FileId, JobHandle, JobToken, LibraryData}; | ||
14 | use gen_lsp_server::{ | ||
15 | RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode, | ||
16 | handle_shutdown, | ||
17 | }; | ||
18 | |||
19 | use { | ||
20 | req, | ||
21 | Result, | ||
22 | vfs::{self, FileEvent}, | ||
23 | server_world::{ServerWorldState, ServerWorld}, | ||
24 | main_loop::subscriptions::{Subscriptions}, | ||
25 | project_model::{CargoWorkspace, workspace_loader}, | ||
26 | thread_watcher::Worker, | ||
27 | }; | ||
28 | |||
29 | #[derive(Debug)] | ||
30 | enum Task { | ||
31 | Respond(RawResponse), | ||
32 | Notify(RawNotification), | ||
33 | } | ||
34 | |||
35 | pub fn main_loop( | ||
36 | internal_mode: bool, | ||
37 | root: PathBuf, | ||
38 | msg_receriver: &mut Receiver<RawMessage>, | ||
39 | msg_sender: &mut Sender<RawMessage>, | ||
40 | ) -> Result<()> { | ||
41 | let pool = rayon::ThreadPoolBuilder::new() | ||
42 | .num_threads(4) | ||
43 | .panic_handler(|_| error!("thread panicked :(")) | ||
44 | .build() | ||
45 | .unwrap(); | ||
46 | let (task_sender, task_receiver) = unbounded::<Task>(); | ||
47 | let (fs_worker, fs_watcher) = vfs::roots_loader(); | ||
48 | let (ws_worker, ws_watcher) = workspace_loader(); | ||
49 | |||
50 | info!("server initialized, serving requests"); | ||
51 | let mut state = ServerWorldState::new(); | ||
52 | |||
53 | let mut pending_requests = HashMap::new(); | ||
54 | let mut subs = Subscriptions::new(); | ||
55 | let main_res = main_loop_inner( | ||
56 | internal_mode, | ||
57 | root, | ||
58 | &pool, | ||
59 | msg_sender, | ||
60 | msg_receriver, | ||
61 | task_sender, | ||
62 | task_receiver.clone(), | ||
63 | fs_worker, | ||
64 | ws_worker, | ||
65 | &mut state, | ||
66 | &mut pending_requests, | ||
67 | &mut subs, | ||
68 | ); | ||
69 | |||
70 | info!("waiting for tasks to finish..."); | ||
71 | task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests)); | ||
72 | info!("...tasks have finished"); | ||
73 | info!("joining threadpool..."); | ||
74 | drop(pool); | ||
75 | info!("...threadpool has finished"); | ||
76 | |||
77 | let fs_res = fs_watcher.stop(); | ||
78 | let ws_res = ws_watcher.stop(); | ||
79 | |||
80 | main_res?; | ||
81 | fs_res?; | ||
82 | ws_res?; | ||
83 | |||
84 | Ok(()) | ||
85 | } | ||
86 | |||
87 | fn main_loop_inner( | ||
88 | internal_mode: bool, | ||
89 | ws_root: PathBuf, | ||
90 | pool: &ThreadPool, | ||
91 | msg_sender: &mut Sender<RawMessage>, | ||
92 | msg_receiver: &mut Receiver<RawMessage>, | ||
93 | task_sender: Sender<Task>, | ||
94 | task_receiver: Receiver<Task>, | ||
95 | fs_worker: Worker<PathBuf, (PathBuf, Vec<FileEvent>)>, | ||
96 | ws_worker: Worker<PathBuf, Result<CargoWorkspace>>, | ||
97 | state: &mut ServerWorldState, | ||
98 | pending_requests: &mut HashMap<u64, JobHandle>, | ||
99 | subs: &mut Subscriptions, | ||
100 | ) -> Result<()> { | ||
101 | let (libdata_sender, libdata_receiver) = unbounded(); | ||
102 | ws_worker.send(ws_root.clone()); | ||
103 | fs_worker.send(ws_root.clone()); | ||
104 | loop { | ||
105 | #[derive(Debug)] | ||
106 | enum Event { | ||
107 | Msg(RawMessage), | ||
108 | Task(Task), | ||
109 | Fs(PathBuf, Vec<FileEvent>), | ||
110 | Ws(Result<CargoWorkspace>), | ||
111 | Lib(LibraryData), | ||
112 | } | ||
113 | trace!("selecting"); | ||
114 | let event = select! { | ||
115 | recv(msg_receiver, msg) => match msg { | ||
116 | Some(msg) => Event::Msg(msg), | ||
117 | None => bail!("client exited without shutdown"), | ||
118 | }, | ||
119 | recv(task_receiver, task) => Event::Task(task.unwrap()), | ||
120 | recv(fs_worker.out, events) => match events { | ||
121 | None => bail!("roots watcher died"), | ||
122 | Some((pb, events)) => Event::Fs(pb, events), | ||
123 | } | ||
124 | recv(ws_worker.out, ws) => match ws { | ||
125 | None => bail!("workspace watcher died"), | ||
126 | Some(ws) => Event::Ws(ws), | ||
127 | } | ||
128 | recv(libdata_receiver, data) => Event::Lib(data.unwrap()) | ||
129 | }; | ||
130 | let mut state_changed = false; | ||
131 | match event { | ||
132 | Event::Task(task) => on_task(task, msg_sender, pending_requests), | ||
133 | Event::Fs(root, events) => { | ||
134 | info!("fs change, {}, {} events", root.display(), events.len()); | ||
135 | if root == ws_root { | ||
136 | state.apply_fs_changes(events); | ||
137 | } else { | ||
138 | let (files, resolver) = state.events_to_files(events); | ||
139 | let sender = libdata_sender.clone(); | ||
140 | pool.spawn(move || { | ||
141 | let start = ::std::time::Instant::now(); | ||
142 | info!("indexing {} ... ", root.display()); | ||
143 | let data = LibraryData::prepare(files, resolver); | ||
144 | info!("indexed {:?} {}", start.elapsed(), root.display()); | ||
145 | sender.send(data); | ||
146 | }); | ||
147 | } | ||
148 | state_changed = true; | ||
149 | } | ||
150 | Event::Ws(ws) => { | ||
151 | match ws { | ||
152 | Ok(ws) => { | ||
153 | let workspaces = vec![ws]; | ||
154 | feedback(internal_mode, "workspace loaded", msg_sender); | ||
155 | for ws in workspaces.iter() { | ||
156 | for pkg in ws.packages().filter(|pkg| !pkg.is_member(ws)) { | ||
157 | debug!("sending root, {}", pkg.root(ws).to_path_buf().display()); | ||
158 | fs_worker.send(pkg.root(ws).to_path_buf()); | ||
159 | } | ||
160 | } | ||
161 | state.set_workspaces(workspaces); | ||
162 | state_changed = true; | ||
163 | } | ||
164 | Err(e) => warn!("loading workspace failed: {}", e), | ||
165 | } | ||
166 | } | ||
167 | Event::Lib(lib) => { | ||
168 | feedback(internal_mode, "library loaded", msg_sender); | ||
169 | state.add_lib(lib); | ||
170 | } | ||
171 | Event::Msg(msg) => { | ||
172 | match msg { | ||
173 | RawMessage::Request(req) => { | ||
174 | let req = match handle_shutdown(req, msg_sender) { | ||
175 | Some(req) => req, | ||
176 | None => return Ok(()), | ||
177 | }; | ||
178 | match on_request(state, pending_requests, pool, &task_sender, req)? { | ||
179 | None => (), | ||
180 | Some(req) => { | ||
181 | error!("unknown request: {:?}", req); | ||
182 | let resp = RawResponse::err( | ||
183 | req.id, | ||
184 | ErrorCode::MethodNotFound as i32, | ||
185 | "unknown request".to_string(), | ||
186 | ); | ||
187 | msg_sender.send(RawMessage::Response(resp)) | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | RawMessage::Notification(not) => { | ||
192 | on_notification(msg_sender, state, pending_requests, subs, not)?; | ||
193 | state_changed = true; | ||
194 | } | ||
195 | RawMessage::Response(resp) => { | ||
196 | error!("unexpected response: {:?}", resp) | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | }; | ||
201 | |||
202 | if state_changed { | ||
203 | update_file_notifications_on_threadpool( | ||
204 | pool, | ||
205 | state.snapshot(), | ||
206 | task_sender.clone(), | ||
207 | subs.subscriptions(), | ||
208 | ) | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | |||
213 | fn on_task( | ||
214 | task: Task, | ||
215 | msg_sender: &mut Sender<RawMessage>, | ||
216 | pending_requests: &mut HashMap<u64, JobHandle>, | ||
217 | ) { | ||
218 | match task { | ||
219 | Task::Respond(response) => { | ||
220 | if let Some(handle) = pending_requests.remove(&response.id) { | ||
221 | assert!(handle.has_completed()); | ||
222 | } | ||
223 | msg_sender.send(RawMessage::Response(response)) | ||
224 | } | ||
225 | Task::Notify(n) => | ||
226 | msg_sender.send(RawMessage::Notification(n)), | ||
227 | } | ||
228 | } | ||
229 | |||
230 | fn on_request( | ||
231 | world: &mut ServerWorldState, | ||
232 | pending_requests: &mut HashMap<u64, JobHandle>, | ||
233 | pool: &ThreadPool, | ||
234 | sender: &Sender<Task>, | ||
235 | req: RawRequest, | ||
236 | ) -> Result<Option<RawRequest>> { | ||
237 | let mut pool_dispatcher = PoolDispatcher { | ||
238 | req: Some(req), | ||
239 | res: None, | ||
240 | pool, world, sender | ||
241 | }; | ||
242 | let req = pool_dispatcher | ||
243 | .on::<req::SyntaxTree>(handlers::handle_syntax_tree)? | ||
244 | .on::<req::ExtendSelection>(handlers::handle_extend_selection)? | ||
245 | .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)? | ||
246 | .on::<req::JoinLines>(handlers::handle_join_lines)? | ||
247 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? | ||
248 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? | ||
249 | .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? | ||
250 | .on::<req::GotoDefinition>(handlers::handle_goto_definition)? | ||
251 | .on::<req::ParentModule>(handlers::handle_parent_module)? | ||
252 | .on::<req::Runnables>(handlers::handle_runnables)? | ||
253 | .on::<req::DecorationsRequest>(handlers::handle_decorations)? | ||
254 | .on::<req::Completion>(handlers::handle_completion)? | ||
255 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? | ||
256 | .finish(); | ||
257 | match req { | ||
258 | Ok((id, handle)) => { | ||
259 | let inserted = pending_requests.insert(id, handle).is_none(); | ||
260 | assert!(inserted, "duplicate request: {}", id); | ||
261 | Ok(None) | ||
262 | }, | ||
263 | Err(req) => Ok(Some(req)), | ||
264 | } | ||
265 | } | ||
266 | |||
267 | fn on_notification( | ||
268 | msg_sender: &mut Sender<RawMessage>, | ||
269 | state: &mut ServerWorldState, | ||
270 | pending_requests: &mut HashMap<u64, JobHandle>, | ||
271 | subs: &mut Subscriptions, | ||
272 | not: RawNotification, | ||
273 | ) -> Result<()> { | ||
274 | let not = match not.cast::<req::Cancel>() { | ||
275 | Ok(params) => { | ||
276 | let id = match params.id { | ||
277 | NumberOrString::Number(id) => id, | ||
278 | NumberOrString::String(id) => { | ||
279 | panic!("string id's not supported: {:?}", id); | ||
280 | } | ||
281 | }; | ||
282 | if let Some(handle) = pending_requests.remove(&id) { | ||
283 | handle.cancel(); | ||
284 | } | ||
285 | return Ok(()) | ||
286 | } | ||
287 | Err(not) => not, | ||
288 | }; | ||
289 | let not = match not.cast::<req::DidOpenTextDocument>() { | ||
290 | Ok(params) => { | ||
291 | let uri = params.text_document.uri; | ||
292 | let path = uri.to_file_path() | ||
293 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
294 | let file_id = state.add_mem_file(path, params.text_document.text); | ||
295 | subs.add_sub(file_id); | ||
296 | return Ok(()) | ||
297 | } | ||
298 | Err(not) => not, | ||
299 | }; | ||
300 | let not = match not.cast::<req::DidChangeTextDocument>() { | ||
301 | Ok(mut params) => { | ||
302 | let uri = params.text_document.uri; | ||
303 | let path = uri.to_file_path() | ||
304 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
305 | let text = params.content_changes.pop() | ||
306 | .ok_or_else(|| format_err!("empty changes"))? | ||
307 | .text; | ||
308 | state.change_mem_file(path.as_path(), text)?; | ||
309 | return Ok(()) | ||
310 | } | ||
311 | Err(not) => not, | ||
312 | }; | ||
313 | let not = match not.cast::<req::DidCloseTextDocument>() { | ||
314 | Ok(params) => { | ||
315 | let uri = params.text_document.uri; | ||
316 | let path = uri.to_file_path() | ||
317 | .map_err(|()| format_err!("invalid uri: {}", uri))?; | ||
318 | let file_id = state.remove_mem_file(path.as_path())?; | ||
319 | subs.remove_sub(file_id); | ||
320 | let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() }; | ||
321 | let not = RawNotification::new::<req::PublishDiagnostics>(¶ms); | ||
322 | msg_sender.send(RawMessage::Notification(not)); | ||
323 | return Ok(()) | ||
324 | } | ||
325 | Err(not) => not, | ||
326 | }; | ||
327 | error!("unhandled notification: {:?}", not); | ||
328 | Ok(()) | ||
329 | } | ||
330 | |||
331 | struct PoolDispatcher<'a> { | ||
332 | req: Option<RawRequest>, | ||
333 | res: Option<(u64, JobHandle)>, | ||
334 | pool: &'a ThreadPool, | ||
335 | world: &'a ServerWorldState, | ||
336 | sender: &'a Sender<Task>, | ||
337 | } | ||
338 | |||
339 | impl<'a> PoolDispatcher<'a> { | ||
340 | fn on<'b, R>( | ||
341 | &'b mut self, | ||
342 | f: fn(ServerWorld, R::Params, JobToken) -> Result<R::Result> | ||
343 | ) -> Result<&'b mut Self> | ||
344 | where R: req::Request, | ||
345 | R::Params: DeserializeOwned + Send + 'static, | ||
346 | R::Result: Serialize + 'static, | ||
347 | { | ||
348 | let req = match self.req.take() { | ||
349 | None => return Ok(self), | ||
350 | Some(req) => req, | ||
351 | }; | ||
352 | match req.cast::<R>() { | ||
353 | Ok((id, params)) => { | ||
354 | let (handle, token) = JobHandle::new(); | ||
355 | let world = self.world.snapshot(); | ||
356 | let sender = self.sender.clone(); | ||
357 | self.pool.spawn(move || { | ||
358 | let resp = match f(world, params, token) { | ||
359 | Ok(resp) => RawResponse::ok::<R>(id, &resp), | ||
360 | Err(e) => RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()), | ||
361 | }; | ||
362 | let task = Task::Respond(resp); | ||
363 | sender.send(task); | ||
364 | }); | ||
365 | self.res = Some((id, handle)); | ||
366 | } | ||
367 | Err(req) => { | ||
368 | self.req = Some(req) | ||
369 | } | ||
370 | } | ||
371 | Ok(self) | ||
372 | } | ||
373 | |||
374 | fn finish(&mut self) -> ::std::result::Result<(u64, JobHandle), RawRequest> { | ||
375 | match (self.res.take(), self.req.take()) { | ||
376 | (Some(res), None) => Ok(res), | ||
377 | (None, Some(req)) => Err(req), | ||
378 | _ => unreachable!(), | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | |||
383 | fn update_file_notifications_on_threadpool( | ||
384 | pool: &ThreadPool, | ||
385 | world: ServerWorld, | ||
386 | sender: Sender<Task>, | ||
387 | subscriptions: Vec<FileId>, | ||
388 | ) { | ||
389 | pool.spawn(move || { | ||
390 | for file_id in subscriptions { | ||
391 | match handlers::publish_diagnostics(world.clone(), file_id) { | ||
392 | Err(e) => { | ||
393 | error!("failed to compute diagnostics: {:?}", e) | ||
394 | } | ||
395 | Ok(params) => { | ||
396 | let not = RawNotification::new::<req::PublishDiagnostics>(¶ms); | ||
397 | sender.send(Task::Notify(not)); | ||
398 | } | ||
399 | } | ||
400 | match handlers::publish_decorations(world.clone(), file_id) { | ||
401 | Err(e) => { | ||
402 | error!("failed to compute decorations: {:?}", e) | ||
403 | } | ||
404 | Ok(params) => { | ||
405 | let not = RawNotification::new::<req::PublishDecorations>(¶ms); | ||
406 | sender.send(Task::Notify(not)) | ||
407 | } | ||
408 | } | ||
409 | } | ||
410 | }); | ||
411 | } | ||
412 | |||
413 | fn feedback(intrnal_mode: bool, msg: &str, sender: &Sender<RawMessage>) { | ||
414 | if !intrnal_mode { | ||
415 | return; | ||
416 | } | ||
417 | let not = RawNotification::new::<req::InternalFeedback>(&msg.to_string()); | ||
418 | sender.send(RawMessage::Notification(not)); | ||
419 | } | ||
diff --git a/crates/ra_lsp_server/src/main_loop/subscriptions.rs b/crates/ra_lsp_server/src/main_loop/subscriptions.rs new file mode 100644 index 000000000..27f92cc9a --- /dev/null +++ b/crates/ra_lsp_server/src/main_loop/subscriptions.rs | |||
@@ -0,0 +1,21 @@ | |||
1 | use std::collections::HashSet; | ||
2 | use ra_analysis::FileId; | ||
3 | |||
4 | pub struct Subscriptions { | ||
5 | subs: HashSet<FileId>, | ||
6 | } | ||
7 | |||
8 | impl Subscriptions { | ||
9 | pub fn new() -> Subscriptions { | ||
10 | Subscriptions { subs: HashSet::new() } | ||
11 | } | ||
12 | pub fn add_sub(&mut self, file_id: FileId) { | ||
13 | self.subs.insert(file_id); | ||
14 | } | ||
15 | pub fn remove_sub(&mut self, file_id: FileId) { | ||
16 | self.subs.remove(&file_id); | ||
17 | } | ||
18 | pub fn subscriptions(&self) -> Vec<FileId> { | ||
19 | self.subs.iter().cloned().collect() | ||
20 | } | ||
21 | } | ||