aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/main_loop
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src/main_loop')
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs1070
-rw-r--r--crates/ra_lsp_server/src/main_loop/pending_requests.rs75
-rw-r--r--crates/ra_lsp_server/src/main_loop/subscriptions.rs22
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
5use std::{
6 collections::hash_map::Entry,
7 fmt::Write as _,
8 io::Write as _,
9 process::{self, Stdio},
10};
11
12use lsp_server::ErrorCode;
13use 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};
22use ra_ide::{
23 AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
24 SearchScope,
25};
26use ra_prof::profile;
27use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
28use rustc_hash::FxHashMap;
29use serde::{Deserialize, Serialize};
30use serde_json::to_value;
31
32use 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
45pub 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
57pub 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
66pub 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
84pub 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
125pub 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
150pub 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 = (&params.text_document, params.range).try_conv_with(&world)?;
156 world.analysis().join_lines(frange)?.try_conv_with(&world)
157}
158
159pub 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`.
172pub 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
207pub 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
246pub 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
290pub 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
304pub 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
318pub 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
332pub 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
341pub 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
383pub 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
392pub 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
432pub 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
450pub 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
470pub 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
492pub 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
511pub 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
534pub 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
581pub 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
646pub 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
750pub 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")]
810enum CodeLensResolveData {
811 Impls(req::TextDocumentPositionParams),
812}
813
814pub 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
859pub 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
886pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> {
887 let _p = profile("handle_ssr");
888 world.analysis().structural_search_replace(&params.arg)??.try_conv_with(&world)
889}
890
891pub 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
911pub 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
920fn 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}
947fn 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
962pub 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
984pub 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
1008pub 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
1040pub 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
3use std::time::{Duration, Instant};
4
5use lsp_server::RequestId;
6use rustc_hash::FxHashMap;
7
8#[derive(Debug)]
9pub struct CompletedRequest {
10 pub id: RequestId,
11 pub method: String,
12 pub duration: Duration,
13}
14
15#[derive(Debug)]
16pub(crate) struct PendingRequest {
17 pub(crate) id: RequestId,
18 pub(crate) method: String,
19 pub(crate) received: Instant,
20}
21
22impl 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)]
33pub(crate) struct PendingRequests {
34 map: FxHashMap<RequestId, PendingRequest>,
35}
36
37impl 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
51const N_COMPLETED_REQUESTS: usize = 10;
52
53#[derive(Debug, Default)]
54pub 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
60impl 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
4use ra_ide::FileId;
5use rustc_hash::FxHashSet;
6
7#[derive(Default, Debug)]
8pub(crate) struct Subscriptions {
9 subs: FxHashSet<FileId>,
10}
11
12impl 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}