diff options
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop')
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 1070 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/pending_requests.rs | 75 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/subscriptions.rs | 22 |
3 files changed, 0 insertions, 1167 deletions
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs deleted file mode 100644 index bb7bab372..000000000 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ /dev/null | |||
@@ -1,1070 +0,0 @@ | |||
1 | //! This module is responsible for implementing handlers for Language Server | ||
2 | //! Protocol. The majority of requests are fulfilled by calling into the | ||
3 | //! `ra_ide` crate. | ||
4 | |||
5 | use std::{ | ||
6 | collections::hash_map::Entry, | ||
7 | fmt::Write as _, | ||
8 | io::Write as _, | ||
9 | process::{self, Stdio}, | ||
10 | }; | ||
11 | |||
12 | use lsp_server::ErrorCode; | ||
13 | use lsp_types::{ | ||
14 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | ||
15 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | ||
16 | CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem, | ||
17 | Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, | ||
18 | FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, | ||
19 | PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier, | ||
20 | TextEdit, WorkspaceEdit, | ||
21 | }; | ||
22 | use ra_ide::{ | ||
23 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, | ||
24 | SearchScope, | ||
25 | }; | ||
26 | use ra_prof::profile; | ||
27 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; | ||
28 | use rustc_hash::FxHashMap; | ||
29 | use serde::{Deserialize, Serialize}; | ||
30 | use serde_json::to_value; | ||
31 | |||
32 | use crate::{ | ||
33 | cargo_target_spec::CargoTargetSpec, | ||
34 | conv::{ | ||
35 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, | ||
36 | TryConvWithToVec, | ||
37 | }, | ||
38 | diagnostics::DiagnosticTask, | ||
39 | from_json, | ||
40 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, | ||
41 | world::WorldSnapshot, | ||
42 | LspError, Result, | ||
43 | }; | ||
44 | |||
45 | pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { | ||
46 | let _p = profile("handle_analyzer_status"); | ||
47 | let mut buf = world.status(); | ||
48 | writeln!(buf, "\n\nrequests:").unwrap(); | ||
49 | let requests = world.latest_requests.read(); | ||
50 | for (is_last, r) in requests.iter() { | ||
51 | let mark = if is_last { "*" } else { " " }; | ||
52 | writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap(); | ||
53 | } | ||
54 | Ok(buf) | ||
55 | } | ||
56 | |||
57 | pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> { | ||
58 | let _p = profile("handle_syntax_tree"); | ||
59 | let id = params.text_document.try_conv_with(&world)?; | ||
60 | let line_index = world.analysis().file_line_index(id)?; | ||
61 | let text_range = params.range.map(|p| p.conv_with(&line_index)); | ||
62 | let res = world.analysis().syntax_tree(id, text_range)?; | ||
63 | Ok(res) | ||
64 | } | ||
65 | |||
66 | pub fn handle_expand_macro( | ||
67 | world: WorldSnapshot, | ||
68 | params: req::ExpandMacroParams, | ||
69 | ) -> Result<Option<req::ExpandedMacro>> { | ||
70 | let _p = profile("handle_expand_macro"); | ||
71 | let file_id = params.text_document.try_conv_with(&world)?; | ||
72 | let line_index = world.analysis().file_line_index(file_id)?; | ||
73 | let offset = params.position.map(|p| p.conv_with(&line_index)); | ||
74 | |||
75 | match offset { | ||
76 | None => Ok(None), | ||
77 | Some(offset) => { | ||
78 | let res = world.analysis().expand_macro(FilePosition { file_id, offset })?; | ||
79 | Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion })) | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | pub fn handle_selection_range( | ||
85 | world: WorldSnapshot, | ||
86 | params: req::SelectionRangeParams, | ||
87 | ) -> Result<Vec<req::SelectionRange>> { | ||
88 | let _p = profile("handle_selection_range"); | ||
89 | let file_id = params.text_document.try_conv_with(&world)?; | ||
90 | let line_index = world.analysis().file_line_index(file_id)?; | ||
91 | params | ||
92 | .positions | ||
93 | .into_iter() | ||
94 | .map_conv_with(&line_index) | ||
95 | .map(|position| { | ||
96 | let mut ranges = Vec::new(); | ||
97 | { | ||
98 | let mut range = TextRange::from_to(position, position); | ||
99 | loop { | ||
100 | ranges.push(range); | ||
101 | let frange = FileRange { file_id, range }; | ||
102 | let next = world.analysis().extend_selection(frange)?; | ||
103 | if next == range { | ||
104 | break; | ||
105 | } else { | ||
106 | range = next | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | let mut range = req::SelectionRange { | ||
111 | range: ranges.last().unwrap().conv_with(&line_index), | ||
112 | parent: None, | ||
113 | }; | ||
114 | for r in ranges.iter().rev().skip(1) { | ||
115 | range = req::SelectionRange { | ||
116 | range: r.conv_with(&line_index), | ||
117 | parent: Some(Box::new(range)), | ||
118 | } | ||
119 | } | ||
120 | Ok(range) | ||
121 | }) | ||
122 | .collect() | ||
123 | } | ||
124 | |||
125 | pub fn handle_find_matching_brace( | ||
126 | world: WorldSnapshot, | ||
127 | params: req::FindMatchingBraceParams, | ||
128 | ) -> Result<Vec<Position>> { | ||
129 | let _p = profile("handle_find_matching_brace"); | ||
130 | let file_id = params.text_document.try_conv_with(&world)?; | ||
131 | let line_index = world.analysis().file_line_index(file_id)?; | ||
132 | let res = params | ||
133 | .offsets | ||
134 | .into_iter() | ||
135 | .map_conv_with(&line_index) | ||
136 | .map(|offset| { | ||
137 | if let Ok(Some(matching_brace_offset)) = | ||
138 | world.analysis().matching_brace(FilePosition { file_id, offset }) | ||
139 | { | ||
140 | matching_brace_offset | ||
141 | } else { | ||
142 | offset | ||
143 | } | ||
144 | }) | ||
145 | .map_conv_with(&line_index) | ||
146 | .collect(); | ||
147 | Ok(res) | ||
148 | } | ||
149 | |||
150 | pub fn handle_join_lines( | ||
151 | world: WorldSnapshot, | ||
152 | params: req::JoinLinesParams, | ||
153 | ) -> Result<req::SourceChange> { | ||
154 | let _p = profile("handle_join_lines"); | ||
155 | let frange = (¶ms.text_document, params.range).try_conv_with(&world)?; | ||
156 | world.analysis().join_lines(frange)?.try_conv_with(&world) | ||
157 | } | ||
158 | |||
159 | pub fn handle_on_enter( | ||
160 | world: WorldSnapshot, | ||
161 | params: req::TextDocumentPositionParams, | ||
162 | ) -> Result<Option<req::SourceChange>> { | ||
163 | let _p = profile("handle_on_enter"); | ||
164 | let position = params.try_conv_with(&world)?; | ||
165 | match world.analysis().on_enter(position)? { | ||
166 | None => Ok(None), | ||
167 | Some(edit) => Ok(Some(edit.try_conv_with(&world)?)), | ||
168 | } | ||
169 | } | ||
170 | |||
171 | // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. | ||
172 | pub fn handle_on_type_formatting( | ||
173 | world: WorldSnapshot, | ||
174 | params: req::DocumentOnTypeFormattingParams, | ||
175 | ) -> Result<Option<Vec<TextEdit>>> { | ||
176 | let _p = profile("handle_on_type_formatting"); | ||
177 | let mut position = params.text_document_position.try_conv_with(&world)?; | ||
178 | let line_index = world.analysis().file_line_index(position.file_id)?; | ||
179 | let line_endings = world.file_line_endings(position.file_id); | ||
180 | |||
181 | // in `ra_ide`, the `on_type` invariant is that | ||
182 | // `text.char_at(position) == typed_char`. | ||
183 | position.offset -= TextUnit::of_char('.'); | ||
184 | let char_typed = params.ch.chars().next().unwrap_or('\0'); | ||
185 | |||
186 | // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`, | ||
187 | // but it requires precise cursor positioning to work, and one can't | ||
188 | // position the cursor with on_type formatting. So, let's just toggle this | ||
189 | // feature off here, hoping that we'll enable it one day, 😿. | ||
190 | if char_typed == '>' { | ||
191 | return Ok(None); | ||
192 | } | ||
193 | |||
194 | let edit = world.analysis().on_char_typed(position, char_typed)?; | ||
195 | let mut edit = match edit { | ||
196 | Some(it) => it, | ||
197 | None => return Ok(None), | ||
198 | }; | ||
199 | |||
200 | // This should be a single-file edit | ||
201 | let edit = edit.source_file_edits.pop().unwrap(); | ||
202 | |||
203 | let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings)); | ||
204 | Ok(Some(change)) | ||
205 | } | ||
206 | |||
207 | pub fn handle_document_symbol( | ||
208 | world: WorldSnapshot, | ||
209 | params: req::DocumentSymbolParams, | ||
210 | ) -> Result<Option<req::DocumentSymbolResponse>> { | ||
211 | let _p = profile("handle_document_symbol"); | ||
212 | let file_id = params.text_document.try_conv_with(&world)?; | ||
213 | let line_index = world.analysis().file_line_index(file_id)?; | ||
214 | |||
215 | let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); | ||
216 | |||
217 | for symbol in world.analysis().file_structure(file_id)? { | ||
218 | let doc_symbol = DocumentSymbol { | ||
219 | name: symbol.label, | ||
220 | detail: symbol.detail, | ||
221 | kind: symbol.kind.conv(), | ||
222 | deprecated: Some(symbol.deprecated), | ||
223 | range: symbol.node_range.conv_with(&line_index), | ||
224 | selection_range: symbol.navigation_range.conv_with(&line_index), | ||
225 | children: None, | ||
226 | }; | ||
227 | parents.push((doc_symbol, symbol.parent)); | ||
228 | } | ||
229 | let mut res = Vec::new(); | ||
230 | while let Some((node, parent)) = parents.pop() { | ||
231 | match parent { | ||
232 | None => res.push(node), | ||
233 | Some(i) => { | ||
234 | let children = &mut parents[i].0.children; | ||
235 | if children.is_none() { | ||
236 | *children = Some(Vec::new()); | ||
237 | } | ||
238 | children.as_mut().unwrap().push(node); | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | Ok(Some(res.into())) | ||
244 | } | ||
245 | |||
246 | pub fn handle_workspace_symbol( | ||
247 | world: WorldSnapshot, | ||
248 | params: req::WorkspaceSymbolParams, | ||
249 | ) -> Result<Option<Vec<SymbolInformation>>> { | ||
250 | let _p = profile("handle_workspace_symbol"); | ||
251 | let all_symbols = params.query.contains('#'); | ||
252 | let libs = params.query.contains('*'); | ||
253 | let query = { | ||
254 | let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect(); | ||
255 | let mut q = Query::new(query); | ||
256 | if !all_symbols { | ||
257 | q.only_types(); | ||
258 | } | ||
259 | if libs { | ||
260 | q.libs(); | ||
261 | } | ||
262 | q.limit(128); | ||
263 | q | ||
264 | }; | ||
265 | let mut res = exec_query(&world, query)?; | ||
266 | if res.is_empty() && !all_symbols { | ||
267 | let mut query = Query::new(params.query); | ||
268 | query.limit(128); | ||
269 | res = exec_query(&world, query)?; | ||
270 | } | ||
271 | |||
272 | return Ok(Some(res)); | ||
273 | |||
274 | fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> { | ||
275 | let mut res = Vec::new(); | ||
276 | for nav in world.analysis().symbol_search(query)? { | ||
277 | let info = SymbolInformation { | ||
278 | name: nav.name().to_string(), | ||
279 | kind: nav.kind().conv(), | ||
280 | location: nav.try_conv_with(world)?, | ||
281 | container_name: nav.container_name().map(|v| v.to_string()), | ||
282 | deprecated: None, | ||
283 | }; | ||
284 | res.push(info); | ||
285 | } | ||
286 | Ok(res) | ||
287 | } | ||
288 | } | ||
289 | |||
290 | pub fn handle_goto_definition( | ||
291 | world: WorldSnapshot, | ||
292 | params: req::TextDocumentPositionParams, | ||
293 | ) -> Result<Option<req::GotoDefinitionResponse>> { | ||
294 | let _p = profile("handle_goto_definition"); | ||
295 | let position = params.try_conv_with(&world)?; | ||
296 | let nav_info = match world.analysis().goto_definition(position)? { | ||
297 | None => return Ok(None), | ||
298 | Some(it) => it, | ||
299 | }; | ||
300 | let res = (position.file_id, nav_info).try_conv_with(&world)?; | ||
301 | Ok(Some(res)) | ||
302 | } | ||
303 | |||
304 | pub fn handle_goto_implementation( | ||
305 | world: WorldSnapshot, | ||
306 | params: req::TextDocumentPositionParams, | ||
307 | ) -> Result<Option<req::GotoImplementationResponse>> { | ||
308 | let _p = profile("handle_goto_implementation"); | ||
309 | let position = params.try_conv_with(&world)?; | ||
310 | let nav_info = match world.analysis().goto_implementation(position)? { | ||
311 | None => return Ok(None), | ||
312 | Some(it) => it, | ||
313 | }; | ||
314 | let res = (position.file_id, nav_info).try_conv_with(&world)?; | ||
315 | Ok(Some(res)) | ||
316 | } | ||
317 | |||
318 | pub fn handle_goto_type_definition( | ||
319 | world: WorldSnapshot, | ||
320 | params: req::TextDocumentPositionParams, | ||
321 | ) -> Result<Option<req::GotoTypeDefinitionResponse>> { | ||
322 | let _p = profile("handle_goto_type_definition"); | ||
323 | let position = params.try_conv_with(&world)?; | ||
324 | let nav_info = match world.analysis().goto_type_definition(position)? { | ||
325 | None => return Ok(None), | ||
326 | Some(it) => it, | ||
327 | }; | ||
328 | let res = (position.file_id, nav_info).try_conv_with(&world)?; | ||
329 | Ok(Some(res)) | ||
330 | } | ||
331 | |||
332 | pub fn handle_parent_module( | ||
333 | world: WorldSnapshot, | ||
334 | params: req::TextDocumentPositionParams, | ||
335 | ) -> Result<Vec<Location>> { | ||
336 | let _p = profile("handle_parent_module"); | ||
337 | let position = params.try_conv_with(&world)?; | ||
338 | world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world) | ||
339 | } | ||
340 | |||
341 | pub fn handle_runnables( | ||
342 | world: WorldSnapshot, | ||
343 | params: req::RunnablesParams, | ||
344 | ) -> Result<Vec<req::Runnable>> { | ||
345 | let _p = profile("handle_runnables"); | ||
346 | let file_id = params.text_document.try_conv_with(&world)?; | ||
347 | let line_index = world.analysis().file_line_index(file_id)?; | ||
348 | let offset = params.position.map(|it| it.conv_with(&line_index)); | ||
349 | let mut res = Vec::new(); | ||
350 | let workspace_root = world.workspace_root_for(file_id); | ||
351 | for runnable in world.analysis().runnables(file_id)? { | ||
352 | if let Some(offset) = offset { | ||
353 | if !runnable.range.contains_inclusive(offset) { | ||
354 | continue; | ||
355 | } | ||
356 | } | ||
357 | res.push(to_lsp_runnable(&world, file_id, runnable)?); | ||
358 | } | ||
359 | let mut check_args = vec!["check".to_string()]; | ||
360 | let label; | ||
361 | match CargoTargetSpec::for_file(&world, file_id)? { | ||
362 | Some(spec) => { | ||
363 | label = format!("cargo check -p {}", spec.package); | ||
364 | spec.push_to(&mut check_args); | ||
365 | } | ||
366 | None => { | ||
367 | label = "cargo check --all".to_string(); | ||
368 | check_args.push("--all".to_string()) | ||
369 | } | ||
370 | } | ||
371 | // Always add `cargo check`. | ||
372 | res.push(req::Runnable { | ||
373 | range: Default::default(), | ||
374 | label, | ||
375 | bin: "cargo".to_string(), | ||
376 | args: check_args, | ||
377 | env: FxHashMap::default(), | ||
378 | cwd: workspace_root.map(|root| root.to_string_lossy().to_string()), | ||
379 | }); | ||
380 | Ok(res) | ||
381 | } | ||
382 | |||
383 | pub fn handle_decorations( | ||
384 | world: WorldSnapshot, | ||
385 | params: TextDocumentIdentifier, | ||
386 | ) -> Result<Vec<Decoration>> { | ||
387 | let _p = profile("handle_decorations"); | ||
388 | let file_id = params.try_conv_with(&world)?; | ||
389 | highlight(&world, file_id) | ||
390 | } | ||
391 | |||
392 | pub fn handle_completion( | ||
393 | world: WorldSnapshot, | ||
394 | params: req::CompletionParams, | ||
395 | ) -> Result<Option<req::CompletionResponse>> { | ||
396 | let _p = profile("handle_completion"); | ||
397 | let position = params.text_document_position.try_conv_with(&world)?; | ||
398 | let completion_triggered_after_single_colon = { | ||
399 | let mut res = false; | ||
400 | if let Some(ctx) = params.context { | ||
401 | if ctx.trigger_character.unwrap_or_default() == ":" { | ||
402 | let source_file = world.analysis().parse(position.file_id)?; | ||
403 | let syntax = source_file.syntax(); | ||
404 | let text = syntax.text(); | ||
405 | if let Some(next_char) = text.char_at(position.offset) { | ||
406 | let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':'); | ||
407 | let prev_char = position.offset - diff; | ||
408 | if text.char_at(prev_char) != Some(':') { | ||
409 | res = true; | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | } | ||
414 | res | ||
415 | }; | ||
416 | if completion_triggered_after_single_colon { | ||
417 | return Ok(None); | ||
418 | } | ||
419 | |||
420 | let items = match world.analysis().completions(position)? { | ||
421 | None => return Ok(None), | ||
422 | Some(items) => items, | ||
423 | }; | ||
424 | let line_index = world.analysis().file_line_index(position.file_id)?; | ||
425 | let line_endings = world.file_line_endings(position.file_id); | ||
426 | let items: Vec<CompletionItem> = | ||
427 | items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect(); | ||
428 | |||
429 | Ok(Some(items.into())) | ||
430 | } | ||
431 | |||
432 | pub fn handle_folding_range( | ||
433 | world: WorldSnapshot, | ||
434 | params: FoldingRangeParams, | ||
435 | ) -> Result<Option<Vec<FoldingRange>>> { | ||
436 | let _p = profile("handle_folding_range"); | ||
437 | let file_id = params.text_document.try_conv_with(&world)?; | ||
438 | let folds = world.analysis().folding_ranges(file_id)?; | ||
439 | let text = world.analysis().file_text(file_id)?; | ||
440 | let line_index = world.analysis().file_line_index(file_id)?; | ||
441 | let ctx = FoldConvCtx { | ||
442 | text: &text, | ||
443 | line_index: &line_index, | ||
444 | line_folding_only: world.options.line_folding_only, | ||
445 | }; | ||
446 | let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); | ||
447 | Ok(res) | ||
448 | } | ||
449 | |||
450 | pub fn handle_signature_help( | ||
451 | world: WorldSnapshot, | ||
452 | params: req::TextDocumentPositionParams, | ||
453 | ) -> Result<Option<req::SignatureHelp>> { | ||
454 | let _p = profile("handle_signature_help"); | ||
455 | let position = params.try_conv_with(&world)?; | ||
456 | if let Some(call_info) = world.analysis().call_info(position)? { | ||
457 | let active_parameter = call_info.active_parameter.map(|it| it as i64); | ||
458 | let sig_info = call_info.signature.conv(); | ||
459 | |||
460 | Ok(Some(req::SignatureHelp { | ||
461 | signatures: vec![sig_info], | ||
462 | active_signature: Some(0), | ||
463 | active_parameter, | ||
464 | })) | ||
465 | } else { | ||
466 | Ok(None) | ||
467 | } | ||
468 | } | ||
469 | |||
470 | pub fn handle_hover( | ||
471 | world: WorldSnapshot, | ||
472 | params: req::TextDocumentPositionParams, | ||
473 | ) -> Result<Option<Hover>> { | ||
474 | let _p = profile("handle_hover"); | ||
475 | let position = params.try_conv_with(&world)?; | ||
476 | let info = match world.analysis().hover(position)? { | ||
477 | None => return Ok(None), | ||
478 | Some(info) => info, | ||
479 | }; | ||
480 | let line_index = world.analysis.file_line_index(position.file_id)?; | ||
481 | let range = info.range.conv_with(&line_index); | ||
482 | let res = Hover { | ||
483 | contents: HoverContents::Markup(MarkupContent { | ||
484 | kind: MarkupKind::Markdown, | ||
485 | value: crate::markdown::format_docs(&info.info.to_markup()), | ||
486 | }), | ||
487 | range: Some(range), | ||
488 | }; | ||
489 | Ok(Some(res)) | ||
490 | } | ||
491 | |||
492 | pub fn handle_prepare_rename( | ||
493 | world: WorldSnapshot, | ||
494 | params: req::TextDocumentPositionParams, | ||
495 | ) -> Result<Option<PrepareRenameResponse>> { | ||
496 | let _p = profile("handle_prepare_rename"); | ||
497 | let position = params.try_conv_with(&world)?; | ||
498 | |||
499 | let optional_change = world.analysis().rename(position, "dummy")?; | ||
500 | let range = match optional_change { | ||
501 | None => return Ok(None), | ||
502 | Some(it) => it.range, | ||
503 | }; | ||
504 | |||
505 | let file_id = params.text_document.try_conv_with(&world)?; | ||
506 | let line_index = world.analysis().file_line_index(file_id)?; | ||
507 | let range = range.conv_with(&line_index); | ||
508 | Ok(Some(PrepareRenameResponse::Range(range))) | ||
509 | } | ||
510 | |||
511 | pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> { | ||
512 | let _p = profile("handle_rename"); | ||
513 | let position = params.text_document_position.try_conv_with(&world)?; | ||
514 | |||
515 | if params.new_name.is_empty() { | ||
516 | return Err(LspError::new( | ||
517 | ErrorCode::InvalidParams as i32, | ||
518 | "New Name cannot be empty".into(), | ||
519 | ) | ||
520 | .into()); | ||
521 | } | ||
522 | |||
523 | let optional_change = world.analysis().rename(position, &*params.new_name)?; | ||
524 | let change = match optional_change { | ||
525 | None => return Ok(None), | ||
526 | Some(it) => it.info, | ||
527 | }; | ||
528 | |||
529 | let source_change_req = change.try_conv_with(&world)?; | ||
530 | |||
531 | Ok(Some(source_change_req.workspace_edit)) | ||
532 | } | ||
533 | |||
534 | pub fn handle_references( | ||
535 | world: WorldSnapshot, | ||
536 | params: req::ReferenceParams, | ||
537 | ) -> Result<Option<Vec<Location>>> { | ||
538 | let _p = profile("handle_references"); | ||
539 | let position = params.text_document_position.try_conv_with(&world)?; | ||
540 | |||
541 | let refs = match world.analysis().find_all_refs(position, None)? { | ||
542 | None => return Ok(None), | ||
543 | Some(refs) => refs, | ||
544 | }; | ||
545 | |||
546 | let locations = if params.context.include_declaration { | ||
547 | refs.into_iter() | ||
548 | .filter_map(|reference| { | ||
549 | let line_index = | ||
550 | world.analysis().file_line_index(reference.file_range.file_id).ok()?; | ||
551 | to_location( | ||
552 | reference.file_range.file_id, | ||
553 | reference.file_range.range, | ||
554 | &world, | ||
555 | &line_index, | ||
556 | ) | ||
557 | .ok() | ||
558 | }) | ||
559 | .collect() | ||
560 | } else { | ||
561 | // Only iterate over the references if include_declaration was false | ||
562 | refs.references() | ||
563 | .iter() | ||
564 | .filter_map(|reference| { | ||
565 | let line_index = | ||
566 | world.analysis().file_line_index(reference.file_range.file_id).ok()?; | ||
567 | to_location( | ||
568 | reference.file_range.file_id, | ||
569 | reference.file_range.range, | ||
570 | &world, | ||
571 | &line_index, | ||
572 | ) | ||
573 | .ok() | ||
574 | }) | ||
575 | .collect() | ||
576 | }; | ||
577 | |||
578 | Ok(Some(locations)) | ||
579 | } | ||
580 | |||
581 | pub fn handle_formatting( | ||
582 | world: WorldSnapshot, | ||
583 | params: DocumentFormattingParams, | ||
584 | ) -> Result<Option<Vec<TextEdit>>> { | ||
585 | let _p = profile("handle_formatting"); | ||
586 | let file_id = params.text_document.try_conv_with(&world)?; | ||
587 | let file = world.analysis().file_text(file_id)?; | ||
588 | let crate_ids = world.analysis().crate_for(file_id)?; | ||
589 | |||
590 | let file_line_index = world.analysis().file_line_index(file_id)?; | ||
591 | let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); | ||
592 | |||
593 | let mut rustfmt = process::Command::new("rustfmt"); | ||
594 | rustfmt.args(&world.options.rustfmt_args); | ||
595 | if let Some(&crate_id) = crate_ids.first() { | ||
596 | // Assume all crates are in the same edition | ||
597 | let edition = world.analysis().crate_edition(crate_id)?; | ||
598 | rustfmt.args(&["--edition", &edition.to_string()]); | ||
599 | } | ||
600 | |||
601 | if let Ok(path) = params.text_document.uri.to_file_path() { | ||
602 | if let Some(parent) = path.parent() { | ||
603 | rustfmt.current_dir(parent); | ||
604 | } | ||
605 | } | ||
606 | let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; | ||
607 | |||
608 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | ||
609 | |||
610 | let output = rustfmt.wait_with_output()?; | ||
611 | let captured_stdout = String::from_utf8(output.stdout)?; | ||
612 | |||
613 | if !output.status.success() { | ||
614 | match output.status.code() { | ||
615 | Some(1) => { | ||
616 | // While `rustfmt` doesn't have a specific exit code for parse errors this is the | ||
617 | // likely cause exiting with 1. Most Language Servers swallow parse errors on | ||
618 | // formatting because otherwise an error is surfaced to the user on top of the | ||
619 | // syntax error diagnostics they're already receiving. This is especially jarring | ||
620 | // if they have format on save enabled. | ||
621 | log::info!("rustfmt exited with status 1, assuming parse error and ignoring"); | ||
622 | return Ok(None); | ||
623 | } | ||
624 | _ => { | ||
625 | // Something else happened - e.g. `rustfmt` is missing or caught a signal | ||
626 | return Err(LspError::new( | ||
627 | -32900, | ||
628 | format!( | ||
629 | r#"rustfmt exited with: | ||
630 | Status: {} | ||
631 | stdout: {}"#, | ||
632 | output.status, captured_stdout, | ||
633 | ), | ||
634 | ) | ||
635 | .into()); | ||
636 | } | ||
637 | } | ||
638 | } | ||
639 | |||
640 | Ok(Some(vec![TextEdit { | ||
641 | range: Range::new(Position::new(0, 0), end_position), | ||
642 | new_text: captured_stdout, | ||
643 | }])) | ||
644 | } | ||
645 | |||
646 | pub fn handle_code_action( | ||
647 | world: WorldSnapshot, | ||
648 | params: req::CodeActionParams, | ||
649 | ) -> Result<Option<CodeActionResponse>> { | ||
650 | let _p = profile("handle_code_action"); | ||
651 | let file_id = params.text_document.try_conv_with(&world)?; | ||
652 | let line_index = world.analysis().file_line_index(file_id)?; | ||
653 | let range = params.range.conv_with(&line_index); | ||
654 | |||
655 | let diagnostics = world.analysis().diagnostics(file_id)?; | ||
656 | let mut res = CodeActionResponse::default(); | ||
657 | |||
658 | let fixes_from_diagnostics = diagnostics | ||
659 | .into_iter() | ||
660 | .filter_map(|d| Some((d.range, d.fix?))) | ||
661 | .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some()) | ||
662 | .map(|(_range, fix)| fix); | ||
663 | |||
664 | for source_edit in fixes_from_diagnostics { | ||
665 | let title = source_edit.label.clone(); | ||
666 | let edit = source_edit.try_conv_with(&world)?; | ||
667 | |||
668 | let command = Command { | ||
669 | title, | ||
670 | command: "rust-analyzer.applySourceChange".to_string(), | ||
671 | arguments: Some(vec![to_value(edit).unwrap()]), | ||
672 | }; | ||
673 | let action = CodeAction { | ||
674 | title: command.title.clone(), | ||
675 | kind: None, | ||
676 | diagnostics: None, | ||
677 | edit: None, | ||
678 | command: Some(command), | ||
679 | is_preferred: None, | ||
680 | }; | ||
681 | res.push(action.into()); | ||
682 | } | ||
683 | |||
684 | for fix in world.check_fixes.get(&file_id).into_iter().flatten() { | ||
685 | let fix_range = fix.range.conv_with(&line_index); | ||
686 | if fix_range.intersection(&range).is_none() { | ||
687 | continue; | ||
688 | } | ||
689 | res.push(fix.action.clone()); | ||
690 | } | ||
691 | |||
692 | let mut groups = FxHashMap::default(); | ||
693 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { | ||
694 | let arg = to_value(assist.source_change.try_conv_with(&world)?)?; | ||
695 | |||
696 | let (command, title, arg) = match assist.group_label { | ||
697 | None => ("rust-analyzer.applySourceChange", assist.label.clone(), arg), | ||
698 | |||
699 | // Group all assists with the same `group_label` into a single CodeAction. | ||
700 | Some(group_label) => { | ||
701 | match groups.entry(group_label.clone()) { | ||
702 | Entry::Occupied(entry) => { | ||
703 | let idx: usize = *entry.get(); | ||
704 | match &mut res[idx] { | ||
705 | CodeActionOrCommand::CodeAction(CodeAction { | ||
706 | command: Some(Command { arguments: Some(arguments), .. }), | ||
707 | .. | ||
708 | }) => match arguments.as_mut_slice() { | ||
709 | [serde_json::Value::Array(arguments)] => arguments.push(arg), | ||
710 | _ => panic!("invalid group"), | ||
711 | }, | ||
712 | _ => panic!("invalid group"), | ||
713 | } | ||
714 | continue; | ||
715 | } | ||
716 | Entry::Vacant(entry) => { | ||
717 | entry.insert(res.len()); | ||
718 | } | ||
719 | } | ||
720 | ("rust-analyzer.selectAndApplySourceChange", group_label, to_value(vec![arg])?) | ||
721 | } | ||
722 | }; | ||
723 | |||
724 | let command = Command { | ||
725 | title: assist.label.clone(), | ||
726 | command: command.to_string(), | ||
727 | arguments: Some(vec![arg]), | ||
728 | }; | ||
729 | |||
730 | let kind = match assist.id { | ||
731 | AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()), | ||
732 | AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()), | ||
733 | _ => None, | ||
734 | }; | ||
735 | |||
736 | let action = CodeAction { | ||
737 | title, | ||
738 | kind, | ||
739 | diagnostics: None, | ||
740 | edit: None, | ||
741 | command: Some(command), | ||
742 | is_preferred: None, | ||
743 | }; | ||
744 | res.push(action.into()); | ||
745 | } | ||
746 | |||
747 | Ok(Some(res)) | ||
748 | } | ||
749 | |||
750 | pub fn handle_code_lens( | ||
751 | world: WorldSnapshot, | ||
752 | params: req::CodeLensParams, | ||
753 | ) -> Result<Option<Vec<CodeLens>>> { | ||
754 | let _p = profile("handle_code_lens"); | ||
755 | let file_id = params.text_document.try_conv_with(&world)?; | ||
756 | let line_index = world.analysis().file_line_index(file_id)?; | ||
757 | |||
758 | let mut lenses: Vec<CodeLens> = Default::default(); | ||
759 | |||
760 | // Gather runnables | ||
761 | for runnable in world.analysis().runnables(file_id)? { | ||
762 | let title = match &runnable.kind { | ||
763 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test", | ||
764 | RunnableKind::Bench { .. } => "Run Bench", | ||
765 | RunnableKind::Bin => "Run", | ||
766 | } | ||
767 | .to_string(); | ||
768 | let r = to_lsp_runnable(&world, file_id, runnable)?; | ||
769 | let lens = CodeLens { | ||
770 | range: r.range, | ||
771 | command: Some(Command { | ||
772 | title, | ||
773 | command: "rust-analyzer.runSingle".into(), | ||
774 | arguments: Some(vec![to_value(r).unwrap()]), | ||
775 | }), | ||
776 | data: None, | ||
777 | }; | ||
778 | |||
779 | lenses.push(lens); | ||
780 | } | ||
781 | |||
782 | // Handle impls | ||
783 | lenses.extend( | ||
784 | world | ||
785 | .analysis() | ||
786 | .file_structure(file_id)? | ||
787 | .into_iter() | ||
788 | .filter(|it| match it.kind { | ||
789 | SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true, | ||
790 | _ => false, | ||
791 | }) | ||
792 | .map(|it| { | ||
793 | let range = it.node_range.conv_with(&line_index); | ||
794 | let pos = range.start; | ||
795 | let lens_params = | ||
796 | req::TextDocumentPositionParams::new(params.text_document.clone(), pos); | ||
797 | CodeLens { | ||
798 | range, | ||
799 | command: None, | ||
800 | data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()), | ||
801 | } | ||
802 | }), | ||
803 | ); | ||
804 | |||
805 | Ok(Some(lenses)) | ||
806 | } | ||
807 | |||
808 | #[derive(Debug, Serialize, Deserialize)] | ||
809 | #[serde(rename_all = "camelCase")] | ||
810 | enum CodeLensResolveData { | ||
811 | Impls(req::TextDocumentPositionParams), | ||
812 | } | ||
813 | |||
814 | pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> { | ||
815 | let _p = profile("handle_code_lens_resolve"); | ||
816 | let data = code_lens.data.unwrap(); | ||
817 | let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?; | ||
818 | match resolve { | ||
819 | Some(CodeLensResolveData::Impls(lens_params)) => { | ||
820 | let locations: Vec<Location> = | ||
821 | match handle_goto_implementation(world, lens_params.clone())? { | ||
822 | Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc], | ||
823 | Some(req::GotoDefinitionResponse::Array(locs)) => locs, | ||
824 | Some(req::GotoDefinitionResponse::Link(links)) => links | ||
825 | .into_iter() | ||
826 | .map(|link| Location::new(link.target_uri, link.target_selection_range)) | ||
827 | .collect(), | ||
828 | _ => vec![], | ||
829 | }; | ||
830 | |||
831 | let title = if locations.len() == 1 { | ||
832 | "1 implementation".into() | ||
833 | } else { | ||
834 | format!("{} implementations", locations.len()) | ||
835 | }; | ||
836 | |||
837 | // We cannot use the 'editor.action.showReferences' command directly | ||
838 | // because that command requires vscode types which we convert in the handler | ||
839 | // on the client side. | ||
840 | let cmd = Command { | ||
841 | title, | ||
842 | command: "rust-analyzer.showReferences".into(), | ||
843 | arguments: Some(vec![ | ||
844 | to_value(&lens_params.text_document.uri).unwrap(), | ||
845 | to_value(code_lens.range.start).unwrap(), | ||
846 | to_value(locations).unwrap(), | ||
847 | ]), | ||
848 | }; | ||
849 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) | ||
850 | } | ||
851 | None => Ok(CodeLens { | ||
852 | range: code_lens.range, | ||
853 | command: Some(Command { title: "Error".into(), ..Default::default() }), | ||
854 | data: None, | ||
855 | }), | ||
856 | } | ||
857 | } | ||
858 | |||
859 | pub fn handle_document_highlight( | ||
860 | world: WorldSnapshot, | ||
861 | params: req::TextDocumentPositionParams, | ||
862 | ) -> Result<Option<Vec<DocumentHighlight>>> { | ||
863 | let _p = profile("handle_document_highlight"); | ||
864 | let file_id = params.text_document.try_conv_with(&world)?; | ||
865 | let line_index = world.analysis().file_line_index(file_id)?; | ||
866 | |||
867 | let refs = match world | ||
868 | .analysis() | ||
869 | .find_all_refs(params.try_conv_with(&world)?, Some(SearchScope::single_file(file_id)))? | ||
870 | { | ||
871 | None => return Ok(None), | ||
872 | Some(refs) => refs, | ||
873 | }; | ||
874 | |||
875 | Ok(Some( | ||
876 | refs.into_iter() | ||
877 | .filter(|reference| reference.file_range.file_id == file_id) | ||
878 | .map(|reference| DocumentHighlight { | ||
879 | range: reference.file_range.range.conv_with(&line_index), | ||
880 | kind: reference.access.map(|it| it.conv()), | ||
881 | }) | ||
882 | .collect(), | ||
883 | )) | ||
884 | } | ||
885 | |||
886 | pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> { | ||
887 | let _p = profile("handle_ssr"); | ||
888 | world.analysis().structural_search_replace(¶ms.arg)??.try_conv_with(&world) | ||
889 | } | ||
890 | |||
891 | pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { | ||
892 | let _p = profile("publish_diagnostics"); | ||
893 | let line_index = world.analysis().file_line_index(file_id)?; | ||
894 | let diagnostics: Vec<Diagnostic> = world | ||
895 | .analysis() | ||
896 | .diagnostics(file_id)? | ||
897 | .into_iter() | ||
898 | .map(|d| Diagnostic { | ||
899 | range: d.range.conv_with(&line_index), | ||
900 | severity: Some(d.severity.conv()), | ||
901 | code: None, | ||
902 | source: Some("rust-analyzer".to_string()), | ||
903 | message: d.message, | ||
904 | related_information: None, | ||
905 | tags: None, | ||
906 | }) | ||
907 | .collect(); | ||
908 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) | ||
909 | } | ||
910 | |||
911 | pub fn publish_decorations( | ||
912 | world: &WorldSnapshot, | ||
913 | file_id: FileId, | ||
914 | ) -> Result<req::PublishDecorationsParams> { | ||
915 | let _p = profile("publish_decorations"); | ||
916 | let uri = world.file_id_to_uri(file_id)?; | ||
917 | Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? }) | ||
918 | } | ||
919 | |||
920 | fn to_lsp_runnable( | ||
921 | world: &WorldSnapshot, | ||
922 | file_id: FileId, | ||
923 | runnable: Runnable, | ||
924 | ) -> Result<req::Runnable> { | ||
925 | let spec = CargoTargetSpec::for_file(world, file_id)?; | ||
926 | let args = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; | ||
927 | let line_index = world.analysis().file_line_index(file_id)?; | ||
928 | let label = match &runnable.kind { | ||
929 | RunnableKind::Test { test_id } => format!("test {}", test_id), | ||
930 | RunnableKind::TestMod { path } => format!("test-mod {}", path), | ||
931 | RunnableKind::Bench { test_id } => format!("bench {}", test_id), | ||
932 | RunnableKind::Bin => "run binary".to_string(), | ||
933 | }; | ||
934 | Ok(req::Runnable { | ||
935 | range: runnable.range.conv_with(&line_index), | ||
936 | label, | ||
937 | bin: "cargo".to_string(), | ||
938 | args, | ||
939 | env: { | ||
940 | let mut m = FxHashMap::default(); | ||
941 | m.insert("RUST_BACKTRACE".to_string(), "short".to_string()); | ||
942 | m | ||
943 | }, | ||
944 | cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()), | ||
945 | }) | ||
946 | } | ||
947 | fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> { | ||
948 | let line_index = world.analysis().file_line_index(file_id)?; | ||
949 | let res = world | ||
950 | .analysis() | ||
951 | .highlight(file_id)? | ||
952 | .into_iter() | ||
953 | .map(|h| Decoration { | ||
954 | range: h.range.conv_with(&line_index), | ||
955 | tag: h.tag, | ||
956 | binding_hash: h.binding_hash.map(|x| x.to_string()), | ||
957 | }) | ||
958 | .collect(); | ||
959 | Ok(res) | ||
960 | } | ||
961 | |||
962 | pub fn handle_inlay_hints( | ||
963 | world: WorldSnapshot, | ||
964 | params: InlayHintsParams, | ||
965 | ) -> Result<Vec<InlayHint>> { | ||
966 | let _p = profile("handle_inlay_hints"); | ||
967 | let file_id = params.text_document.try_conv_with(&world)?; | ||
968 | let analysis = world.analysis(); | ||
969 | let line_index = analysis.file_line_index(file_id)?; | ||
970 | Ok(analysis | ||
971 | .inlay_hints(file_id, world.options.max_inlay_hint_length)? | ||
972 | .into_iter() | ||
973 | .map(|api_type| InlayHint { | ||
974 | label: api_type.label.to_string(), | ||
975 | range: api_type.range.conv_with(&line_index), | ||
976 | kind: match api_type.kind { | ||
977 | ra_ide::InlayKind::TypeHint => InlayKind::TypeHint, | ||
978 | ra_ide::InlayKind::ParameterHint => InlayKind::ParameterHint, | ||
979 | }, | ||
980 | }) | ||
981 | .collect()) | ||
982 | } | ||
983 | |||
984 | pub fn handle_call_hierarchy_prepare( | ||
985 | world: WorldSnapshot, | ||
986 | params: CallHierarchyPrepareParams, | ||
987 | ) -> Result<Option<Vec<CallHierarchyItem>>> { | ||
988 | let _p = profile("handle_call_hierarchy_prepare"); | ||
989 | let position = params.text_document_position_params.try_conv_with(&world)?; | ||
990 | let file_id = position.file_id; | ||
991 | |||
992 | let nav_info = match world.analysis().call_hierarchy(position)? { | ||
993 | None => return Ok(None), | ||
994 | Some(it) => it, | ||
995 | }; | ||
996 | |||
997 | let line_index = world.analysis().file_line_index(file_id)?; | ||
998 | let RangeInfo { range, info: navs } = nav_info; | ||
999 | let res = navs | ||
1000 | .into_iter() | ||
1001 | .filter(|it| it.kind() == SyntaxKind::FN_DEF) | ||
1002 | .filter_map(|it| to_call_hierarchy_item(file_id, range, &world, &line_index, it).ok()) | ||
1003 | .collect(); | ||
1004 | |||
1005 | Ok(Some(res)) | ||
1006 | } | ||
1007 | |||
1008 | pub fn handle_call_hierarchy_incoming( | ||
1009 | world: WorldSnapshot, | ||
1010 | params: CallHierarchyIncomingCallsParams, | ||
1011 | ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> { | ||
1012 | let _p = profile("handle_call_hierarchy_incoming"); | ||
1013 | let item = params.item; | ||
1014 | |||
1015 | let doc = TextDocumentIdentifier::new(item.uri); | ||
1016 | let frange: FileRange = (&doc, item.range).try_conv_with(&world)?; | ||
1017 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
1018 | |||
1019 | let call_items = match world.analysis().incoming_calls(fpos)? { | ||
1020 | None => return Ok(None), | ||
1021 | Some(it) => it, | ||
1022 | }; | ||
1023 | |||
1024 | let mut res = vec![]; | ||
1025 | |||
1026 | for call_item in call_items.into_iter() { | ||
1027 | let file_id = call_item.target.file_id(); | ||
1028 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1029 | let range = call_item.target.range(); | ||
1030 | let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?; | ||
1031 | res.push(CallHierarchyIncomingCall { | ||
1032 | from: item, | ||
1033 | from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(), | ||
1034 | }); | ||
1035 | } | ||
1036 | |||
1037 | Ok(Some(res)) | ||
1038 | } | ||
1039 | |||
1040 | pub fn handle_call_hierarchy_outgoing( | ||
1041 | world: WorldSnapshot, | ||
1042 | params: CallHierarchyOutgoingCallsParams, | ||
1043 | ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> { | ||
1044 | let _p = profile("handle_call_hierarchy_outgoing"); | ||
1045 | let item = params.item; | ||
1046 | |||
1047 | let doc = TextDocumentIdentifier::new(item.uri); | ||
1048 | let frange: FileRange = (&doc, item.range).try_conv_with(&world)?; | ||
1049 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
1050 | |||
1051 | let call_items = match world.analysis().outgoing_calls(fpos)? { | ||
1052 | None => return Ok(None), | ||
1053 | Some(it) => it, | ||
1054 | }; | ||
1055 | |||
1056 | let mut res = vec![]; | ||
1057 | |||
1058 | for call_item in call_items.into_iter() { | ||
1059 | let file_id = call_item.target.file_id(); | ||
1060 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1061 | let range = call_item.target.range(); | ||
1062 | let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?; | ||
1063 | res.push(CallHierarchyOutgoingCall { | ||
1064 | to: item, | ||
1065 | from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(), | ||
1066 | }); | ||
1067 | } | ||
1068 | |||
1069 | Ok(Some(res)) | ||
1070 | } | ||
diff --git a/crates/ra_lsp_server/src/main_loop/pending_requests.rs b/crates/ra_lsp_server/src/main_loop/pending_requests.rs deleted file mode 100644 index 73b33e419..000000000 --- a/crates/ra_lsp_server/src/main_loop/pending_requests.rs +++ /dev/null | |||
@@ -1,75 +0,0 @@ | |||
1 | //! Data structures that keep track of inflight requests. | ||
2 | |||
3 | use std::time::{Duration, Instant}; | ||
4 | |||
5 | use lsp_server::RequestId; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | |||
8 | #[derive(Debug)] | ||
9 | pub struct CompletedRequest { | ||
10 | pub id: RequestId, | ||
11 | pub method: String, | ||
12 | pub duration: Duration, | ||
13 | } | ||
14 | |||
15 | #[derive(Debug)] | ||
16 | pub(crate) struct PendingRequest { | ||
17 | pub(crate) id: RequestId, | ||
18 | pub(crate) method: String, | ||
19 | pub(crate) received: Instant, | ||
20 | } | ||
21 | |||
22 | impl From<PendingRequest> for CompletedRequest { | ||
23 | fn from(pending: PendingRequest) -> CompletedRequest { | ||
24 | CompletedRequest { | ||
25 | id: pending.id, | ||
26 | method: pending.method, | ||
27 | duration: pending.received.elapsed(), | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | #[derive(Debug, Default)] | ||
33 | pub(crate) struct PendingRequests { | ||
34 | map: FxHashMap<RequestId, PendingRequest>, | ||
35 | } | ||
36 | |||
37 | impl PendingRequests { | ||
38 | pub(crate) fn start(&mut self, request: PendingRequest) { | ||
39 | let id = request.id.clone(); | ||
40 | let prev = self.map.insert(id.clone(), request); | ||
41 | assert!(prev.is_none(), "duplicate request with id {}", id); | ||
42 | } | ||
43 | pub(crate) fn cancel(&mut self, id: &RequestId) -> bool { | ||
44 | self.map.remove(id).is_some() | ||
45 | } | ||
46 | pub(crate) fn finish(&mut self, id: &RequestId) -> Option<CompletedRequest> { | ||
47 | self.map.remove(id).map(CompletedRequest::from) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | const N_COMPLETED_REQUESTS: usize = 10; | ||
52 | |||
53 | #[derive(Debug, Default)] | ||
54 | pub struct LatestRequests { | ||
55 | // hand-rolling VecDeque here to print things in a nicer way | ||
56 | buf: [Option<CompletedRequest>; N_COMPLETED_REQUESTS], | ||
57 | idx: usize, | ||
58 | } | ||
59 | |||
60 | impl LatestRequests { | ||
61 | pub(crate) fn record(&mut self, request: CompletedRequest) { | ||
62 | // special case: don't track status request itself | ||
63 | if request.method == "rust-analyzer/analyzerStatus" { | ||
64 | return; | ||
65 | } | ||
66 | let idx = self.idx; | ||
67 | self.buf[idx] = Some(request); | ||
68 | self.idx = (idx + 1) % N_COMPLETED_REQUESTS; | ||
69 | } | ||
70 | |||
71 | pub(crate) fn iter(&self) -> impl Iterator<Item = (bool, &CompletedRequest)> { | ||
72 | let idx = self.idx; | ||
73 | self.buf.iter().enumerate().filter_map(move |(i, req)| Some((i == idx, req.as_ref()?))) | ||
74 | } | ||
75 | } | ||
diff --git a/crates/ra_lsp_server/src/main_loop/subscriptions.rs b/crates/ra_lsp_server/src/main_loop/subscriptions.rs deleted file mode 100644 index bee6437cf..000000000 --- a/crates/ra_lsp_server/src/main_loop/subscriptions.rs +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | //! Keeps track of file subscriptions -- the set of currently opened files for | ||
2 | //! which we want to publish diagnostics, syntax highlighting, etc. | ||
3 | |||
4 | use ra_ide::FileId; | ||
5 | use rustc_hash::FxHashSet; | ||
6 | |||
7 | #[derive(Default, Debug)] | ||
8 | pub(crate) struct Subscriptions { | ||
9 | subs: FxHashSet<FileId>, | ||
10 | } | ||
11 | |||
12 | impl Subscriptions { | ||
13 | pub(crate) fn add_sub(&mut self, file_id: FileId) { | ||
14 | self.subs.insert(file_id); | ||
15 | } | ||
16 | pub(crate) fn remove_sub(&mut self, file_id: FileId) { | ||
17 | self.subs.remove(&file_id); | ||
18 | } | ||
19 | pub(crate) fn subscriptions(&self) -> Vec<FileId> { | ||
20 | self.subs.iter().cloned().collect() | ||
21 | } | ||
22 | } | ||