]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/handlers.rs
d39dceae0424c23dd66a0ca2a1b6085c03068d1b
[rust.git] / crates / rust-analyzer / src / handlers.rs
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     io::Write as _,
7     process::{self, Stdio},
8 };
9
10 use lsp_server::ErrorCode;
11 use lsp_types::{
12     CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
13     CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
14     CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
15     DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, Position,
16     PrepareRenameResponse, Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
17     SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
18     Url, WorkspaceEdit,
19 };
20 use ra_ide::{
21     FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
22     RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
23 };
24 use ra_prof::profile;
25 use ra_project_model::TargetKind;
26 use ra_syntax::{algo, ast, AstNode, SyntaxKind, TextRange, TextSize};
27 use serde::{Deserialize, Serialize};
28 use serde_json::to_value;
29 use stdx::{format_to, split_delim};
30
31 use crate::{
32     cargo_target_spec::CargoTargetSpec,
33     config::RustfmtConfig,
34     from_json, from_proto,
35     global_state::{GlobalState, GlobalStateSnapshot},
36     lsp_ext::{self, InlayHint, InlayHintsParams},
37     to_proto, LspError, Result,
38 };
39
40 pub(crate) fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> {
41     let _p = profile("handle_analyzer_status");
42
43     let mut buf = String::new();
44     if snap.workspaces.is_empty() {
45         buf.push_str("no workspaces\n")
46     } else {
47         buf.push_str("workspaces:\n");
48         for w in snap.workspaces.iter() {
49             format_to!(buf, "{} packages loaded\n", w.n_packages());
50         }
51     }
52     buf.push_str("\nanalysis:\n");
53     buf.push_str(
54         &snap.analysis.status().unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
55     );
56     format_to!(buf, "\n\nrequests:\n");
57     let requests = snap.latest_requests.read();
58     for (is_last, r) in requests.iter() {
59         let mark = if is_last { "*" } else { " " };
60         format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis());
61     }
62     Ok(buf)
63 }
64
65 pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<String> {
66     let _p = profile("handle_memory_usage");
67     let mem = state.analysis_host.per_query_memory_usage();
68
69     let mut out = String::new();
70     for (name, bytes) in mem {
71         format_to!(out, "{:>8} {}\n", bytes, name);
72     }
73     Ok(out)
74 }
75
76 pub(crate) fn handle_syntax_tree(
77     snap: GlobalStateSnapshot,
78     params: lsp_ext::SyntaxTreeParams,
79 ) -> Result<String> {
80     let _p = profile("handle_syntax_tree");
81     let id = from_proto::file_id(&snap, &params.text_document.uri)?;
82     let line_index = snap.analysis.file_line_index(id)?;
83     let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
84     let res = snap.analysis.syntax_tree(id, text_range)?;
85     Ok(res)
86 }
87
88 pub(crate) fn handle_expand_macro(
89     snap: GlobalStateSnapshot,
90     params: lsp_ext::ExpandMacroParams,
91 ) -> Result<Option<lsp_ext::ExpandedMacro>> {
92     let _p = profile("handle_expand_macro");
93     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
94     let line_index = snap.analysis.file_line_index(file_id)?;
95     let offset = from_proto::offset(&line_index, params.position);
96
97     let res = snap.analysis.expand_macro(FilePosition { file_id, offset })?;
98     Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
99 }
100
101 pub(crate) fn handle_selection_range(
102     snap: GlobalStateSnapshot,
103     params: lsp_types::SelectionRangeParams,
104 ) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
105     let _p = profile("handle_selection_range");
106     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
107     let line_index = snap.analysis.file_line_index(file_id)?;
108     let res: Result<Vec<lsp_types::SelectionRange>> = params
109         .positions
110         .into_iter()
111         .map(|position| {
112             let offset = from_proto::offset(&line_index, position);
113             let mut ranges = Vec::new();
114             {
115                 let mut range = TextRange::new(offset, offset);
116                 loop {
117                     ranges.push(range);
118                     let frange = FileRange { file_id, range };
119                     let next = snap.analysis.extend_selection(frange)?;
120                     if next == range {
121                         break;
122                     } else {
123                         range = next
124                     }
125                 }
126             }
127             let mut range = lsp_types::SelectionRange {
128                 range: to_proto::range(&line_index, *ranges.last().unwrap()),
129                 parent: None,
130             };
131             for &r in ranges.iter().rev().skip(1) {
132                 range = lsp_types::SelectionRange {
133                     range: to_proto::range(&line_index, r),
134                     parent: Some(Box::new(range)),
135                 }
136             }
137             Ok(range)
138         })
139         .collect();
140
141     Ok(Some(res?))
142 }
143
144 pub(crate) fn handle_matching_brace(
145     snap: GlobalStateSnapshot,
146     params: lsp_ext::MatchingBraceParams,
147 ) -> Result<Vec<Position>> {
148     let _p = profile("handle_matching_brace");
149     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
150     let line_index = snap.analysis.file_line_index(file_id)?;
151     let res = params
152         .positions
153         .into_iter()
154         .map(|position| {
155             let offset = from_proto::offset(&line_index, position);
156             let offset = match snap.analysis.matching_brace(FilePosition { file_id, offset }) {
157                 Ok(Some(matching_brace_offset)) => matching_brace_offset,
158                 Err(_) | Ok(None) => offset,
159             };
160             to_proto::position(&line_index, offset)
161         })
162         .collect();
163     Ok(res)
164 }
165
166 pub(crate) fn handle_join_lines(
167     snap: GlobalStateSnapshot,
168     params: lsp_ext::JoinLinesParams,
169 ) -> Result<Vec<lsp_types::TextEdit>> {
170     let _p = profile("handle_join_lines");
171     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
172     let line_index = snap.analysis.file_line_index(file_id)?;
173     let line_endings = snap.file_line_endings(file_id);
174     let mut res = TextEdit::default();
175     for range in params.ranges {
176         let range = from_proto::text_range(&line_index, range);
177         let edit = snap.analysis.join_lines(FileRange { file_id, range })?;
178         match res.union(edit) {
179             Ok(()) => (),
180             Err(_edit) => {
181                 // just ignore overlapping edits
182             }
183         }
184     }
185     let res = to_proto::text_edit_vec(&line_index, line_endings, res);
186     Ok(res)
187 }
188
189 pub(crate) fn handle_on_enter(
190     snap: GlobalStateSnapshot,
191     params: lsp_types::TextDocumentPositionParams,
192 ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
193     let _p = profile("handle_on_enter");
194     let position = from_proto::file_position(&snap, params)?;
195     let edit = match snap.analysis.on_enter(position)? {
196         None => return Ok(None),
197         Some(it) => it,
198     };
199     let line_index = snap.analysis.file_line_index(position.file_id)?;
200     let line_endings = snap.file_line_endings(position.file_id);
201     let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
202     Ok(Some(edit))
203 }
204
205 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
206 pub(crate) fn handle_on_type_formatting(
207     snap: GlobalStateSnapshot,
208     params: lsp_types::DocumentOnTypeFormattingParams,
209 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
210     let _p = profile("handle_on_type_formatting");
211     let mut position = from_proto::file_position(&snap, params.text_document_position)?;
212     let line_index = snap.analysis.file_line_index(position.file_id)?;
213     let line_endings = snap.file_line_endings(position.file_id);
214
215     // in `ra_ide`, the `on_type` invariant is that
216     // `text.char_at(position) == typed_char`.
217     position.offset -= TextSize::of('.');
218     let char_typed = params.ch.chars().next().unwrap_or('\0');
219     assert!({
220         let text = snap.analysis.file_text(position.file_id)?;
221         text[usize::from(position.offset)..].starts_with(char_typed)
222     });
223
224     // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
225     // but it requires precise cursor positioning to work, and one can't
226     // position the cursor with on_type formatting. So, let's just toggle this
227     // feature off here, hoping that we'll enable it one day, ðŸ˜¿.
228     if char_typed == '>' {
229         return Ok(None);
230     }
231
232     let edit = snap.analysis.on_char_typed(position, char_typed)?;
233     let mut edit = match edit {
234         Some(it) => it,
235         None => return Ok(None),
236     };
237
238     // This should be a single-file edit
239     let edit = edit.source_file_edits.pop().unwrap();
240
241     let change = to_proto::text_edit_vec(&line_index, line_endings, edit.edit);
242     Ok(Some(change))
243 }
244
245 pub(crate) fn handle_document_symbol(
246     snap: GlobalStateSnapshot,
247     params: lsp_types::DocumentSymbolParams,
248 ) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
249     let _p = profile("handle_document_symbol");
250     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
251     let line_index = snap.analysis.file_line_index(file_id)?;
252
253     let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
254
255     for symbol in snap.analysis.file_structure(file_id)? {
256         let doc_symbol = DocumentSymbol {
257             name: symbol.label,
258             detail: symbol.detail,
259             kind: to_proto::symbol_kind(symbol.kind),
260             deprecated: Some(symbol.deprecated),
261             range: to_proto::range(&line_index, symbol.node_range),
262             selection_range: to_proto::range(&line_index, symbol.navigation_range),
263             children: None,
264         };
265         parents.push((doc_symbol, symbol.parent));
266     }
267     let mut document_symbols = Vec::new();
268     while let Some((node, parent)) = parents.pop() {
269         match parent {
270             None => document_symbols.push(node),
271             Some(i) => {
272                 let children = &mut parents[i].0.children;
273                 if children.is_none() {
274                     *children = Some(Vec::new());
275                 }
276                 children.as_mut().unwrap().push(node);
277             }
278         }
279     }
280
281     let res = if snap.config.client_caps.hierarchical_symbols {
282         document_symbols.into()
283     } else {
284         let url = to_proto::url(&snap, file_id);
285         let mut symbol_information = Vec::<SymbolInformation>::new();
286         for symbol in document_symbols {
287             flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
288         }
289         symbol_information.into()
290     };
291     return Ok(Some(res));
292
293     fn flatten_document_symbol(
294         symbol: &DocumentSymbol,
295         container_name: Option<String>,
296         url: &Url,
297         res: &mut Vec<SymbolInformation>,
298     ) {
299         res.push(SymbolInformation {
300             name: symbol.name.clone(),
301             kind: symbol.kind,
302             deprecated: symbol.deprecated,
303             location: Location::new(url.clone(), symbol.range),
304             container_name,
305         });
306
307         for child in symbol.children.iter().flatten() {
308             flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
309         }
310     }
311 }
312
313 pub(crate) fn handle_workspace_symbol(
314     snap: GlobalStateSnapshot,
315     params: lsp_types::WorkspaceSymbolParams,
316 ) -> Result<Option<Vec<SymbolInformation>>> {
317     let _p = profile("handle_workspace_symbol");
318     let all_symbols = params.query.contains('#');
319     let libs = params.query.contains('*');
320     let query = {
321         let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
322         let mut q = Query::new(query);
323         if !all_symbols {
324             q.only_types();
325         }
326         if libs {
327             q.libs();
328         }
329         q.limit(128);
330         q
331     };
332     let mut res = exec_query(&snap, query)?;
333     if res.is_empty() && !all_symbols {
334         let mut query = Query::new(params.query);
335         query.limit(128);
336         res = exec_query(&snap, query)?;
337     }
338
339     return Ok(Some(res));
340
341     fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
342         let mut res = Vec::new();
343         for nav in snap.analysis.symbol_search(query)? {
344             let container_name = nav.container_name().map(|v| v.to_string());
345             let info = SymbolInformation {
346                 name: nav.name().to_string(),
347                 kind: to_proto::symbol_kind(nav.kind()),
348                 location: to_proto::location_from_nav(snap, nav)?,
349                 container_name,
350                 deprecated: None,
351             };
352             res.push(info);
353         }
354         Ok(res)
355     }
356 }
357
358 pub(crate) fn handle_goto_definition(
359     snap: GlobalStateSnapshot,
360     params: lsp_types::GotoDefinitionParams,
361 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
362     let _p = profile("handle_goto_definition");
363     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
364     let nav_info = match snap.analysis.goto_definition(position)? {
365         None => return Ok(None),
366         Some(it) => it,
367     };
368     let src = FileRange { file_id: position.file_id, range: nav_info.range };
369     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
370     Ok(Some(res))
371 }
372
373 pub(crate) fn handle_goto_implementation(
374     snap: GlobalStateSnapshot,
375     params: lsp_types::request::GotoImplementationParams,
376 ) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
377     let _p = profile("handle_goto_implementation");
378     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
379     let nav_info = match snap.analysis.goto_implementation(position)? {
380         None => return Ok(None),
381         Some(it) => it,
382     };
383     let src = FileRange { file_id: position.file_id, range: nav_info.range };
384     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
385     Ok(Some(res))
386 }
387
388 pub(crate) fn handle_goto_type_definition(
389     snap: GlobalStateSnapshot,
390     params: lsp_types::request::GotoTypeDefinitionParams,
391 ) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
392     let _p = profile("handle_goto_type_definition");
393     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
394     let nav_info = match snap.analysis.goto_type_definition(position)? {
395         None => return Ok(None),
396         Some(it) => it,
397     };
398     let src = FileRange { file_id: position.file_id, range: nav_info.range };
399     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
400     Ok(Some(res))
401 }
402
403 pub(crate) fn handle_parent_module(
404     snap: GlobalStateSnapshot,
405     params: lsp_types::TextDocumentPositionParams,
406 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
407     let _p = profile("handle_parent_module");
408     let position = from_proto::file_position(&snap, params)?;
409     let navs = snap.analysis.parent_module(position)?;
410     let res = to_proto::goto_definition_response(&snap, None, navs)?;
411     Ok(Some(res))
412 }
413
414 pub(crate) fn handle_runnables(
415     snap: GlobalStateSnapshot,
416     params: lsp_ext::RunnablesParams,
417 ) -> Result<Vec<lsp_ext::Runnable>> {
418     let _p = profile("handle_runnables");
419     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
420     let line_index = snap.analysis.file_line_index(file_id)?;
421     let offset = params.position.map(|it| from_proto::offset(&line_index, it));
422     let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
423
424     let expect_test = match offset {
425         Some(offset) => {
426             let source_file = snap.analysis.parse(file_id)?;
427             algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset)
428                 .and_then(|it| it.path()?.segment()?.name_ref())
429                 .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file")
430         }
431         None => false,
432     };
433
434     let mut res = Vec::new();
435     for runnable in snap.analysis.runnables(file_id)? {
436         if let Some(offset) = offset {
437             if !runnable.nav.full_range().contains_inclusive(offset) {
438                 continue;
439             }
440         }
441         if should_skip_target(&runnable, cargo_spec.as_ref()) {
442             continue;
443         }
444         let mut runnable = to_proto::runnable(&snap, file_id, runnable)?;
445         if expect_test {
446             runnable.label = format!("{} + expect", runnable.label);
447             runnable.args.expect_test = Some(true);
448         }
449         res.push(runnable);
450     }
451
452     // Add `cargo check` and `cargo test` for the whole package
453     match cargo_spec {
454         Some(spec) => {
455             for &cmd in ["check", "test"].iter() {
456                 res.push(lsp_ext::Runnable {
457                     label: format!("cargo {} -p {}", cmd, spec.package),
458                     location: None,
459                     kind: lsp_ext::RunnableKind::Cargo,
460                     args: lsp_ext::CargoRunnable {
461                         workspace_root: Some(spec.workspace_root.clone().into()),
462                         cargo_args: vec![
463                             cmd.to_string(),
464                             "--package".to_string(),
465                             spec.package.clone(),
466                         ],
467                         executable_args: Vec::new(),
468                         expect_test: None,
469                     },
470                 })
471             }
472         }
473         None => {
474             res.push(lsp_ext::Runnable {
475                 label: "cargo check --workspace".to_string(),
476                 location: None,
477                 kind: lsp_ext::RunnableKind::Cargo,
478                 args: lsp_ext::CargoRunnable {
479                     workspace_root: None,
480                     cargo_args: vec!["check".to_string(), "--workspace".to_string()],
481                     executable_args: Vec::new(),
482                     expect_test: None,
483                 },
484             });
485         }
486     }
487     Ok(res)
488 }
489
490 pub(crate) fn handle_completion(
491     snap: GlobalStateSnapshot,
492     params: lsp_types::CompletionParams,
493 ) -> Result<Option<lsp_types::CompletionResponse>> {
494     let _p = profile("handle_completion");
495     let position = from_proto::file_position(&snap, params.text_document_position)?;
496     let completion_triggered_after_single_colon = {
497         let mut res = false;
498         if let Some(ctx) = params.context {
499             if ctx.trigger_character.unwrap_or_default() == ":" {
500                 let source_file = snap.analysis.parse(position.file_id)?;
501                 let syntax = source_file.syntax();
502                 let text = syntax.text();
503                 if let Some(next_char) = text.char_at(position.offset) {
504                     let diff = TextSize::of(next_char) + TextSize::of(':');
505                     let prev_char = position.offset - diff;
506                     if text.char_at(prev_char) != Some(':') {
507                         res = true;
508                     }
509                 }
510             }
511         }
512         res
513     };
514     if completion_triggered_after_single_colon {
515         return Ok(None);
516     }
517
518     let items = match snap.analysis.completions(&snap.config.completion, position)? {
519         None => return Ok(None),
520         Some(items) => items,
521     };
522     let line_index = snap.analysis.file_line_index(position.file_id)?;
523     let line_endings = snap.file_line_endings(position.file_id);
524     let items: Vec<CompletionItem> = items
525         .into_iter()
526         .map(|item| to_proto::completion_item(&line_index, line_endings, item))
527         .collect();
528
529     Ok(Some(items.into()))
530 }
531
532 pub(crate) fn handle_folding_range(
533     snap: GlobalStateSnapshot,
534     params: FoldingRangeParams,
535 ) -> Result<Option<Vec<FoldingRange>>> {
536     let _p = profile("handle_folding_range");
537     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
538     let folds = snap.analysis.folding_ranges(file_id)?;
539     let text = snap.analysis.file_text(file_id)?;
540     let line_index = snap.analysis.file_line_index(file_id)?;
541     let line_folding_only = snap.config.client_caps.line_folding_only;
542     let res = folds
543         .into_iter()
544         .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
545         .collect();
546     Ok(Some(res))
547 }
548
549 pub(crate) fn handle_signature_help(
550     snap: GlobalStateSnapshot,
551     params: lsp_types::SignatureHelpParams,
552 ) -> Result<Option<lsp_types::SignatureHelp>> {
553     let _p = profile("handle_signature_help");
554     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
555     let call_info = match snap.analysis.call_info(position)? {
556         None => return Ok(None),
557         Some(it) => it,
558     };
559     let concise = !snap.config.call_info_full;
560     let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
561     if concise && call_info.signature.has_self_param {
562         active_parameter = active_parameter.map(|it| it.saturating_sub(1));
563     }
564     let sig_info = to_proto::signature_information(call_info.signature, concise);
565
566     Ok(Some(lsp_types::SignatureHelp {
567         signatures: vec![sig_info],
568         active_signature: Some(0),
569         active_parameter,
570     }))
571 }
572
573 pub(crate) fn handle_hover(
574     snap: GlobalStateSnapshot,
575     params: lsp_types::HoverParams,
576 ) -> Result<Option<lsp_ext::Hover>> {
577     let _p = profile("handle_hover");
578     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
579     let info = match snap.analysis.hover(position)? {
580         None => return Ok(None),
581         Some(info) => info,
582     };
583     let line_index = snap.analysis.file_line_index(position.file_id)?;
584     let range = to_proto::range(&line_index, info.range);
585     let hover = lsp_ext::Hover {
586         hover: lsp_types::Hover {
587             contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)),
588             range: Some(range),
589         },
590         actions: prepare_hover_actions(&snap, position.file_id, &info.info.actions),
591     };
592
593     Ok(Some(hover))
594 }
595
596 pub(crate) fn handle_prepare_rename(
597     snap: GlobalStateSnapshot,
598     params: lsp_types::TextDocumentPositionParams,
599 ) -> Result<Option<PrepareRenameResponse>> {
600     let _p = profile("handle_prepare_rename");
601     let position = from_proto::file_position(&snap, params)?;
602
603     let optional_change = snap.analysis.rename(position, "dummy")?;
604     let range = match optional_change {
605         None => return Ok(None),
606         Some(it) => it.range,
607     };
608
609     let line_index = snap.analysis.file_line_index(position.file_id)?;
610     let range = to_proto::range(&line_index, range);
611     Ok(Some(PrepareRenameResponse::Range(range)))
612 }
613
614 pub(crate) fn handle_rename(
615     snap: GlobalStateSnapshot,
616     params: RenameParams,
617 ) -> Result<Option<WorkspaceEdit>> {
618     let _p = profile("handle_rename");
619     let position = from_proto::file_position(&snap, params.text_document_position)?;
620
621     if params.new_name.is_empty() {
622         return Err(LspError::new(
623             ErrorCode::InvalidParams as i32,
624             "New Name cannot be empty".into(),
625         )
626         .into());
627     }
628
629     let optional_change = snap.analysis.rename(position, &*params.new_name)?;
630     let source_change = match optional_change {
631         None => return Ok(None),
632         Some(it) => it.info,
633     };
634     let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
635     Ok(Some(workspace_edit))
636 }
637
638 pub(crate) fn handle_references(
639     snap: GlobalStateSnapshot,
640     params: lsp_types::ReferenceParams,
641 ) -> Result<Option<Vec<Location>>> {
642     let _p = profile("handle_references");
643     let position = from_proto::file_position(&snap, params.text_document_position)?;
644
645     let refs = match snap.analysis.find_all_refs(position, None)? {
646         None => return Ok(None),
647         Some(refs) => refs,
648     };
649
650     let locations = if params.context.include_declaration {
651         refs.into_iter()
652             .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
653             .collect()
654     } else {
655         // Only iterate over the references if include_declaration was false
656         refs.references()
657             .iter()
658             .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
659             .collect()
660     };
661
662     Ok(Some(locations))
663 }
664
665 pub(crate) fn handle_formatting(
666     snap: GlobalStateSnapshot,
667     params: DocumentFormattingParams,
668 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
669     let _p = profile("handle_formatting");
670     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
671     let file = snap.analysis.file_text(file_id)?;
672     let crate_ids = snap.analysis.crate_for(file_id)?;
673
674     let file_line_index = snap.analysis.file_line_index(file_id)?;
675     let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
676
677     let mut rustfmt = match &snap.config.rustfmt {
678         RustfmtConfig::Rustfmt { extra_args } => {
679             let mut cmd = process::Command::new(ra_toolchain::rustfmt());
680             cmd.args(extra_args);
681             if let Some(&crate_id) = crate_ids.first() {
682                 // Assume all crates are in the same edition
683                 let edition = snap.analysis.crate_edition(crate_id)?;
684                 cmd.arg("--edition");
685                 cmd.arg(edition.to_string());
686             }
687             cmd
688         }
689         RustfmtConfig::CustomCommand { command, args } => {
690             let mut cmd = process::Command::new(command);
691             cmd.args(args);
692             cmd
693         }
694     };
695
696     if let Ok(path) = params.text_document.uri.to_file_path() {
697         if let Some(parent) = path.parent() {
698             rustfmt.current_dir(parent);
699         }
700     }
701     let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
702
703     rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
704
705     let output = rustfmt.wait_with_output()?;
706     let captured_stdout = String::from_utf8(output.stdout)?;
707
708     if !output.status.success() {
709         match output.status.code() {
710             Some(1) => {
711                 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
712                 // likely cause exiting with 1. Most Language Servers swallow parse errors on
713                 // formatting because otherwise an error is surfaced to the user on top of the
714                 // syntax error diagnostics they're already receiving. This is especially jarring
715                 // if they have format on save enabled.
716                 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
717                 return Ok(None);
718             }
719             _ => {
720                 // Something else happened - e.g. `rustfmt` is missing or caught a signal
721                 return Err(LspError::new(
722                     -32900,
723                     format!(
724                         r#"rustfmt exited with:
725                            Status: {}
726                            stdout: {}"#,
727                         output.status, captured_stdout,
728                     ),
729                 )
730                 .into());
731             }
732         }
733     }
734
735     Ok(Some(vec![lsp_types::TextEdit {
736         range: Range::new(Position::new(0, 0), end_position),
737         new_text: captured_stdout,
738     }]))
739 }
740
741 fn handle_fixes(
742     snap: &GlobalStateSnapshot,
743     params: &lsp_types::CodeActionParams,
744     res: &mut Vec<lsp_ext::CodeAction>,
745 ) -> Result<()> {
746     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
747     let line_index = snap.analysis.file_line_index(file_id)?;
748     let range = from_proto::text_range(&line_index, params.range);
749     let diagnostics = snap.analysis.diagnostics(file_id)?;
750
751     let fixes_from_diagnostics = diagnostics
752         .into_iter()
753         .filter_map(|d| Some((d.range, d.fix?)))
754         .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
755         .map(|(_range, fix)| fix);
756     for fix in fixes_from_diagnostics {
757         let title = fix.label;
758         let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
759         let action = lsp_ext::CodeAction {
760             title,
761             id: None,
762             group: None,
763             kind: Some(lsp_types::code_action_kind::QUICKFIX.into()),
764             edit: Some(edit),
765         };
766         res.push(action);
767     }
768
769     for fix in snap.check_fixes.get(&file_id).into_iter().flatten() {
770         let fix_range = from_proto::text_range(&line_index, fix.range);
771         if fix_range.intersect(range).is_none() {
772             continue;
773         }
774         res.push(fix.action.clone());
775     }
776     Ok(())
777 }
778
779 pub(crate) fn handle_code_action(
780     snap: GlobalStateSnapshot,
781     params: lsp_types::CodeActionParams,
782 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
783     let _p = profile("handle_code_action");
784     // We intentionally don't support command-based actions, as those either
785     // requires custom client-code anyway, or requires server-initiated edits.
786     // Server initiated edits break causality, so we avoid those as well.
787     if !snap.config.client_caps.code_action_literals {
788         return Ok(None);
789     }
790
791     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
792     let line_index = snap.analysis.file_line_index(file_id)?;
793     let range = from_proto::text_range(&line_index, params.range);
794     let frange = FileRange { file_id, range };
795     let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
796
797     handle_fixes(&snap, &params, &mut res)?;
798
799     if snap.config.client_caps.resolve_code_action {
800         for (index, assist) in
801             snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
802         {
803             res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
804         }
805     } else {
806         for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() {
807             res.push(to_proto::resolved_code_action(&snap, assist)?);
808         }
809     }
810
811     Ok(Some(res))
812 }
813
814 pub(crate) fn handle_resolve_code_action(
815     snap: GlobalStateSnapshot,
816     params: lsp_ext::ResolveCodeActionParams,
817 ) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
818     let _p = profile("handle_resolve_code_action");
819     let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
820     let line_index = snap.analysis.file_line_index(file_id)?;
821     let range = from_proto::text_range(&line_index, params.code_action_params.range);
822     let frange = FileRange { file_id, range };
823
824     let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?;
825     let (id_string, index) = split_delim(&params.id, ':').unwrap();
826     let index = index.parse::<usize>().unwrap();
827     let assist = &assists[index];
828     assert!(assist.assist.id.0 == id_string);
829     Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
830 }
831
832 pub(crate) fn handle_code_lens(
833     snap: GlobalStateSnapshot,
834     params: lsp_types::CodeLensParams,
835 ) -> Result<Option<Vec<CodeLens>>> {
836     let _p = profile("handle_code_lens");
837     let mut lenses: Vec<CodeLens> = Default::default();
838
839     if snap.config.lens.none() {
840         // early return before any db query!
841         return Ok(Some(lenses));
842     }
843
844     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
845     let line_index = snap.analysis.file_line_index(file_id)?;
846     let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
847
848     if snap.config.lens.runnable() {
849         // Gather runnables
850         for runnable in snap.analysis.runnables(file_id)? {
851             if should_skip_target(&runnable, cargo_spec.as_ref()) {
852                 continue;
853             }
854
855             let action = runnable.action();
856             let range = to_proto::range(&line_index, runnable.nav.range());
857             let r = to_proto::runnable(&snap, file_id, runnable)?;
858             if snap.config.lens.run {
859                 let lens = CodeLens {
860                     range,
861                     command: Some(run_single_command(&r, action.run_title)),
862                     data: None,
863                 };
864                 lenses.push(lens);
865             }
866
867             if action.debugee && snap.config.lens.debug {
868                 let debug_lens =
869                     CodeLens { range, command: Some(debug_single_command(&r)), data: None };
870                 lenses.push(debug_lens);
871             }
872         }
873     }
874
875     if snap.config.lens.implementations {
876         // Handle impls
877         lenses.extend(
878             snap.analysis
879                 .file_structure(file_id)?
880                 .into_iter()
881                 .filter(|it| match it.kind {
882                     SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
883                     _ => false,
884                 })
885                 .map(|it| {
886                     let range = to_proto::range(&line_index, it.node_range);
887                     let pos = range.start;
888                     let lens_params = lsp_types::request::GotoImplementationParams {
889                         text_document_position_params: lsp_types::TextDocumentPositionParams::new(
890                             params.text_document.clone(),
891                             pos,
892                         ),
893                         work_done_progress_params: Default::default(),
894                         partial_result_params: Default::default(),
895                     };
896                     CodeLens {
897                         range,
898                         command: None,
899                         data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
900                     }
901                 }),
902         );
903     }
904     Ok(Some(lenses))
905 }
906
907 #[derive(Debug, Serialize, Deserialize)]
908 #[serde(rename_all = "camelCase")]
909 enum CodeLensResolveData {
910     Impls(lsp_types::request::GotoImplementationParams),
911 }
912
913 pub(crate) fn handle_code_lens_resolve(
914     snap: GlobalStateSnapshot,
915     code_lens: CodeLens,
916 ) -> Result<CodeLens> {
917     let _p = profile("handle_code_lens_resolve");
918     let data = code_lens.data.unwrap();
919     let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
920     match resolve {
921         Some(CodeLensResolveData::Impls(lens_params)) => {
922             let locations: Vec<Location> =
923                 match handle_goto_implementation(snap, lens_params.clone())? {
924                     Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
925                     Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
926                     Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
927                         .into_iter()
928                         .map(|link| Location::new(link.target_uri, link.target_selection_range))
929                         .collect(),
930                     _ => vec![],
931                 };
932
933             let title = implementation_title(locations.len());
934             let cmd = show_references_command(
935                 title,
936                 &lens_params.text_document_position_params.text_document.uri,
937                 code_lens.range.start,
938                 locations,
939             );
940             Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
941         }
942         None => Ok(CodeLens {
943             range: code_lens.range,
944             command: Some(Command { title: "Error".into(), ..Default::default() }),
945             data: None,
946         }),
947     }
948 }
949
950 pub(crate) fn handle_document_highlight(
951     snap: GlobalStateSnapshot,
952     params: lsp_types::DocumentHighlightParams,
953 ) -> Result<Option<Vec<DocumentHighlight>>> {
954     let _p = profile("handle_document_highlight");
955     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
956     let line_index = snap.analysis.file_line_index(position.file_id)?;
957
958     let refs = match snap
959         .analysis
960         .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
961     {
962         None => return Ok(None),
963         Some(refs) => refs,
964     };
965
966     let res = refs
967         .into_iter()
968         .filter(|reference| reference.file_range.file_id == position.file_id)
969         .map(|reference| DocumentHighlight {
970             range: to_proto::range(&line_index, reference.file_range.range),
971             kind: reference.access.map(to_proto::document_highlight_kind),
972         })
973         .collect();
974     Ok(Some(res))
975 }
976
977 pub(crate) fn handle_ssr(
978     snap: GlobalStateSnapshot,
979     params: lsp_ext::SsrParams,
980 ) -> Result<lsp_types::WorkspaceEdit> {
981     let _p = profile("handle_ssr");
982     let source_change =
983         snap.analysis.structural_search_replace(&params.query, params.parse_only)??;
984     to_proto::workspace_edit(&snap, source_change)
985 }
986
987 pub(crate) fn publish_diagnostics(
988     snap: &GlobalStateSnapshot,
989     file_id: FileId,
990 ) -> Result<Vec<Diagnostic>> {
991     let _p = profile("publish_diagnostics");
992     let line_index = snap.analysis.file_line_index(file_id)?;
993     let diagnostics: Vec<Diagnostic> = snap
994         .analysis
995         .diagnostics(file_id)?
996         .into_iter()
997         .map(|d| Diagnostic {
998             range: to_proto::range(&line_index, d.range),
999             severity: Some(to_proto::diagnostic_severity(d.severity)),
1000             code: None,
1001             source: Some("rust-analyzer".to_string()),
1002             message: d.message,
1003             related_information: None,
1004             tags: None,
1005         })
1006         .collect();
1007     Ok(diagnostics)
1008 }
1009
1010 pub(crate) fn handle_inlay_hints(
1011     snap: GlobalStateSnapshot,
1012     params: InlayHintsParams,
1013 ) -> Result<Vec<InlayHint>> {
1014     let _p = profile("handle_inlay_hints");
1015     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1016     let line_index = snap.analysis.file_line_index(file_id)?;
1017     Ok(snap
1018         .analysis
1019         .inlay_hints(file_id, &snap.config.inlay_hints)?
1020         .into_iter()
1021         .map(|it| to_proto::inlay_int(&line_index, it))
1022         .collect())
1023 }
1024
1025 pub(crate) fn handle_call_hierarchy_prepare(
1026     snap: GlobalStateSnapshot,
1027     params: CallHierarchyPrepareParams,
1028 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1029     let _p = profile("handle_call_hierarchy_prepare");
1030     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1031
1032     let nav_info = match snap.analysis.call_hierarchy(position)? {
1033         None => return Ok(None),
1034         Some(it) => it,
1035     };
1036
1037     let RangeInfo { range: _, info: navs } = nav_info;
1038     let res = navs
1039         .into_iter()
1040         .filter(|it| it.kind() == SyntaxKind::FN_DEF)
1041         .map(|it| to_proto::call_hierarchy_item(&snap, it))
1042         .collect::<Result<Vec<_>>>()?;
1043
1044     Ok(Some(res))
1045 }
1046
1047 pub(crate) fn handle_call_hierarchy_incoming(
1048     snap: GlobalStateSnapshot,
1049     params: CallHierarchyIncomingCallsParams,
1050 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1051     let _p = profile("handle_call_hierarchy_incoming");
1052     let item = params.item;
1053
1054     let doc = TextDocumentIdentifier::new(item.uri);
1055     let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
1056     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1057
1058     let call_items = match snap.analysis.incoming_calls(fpos)? {
1059         None => return Ok(None),
1060         Some(it) => it,
1061     };
1062
1063     let mut res = vec![];
1064
1065     for call_item in call_items.into_iter() {
1066         let file_id = call_item.target.file_id();
1067         let line_index = snap.analysis.file_line_index(file_id)?;
1068         let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1069         res.push(CallHierarchyIncomingCall {
1070             from: item,
1071             from_ranges: call_item
1072                 .ranges
1073                 .into_iter()
1074                 .map(|it| to_proto::range(&line_index, it))
1075                 .collect(),
1076         });
1077     }
1078
1079     Ok(Some(res))
1080 }
1081
1082 pub(crate) fn handle_call_hierarchy_outgoing(
1083     snap: GlobalStateSnapshot,
1084     params: CallHierarchyOutgoingCallsParams,
1085 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1086     let _p = profile("handle_call_hierarchy_outgoing");
1087     let item = params.item;
1088
1089     let doc = TextDocumentIdentifier::new(item.uri);
1090     let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
1091     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1092
1093     let call_items = match snap.analysis.outgoing_calls(fpos)? {
1094         None => return Ok(None),
1095         Some(it) => it,
1096     };
1097
1098     let mut res = vec![];
1099
1100     for call_item in call_items.into_iter() {
1101         let file_id = call_item.target.file_id();
1102         let line_index = snap.analysis.file_line_index(file_id)?;
1103         let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1104         res.push(CallHierarchyOutgoingCall {
1105             to: item,
1106             from_ranges: call_item
1107                 .ranges
1108                 .into_iter()
1109                 .map(|it| to_proto::range(&line_index, it))
1110                 .collect(),
1111         });
1112     }
1113
1114     Ok(Some(res))
1115 }
1116
1117 pub(crate) fn handle_semantic_tokens(
1118     snap: GlobalStateSnapshot,
1119     params: SemanticTokensParams,
1120 ) -> Result<Option<SemanticTokensResult>> {
1121     let _p = profile("handle_semantic_tokens");
1122
1123     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1124     let text = snap.analysis.file_text(file_id)?;
1125     let line_index = snap.analysis.file_line_index(file_id)?;
1126
1127     let highlights = snap.analysis.highlight(file_id)?;
1128     let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1129     Ok(Some(semantic_tokens.into()))
1130 }
1131
1132 pub(crate) fn handle_semantic_tokens_range(
1133     snap: GlobalStateSnapshot,
1134     params: SemanticTokensRangeParams,
1135 ) -> Result<Option<SemanticTokensRangeResult>> {
1136     let _p = profile("handle_semantic_tokens_range");
1137
1138     let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1139     let text = snap.analysis.file_text(frange.file_id)?;
1140     let line_index = snap.analysis.file_line_index(frange.file_id)?;
1141
1142     let highlights = snap.analysis.highlight_range(frange)?;
1143     let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1144     Ok(Some(semantic_tokens.into()))
1145 }
1146
1147 fn implementation_title(count: usize) -> String {
1148     if count == 1 {
1149         "1 implementation".into()
1150     } else {
1151         format!("{} implementations", count)
1152     }
1153 }
1154
1155 fn show_references_command(
1156     title: String,
1157     uri: &lsp_types::Url,
1158     position: lsp_types::Position,
1159     locations: Vec<lsp_types::Location>,
1160 ) -> Command {
1161     // We cannot use the 'editor.action.showReferences' command directly
1162     // because that command requires vscode types which we convert in the handler
1163     // on the client side.
1164
1165     Command {
1166         title,
1167         command: "rust-analyzer.showReferences".into(),
1168         arguments: Some(vec![
1169             to_value(uri).unwrap(),
1170             to_value(position).unwrap(),
1171             to_value(locations).unwrap(),
1172         ]),
1173     }
1174 }
1175
1176 fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command {
1177     Command {
1178         title: title.to_string(),
1179         command: "rust-analyzer.runSingle".into(),
1180         arguments: Some(vec![to_value(runnable).unwrap()]),
1181     }
1182 }
1183
1184 fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command {
1185     Command {
1186         title: "Debug".into(),
1187         command: "rust-analyzer.debugSingle".into(),
1188         arguments: Some(vec![to_value(runnable).unwrap()]),
1189     }
1190 }
1191
1192 fn goto_location_command(snap: &GlobalStateSnapshot, nav: &NavigationTarget) -> Option<Command> {
1193     let value = if snap.config.client_caps.location_link {
1194         let link = to_proto::location_link(snap, None, nav.clone()).ok()?;
1195         to_value(link).ok()?
1196     } else {
1197         let range = FileRange { file_id: nav.file_id(), range: nav.range() };
1198         let location = to_proto::location(snap, range).ok()?;
1199         to_value(location).ok()?
1200     };
1201
1202     Some(Command {
1203         title: nav.name().to_string(),
1204         command: "rust-analyzer.gotoLocation".into(),
1205         arguments: Some(vec![value]),
1206     })
1207 }
1208
1209 fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
1210     lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1211 }
1212
1213 fn show_impl_command_link(
1214     snap: &GlobalStateSnapshot,
1215     position: &FilePosition,
1216 ) -> Option<lsp_ext::CommandLinkGroup> {
1217     if snap.config.hover.implementations {
1218         if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) {
1219             let uri = to_proto::url(snap, position.file_id);
1220             let line_index = snap.analysis.file_line_index(position.file_id).ok()?;
1221             let position = to_proto::position(&line_index, position.offset);
1222             let locations: Vec<_> = nav_data
1223                 .info
1224                 .into_iter()
1225                 .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok())
1226                 .collect();
1227             let title = implementation_title(locations.len());
1228             let command = show_references_command(title, &uri, position, locations);
1229
1230             return Some(lsp_ext::CommandLinkGroup {
1231                 commands: vec![to_command_link(command, "Go to implementations".into())],
1232                 ..Default::default()
1233             });
1234         }
1235     }
1236     None
1237 }
1238
1239 fn runnable_action_links(
1240     snap: &GlobalStateSnapshot,
1241     file_id: FileId,
1242     runnable: Runnable,
1243 ) -> Option<lsp_ext::CommandLinkGroup> {
1244     let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?;
1245     if !snap.config.hover.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) {
1246         return None;
1247     }
1248
1249     let action: &'static _ = runnable.action();
1250     to_proto::runnable(snap, file_id, runnable).ok().map(|r| {
1251         let mut group = lsp_ext::CommandLinkGroup::default();
1252
1253         if snap.config.hover.run {
1254             let run_command = run_single_command(&r, action.run_title);
1255             group.commands.push(to_command_link(run_command, r.label.clone()));
1256         }
1257
1258         if snap.config.hover.debug {
1259             let dbg_command = debug_single_command(&r);
1260             group.commands.push(to_command_link(dbg_command, r.label));
1261         }
1262
1263         group
1264     })
1265 }
1266
1267 fn goto_type_action_links(
1268     snap: &GlobalStateSnapshot,
1269     nav_targets: &[HoverGotoTypeData],
1270 ) -> Option<lsp_ext::CommandLinkGroup> {
1271     if !snap.config.hover.goto_type_def || nav_targets.is_empty() {
1272         return None;
1273     }
1274
1275     Some(lsp_ext::CommandLinkGroup {
1276         title: Some("Go to ".into()),
1277         commands: nav_targets
1278             .iter()
1279             .filter_map(|it| {
1280                 goto_location_command(snap, &it.nav)
1281                     .map(|cmd| to_command_link(cmd, it.mod_path.clone()))
1282             })
1283             .collect(),
1284     })
1285 }
1286
1287 fn prepare_hover_actions(
1288     snap: &GlobalStateSnapshot,
1289     file_id: FileId,
1290     actions: &[HoverAction],
1291 ) -> Vec<lsp_ext::CommandLinkGroup> {
1292     if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
1293         return Vec::new();
1294     }
1295
1296     actions
1297         .iter()
1298         .filter_map(|it| match it {
1299             HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
1300             HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()),
1301             HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
1302         })
1303         .collect()
1304 }
1305
1306 fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool {
1307     match runnable.kind {
1308         RunnableKind::Bin => {
1309             // Do not suggest binary run on other target than binary
1310             match &cargo_spec {
1311                 Some(spec) => !matches!(spec.target_kind, TargetKind::Bin | TargetKind::Example),
1312                 None => true,
1313             }
1314         }
1315         _ => false,
1316     }
1317 }