]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/handlers.rs
Merge #11115
[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 //! `ide` crate.
4
5 use std::{
6     io::Write as _,
7     process::{self, Stdio},
8 };
9
10 use anyhow::Context;
11 use ide::{
12     AnnotationConfig, AssistKind, AssistResolveStrategy, FileId, FilePosition, FileRange,
13     HoverAction, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SingleResolve,
14     SourceChange, TextEdit,
15 };
16 use ide_db::SymbolKind;
17 use lsp_server::ErrorCode;
18 use lsp_types::{
19     CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
20     CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
21     CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange,
22     FoldingRangeParams, HoverContents, Location, LocationLink, NumberOrString, Position,
23     PrepareRenameResponse, Range, RenameParams, SemanticTokensDeltaParams,
24     SemanticTokensFullDeltaResult, SemanticTokensParams, SemanticTokensRangeParams,
25     SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag,
26     TextDocumentIdentifier, Url, WorkspaceEdit,
27 };
28 use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
29 use serde_json::json;
30 use stdx::{format_to, never};
31 use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
32 use vfs::AbsPathBuf;
33
34 use crate::{
35     cargo_target_spec::CargoTargetSpec,
36     config::RustfmtConfig,
37     diff::diff,
38     from_proto,
39     global_state::{GlobalState, GlobalStateSnapshot},
40     line_index::LineEndings,
41     lsp_ext::{
42         self, InlayHint, InlayHintsParams, PositionOrRange, ViewCrateGraphParams,
43         WorkspaceSymbolParams,
44     },
45     lsp_utils::{all_edits_are_disjoint, invalid_params_error},
46     to_proto, LspError, Result,
47 };
48
49 pub(crate) fn handle_analyzer_status(
50     snap: GlobalStateSnapshot,
51     params: lsp_ext::AnalyzerStatusParams,
52 ) -> Result<String> {
53     let _p = profile::span("handle_analyzer_status");
54
55     let mut buf = String::new();
56
57     let mut file_id = None;
58     if let Some(tdi) = params.text_document {
59         match from_proto::file_id(&snap, &tdi.uri) {
60             Ok(it) => file_id = Some(it),
61             Err(_) => format_to!(buf, "file {} not found in vfs", tdi.uri),
62         }
63     }
64
65     if snap.workspaces.is_empty() {
66         buf.push_str("No workspaces\n")
67     } else {
68         buf.push_str("Workspaces:\n");
69         format_to!(
70             buf,
71             "Loaded {:?} packages across {} workspace{}.\n",
72             snap.workspaces.iter().map(|w| w.n_packages()).sum::<usize>(),
73             snap.workspaces.len(),
74             if snap.workspaces.len() == 1 { "" } else { "s" }
75         );
76     }
77     buf.push_str("\nAnalysis:\n");
78     buf.push_str(
79         &snap
80             .analysis
81             .status(file_id)
82             .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
83     );
84     Ok(buf)
85 }
86
87 pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<String> {
88     let _p = profile::span("handle_memory_usage");
89     let mut mem = state.analysis_host.per_query_memory_usage();
90     mem.push(("Remaining".into(), profile::memory_usage().allocated));
91
92     let mut out = String::new();
93     for (name, bytes) in mem {
94         format_to!(out, "{:>8} {}\n", bytes, name);
95     }
96     Ok(out)
97 }
98
99 pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Result<()> {
100     state.analysis_host.shuffle_crate_graph();
101     Ok(())
102 }
103
104 pub(crate) fn handle_syntax_tree(
105     snap: GlobalStateSnapshot,
106     params: lsp_ext::SyntaxTreeParams,
107 ) -> Result<String> {
108     let _p = profile::span("handle_syntax_tree");
109     let id = from_proto::file_id(&snap, &params.text_document.uri)?;
110     let line_index = snap.file_line_index(id)?;
111     let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
112     let res = snap.analysis.syntax_tree(id, text_range)?;
113     Ok(res)
114 }
115
116 pub(crate) fn handle_view_hir(
117     snap: GlobalStateSnapshot,
118     params: lsp_types::TextDocumentPositionParams,
119 ) -> Result<String> {
120     let _p = profile::span("handle_view_hir");
121     let position = from_proto::file_position(&snap, params)?;
122     let res = snap.analysis.view_hir(position)?;
123     Ok(res)
124 }
125
126 pub(crate) fn handle_view_item_tree(
127     snap: GlobalStateSnapshot,
128     params: lsp_ext::ViewItemTreeParams,
129 ) -> Result<String> {
130     let _p = profile::span("handle_view_item_tree");
131     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
132     let res = snap.analysis.view_item_tree(file_id)?;
133     Ok(res)
134 }
135
136 pub(crate) fn handle_view_crate_graph(
137     snap: GlobalStateSnapshot,
138     params: ViewCrateGraphParams,
139 ) -> Result<String> {
140     let _p = profile::span("handle_view_crate_graph");
141     let dot = snap.analysis.view_crate_graph(params.full)??;
142     Ok(dot)
143 }
144
145 pub(crate) fn handle_expand_macro(
146     snap: GlobalStateSnapshot,
147     params: lsp_ext::ExpandMacroParams,
148 ) -> Result<Option<lsp_ext::ExpandedMacro>> {
149     let _p = profile::span("handle_expand_macro");
150     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
151     let line_index = snap.file_line_index(file_id)?;
152     let offset = from_proto::offset(&line_index, params.position);
153
154     let res = snap.analysis.expand_macro(FilePosition { file_id, offset })?;
155     Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
156 }
157
158 pub(crate) fn handle_selection_range(
159     snap: GlobalStateSnapshot,
160     params: lsp_types::SelectionRangeParams,
161 ) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
162     let _p = profile::span("handle_selection_range");
163     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
164     let line_index = snap.file_line_index(file_id)?;
165     let res: Result<Vec<lsp_types::SelectionRange>> = params
166         .positions
167         .into_iter()
168         .map(|position| {
169             let offset = from_proto::offset(&line_index, position);
170             let mut ranges = Vec::new();
171             {
172                 let mut range = TextRange::new(offset, offset);
173                 loop {
174                     ranges.push(range);
175                     let frange = FileRange { file_id, range };
176                     let next = snap.analysis.extend_selection(frange)?;
177                     if next == range {
178                         break;
179                     } else {
180                         range = next
181                     }
182                 }
183             }
184             let mut range = lsp_types::SelectionRange {
185                 range: to_proto::range(&line_index, *ranges.last().unwrap()),
186                 parent: None,
187             };
188             for &r in ranges.iter().rev().skip(1) {
189                 range = lsp_types::SelectionRange {
190                     range: to_proto::range(&line_index, r),
191                     parent: Some(Box::new(range)),
192                 }
193             }
194             Ok(range)
195         })
196         .collect();
197
198     Ok(Some(res?))
199 }
200
201 pub(crate) fn handle_matching_brace(
202     snap: GlobalStateSnapshot,
203     params: lsp_ext::MatchingBraceParams,
204 ) -> Result<Vec<Position>> {
205     let _p = profile::span("handle_matching_brace");
206     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
207     let line_index = snap.file_line_index(file_id)?;
208     let res = params
209         .positions
210         .into_iter()
211         .map(|position| {
212             let offset = from_proto::offset(&line_index, position);
213             let offset = match snap.analysis.matching_brace(FilePosition { file_id, offset }) {
214                 Ok(Some(matching_brace_offset)) => matching_brace_offset,
215                 Err(_) | Ok(None) => offset,
216             };
217             to_proto::position(&line_index, offset)
218         })
219         .collect();
220     Ok(res)
221 }
222
223 pub(crate) fn handle_join_lines(
224     snap: GlobalStateSnapshot,
225     params: lsp_ext::JoinLinesParams,
226 ) -> Result<Vec<lsp_types::TextEdit>> {
227     let _p = profile::span("handle_join_lines");
228
229     let config = snap.config.join_lines();
230     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
231     let line_index = snap.file_line_index(file_id)?;
232
233     let mut res = TextEdit::default();
234     for range in params.ranges {
235         let range = from_proto::text_range(&line_index, range);
236         let edit = snap.analysis.join_lines(&config, FileRange { file_id, range })?;
237         match res.union(edit) {
238             Ok(()) => (),
239             Err(_edit) => {
240                 // just ignore overlapping edits
241             }
242         }
243     }
244
245     Ok(to_proto::text_edit_vec(&line_index, res))
246 }
247
248 pub(crate) fn handle_on_enter(
249     snap: GlobalStateSnapshot,
250     params: lsp_types::TextDocumentPositionParams,
251 ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
252     let _p = profile::span("handle_on_enter");
253     let position = from_proto::file_position(&snap, params)?;
254     let edit = match snap.analysis.on_enter(position)? {
255         None => return Ok(None),
256         Some(it) => it,
257     };
258     let line_index = snap.file_line_index(position.file_id)?;
259     let edit = to_proto::snippet_text_edit_vec(&line_index, true, edit);
260     Ok(Some(edit))
261 }
262
263 pub(crate) fn handle_on_type_formatting(
264     snap: GlobalStateSnapshot,
265     params: lsp_types::DocumentOnTypeFormattingParams,
266 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
267     let _p = profile::span("handle_on_type_formatting");
268     let mut position = from_proto::file_position(&snap, params.text_document_position)?;
269     let line_index = snap.file_line_index(position.file_id)?;
270
271     // in `ide`, the `on_type` invariant is that
272     // `text.char_at(position) == typed_char`.
273     position.offset -= TextSize::of('.');
274     let char_typed = params.ch.chars().next().unwrap_or('\0');
275
276     let text = snap.analysis.file_text(position.file_id)?;
277     if !text[usize::from(position.offset)..].starts_with(char_typed) {
278         // Add `always!` here once VS Code bug is fixed:
279         //   https://github.com/rust-analyzer/rust-analyzer/issues/10002
280         return Ok(None);
281     }
282
283     // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
284     // but it requires precise cursor positioning to work, and one can't
285     // position the cursor with on_type formatting. So, let's just toggle this
286     // feature off here, hoping that we'll enable it one day, ðŸ˜¿.
287     if char_typed == '>' {
288         return Ok(None);
289     }
290
291     let edit = snap.analysis.on_char_typed(position, char_typed)?;
292     let edit = match edit {
293         Some(it) => it,
294         None => return Ok(None),
295     };
296
297     // This should be a single-file edit
298     let (_, edit) = edit.source_file_edits.into_iter().next().unwrap();
299
300     let change = to_proto::text_edit_vec(&line_index, edit);
301     Ok(Some(change))
302 }
303
304 pub(crate) fn handle_document_symbol(
305     snap: GlobalStateSnapshot,
306     params: lsp_types::DocumentSymbolParams,
307 ) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
308     let _p = profile::span("handle_document_symbol");
309     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
310     let line_index = snap.file_line_index(file_id)?;
311
312     let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new();
313
314     for symbol in snap.analysis.file_structure(file_id)? {
315         let mut tags = Vec::new();
316         if symbol.deprecated {
317             tags.push(SymbolTag::DEPRECATED)
318         };
319
320         #[allow(deprecated)]
321         let doc_symbol = lsp_types::DocumentSymbol {
322             name: symbol.label,
323             detail: symbol.detail,
324             kind: to_proto::structure_node_kind(symbol.kind),
325             tags: Some(tags),
326             deprecated: Some(symbol.deprecated),
327             range: to_proto::range(&line_index, symbol.node_range),
328             selection_range: to_proto::range(&line_index, symbol.navigation_range),
329             children: None,
330         };
331         parents.push((doc_symbol, symbol.parent));
332     }
333
334     // Builds hierarchy from a flat list, in reverse order (so that indices
335     // makes sense)
336     let document_symbols = {
337         let mut acc = Vec::new();
338         while let Some((mut node, parent_idx)) = parents.pop() {
339             if let Some(children) = &mut node.children {
340                 children.reverse();
341             }
342             let parent = match parent_idx {
343                 None => &mut acc,
344                 Some(i) => parents[i].0.children.get_or_insert_with(Vec::new),
345             };
346             parent.push(node);
347         }
348         acc.reverse();
349         acc
350     };
351
352     let res = if snap.config.hierarchical_symbols() {
353         document_symbols.into()
354     } else {
355         let url = to_proto::url(&snap, file_id);
356         let mut symbol_information = Vec::<SymbolInformation>::new();
357         for symbol in document_symbols {
358             flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
359         }
360         symbol_information.into()
361     };
362     return Ok(Some(res));
363
364     fn flatten_document_symbol(
365         symbol: &lsp_types::DocumentSymbol,
366         container_name: Option<String>,
367         url: &Url,
368         res: &mut Vec<SymbolInformation>,
369     ) {
370         let mut tags = Vec::new();
371
372         #[allow(deprecated)]
373         if let Some(true) = symbol.deprecated {
374             tags.push(SymbolTag::DEPRECATED)
375         }
376
377         #[allow(deprecated)]
378         res.push(SymbolInformation {
379             name: symbol.name.clone(),
380             kind: symbol.kind,
381             tags: Some(tags),
382             deprecated: symbol.deprecated,
383             location: Location::new(url.clone(), symbol.range),
384             container_name,
385         });
386
387         for child in symbol.children.iter().flatten() {
388             flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
389         }
390     }
391 }
392
393 pub(crate) fn handle_workspace_symbol(
394     snap: GlobalStateSnapshot,
395     params: WorkspaceSymbolParams,
396 ) -> Result<Option<Vec<SymbolInformation>>> {
397     let _p = profile::span("handle_workspace_symbol");
398
399     let (all_symbols, libs) = decide_search_scope_and_kind(&params, &snap);
400
401     let query = {
402         let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
403         let mut q = Query::new(query);
404         if !all_symbols {
405             q.only_types();
406         }
407         if libs {
408             q.libs();
409         }
410         q.limit(128);
411         q
412     };
413     let mut res = exec_query(&snap, query)?;
414     if res.is_empty() && !all_symbols {
415         let mut query = Query::new(params.query);
416         query.limit(128);
417         res = exec_query(&snap, query)?;
418     }
419
420     return Ok(Some(res));
421
422     fn decide_search_scope_and_kind(
423         params: &WorkspaceSymbolParams,
424         snap: &GlobalStateSnapshot,
425     ) -> (bool, bool) {
426         // Support old-style parsing of markers in the query.
427         let mut all_symbols = params.query.contains('#');
428         let mut libs = params.query.contains('*');
429
430         let config = snap.config.workspace_symbol();
431
432         // If no explicit marker was set, check request params. If that's also empty
433         // use global config.
434         if !all_symbols {
435             let search_kind = match params.search_kind {
436                 Some(ref search_kind) => search_kind,
437                 None => &config.search_kind,
438             };
439             all_symbols = match search_kind {
440                 lsp_ext::WorkspaceSymbolSearchKind::OnlyTypes => false,
441                 lsp_ext::WorkspaceSymbolSearchKind::AllSymbols => true,
442             }
443         }
444
445         if !libs {
446             let search_scope = match params.search_scope {
447                 Some(ref search_scope) => search_scope,
448                 None => &config.search_scope,
449             };
450             libs = match search_scope {
451                 lsp_ext::WorkspaceSymbolSearchScope::Workspace => false,
452                 lsp_ext::WorkspaceSymbolSearchScope::WorkspaceAndDependencies => true,
453             }
454         }
455
456         (all_symbols, libs)
457     }
458
459     fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
460         let mut res = Vec::new();
461         for nav in snap.analysis.symbol_search(query)? {
462             let container_name = nav.container_name.as_ref().map(|v| v.to_string());
463
464             #[allow(deprecated)]
465             let info = SymbolInformation {
466                 name: nav.name.to_string(),
467                 kind: nav
468                     .kind
469                     .map(to_proto::symbol_kind)
470                     .unwrap_or(lsp_types::SymbolKind::VARIABLE),
471                 tags: None,
472                 location: to_proto::location_from_nav(snap, nav)?,
473                 container_name,
474                 deprecated: None,
475             };
476             res.push(info);
477         }
478         Ok(res)
479     }
480 }
481
482 pub(crate) fn handle_will_rename_files(
483     snap: GlobalStateSnapshot,
484     params: lsp_types::RenameFilesParams,
485 ) -> Result<Option<lsp_types::WorkspaceEdit>> {
486     let _p = profile::span("handle_will_rename_files");
487
488     let source_changes: Vec<SourceChange> = params
489         .files
490         .into_iter()
491         .filter_map(|file_rename| {
492             let from = Url::parse(&file_rename.old_uri).ok()?;
493             let to = Url::parse(&file_rename.new_uri).ok()?;
494
495             let from_path = from.to_file_path().ok()?;
496             let to_path = to.to_file_path().ok()?;
497
498             // Limit to single-level moves for now.
499             match (from_path.parent(), to_path.parent()) {
500                 (Some(p1), Some(p2)) if p1 == p2 => {
501                     if from_path.is_dir() {
502                         // add '/' to end of url -- from `file://path/to/folder` to `file://path/to/folder/`
503                         let mut old_folder_name = from_path.file_stem()?.to_str()?.to_string();
504                         old_folder_name.push('/');
505                         let from_with_trailing_slash = from.join(&old_folder_name).ok()?;
506
507                         let imitate_from_url = from_with_trailing_slash.join("mod.rs").ok()?;
508                         let new_file_name = to_path.file_name()?.to_str()?;
509                         Some((
510                             snap.url_to_file_id(&imitate_from_url).ok()?,
511                             new_file_name.to_string(),
512                         ))
513                     } else {
514                         let old_name = from_path.file_stem()?.to_str()?;
515                         let new_name = to_path.file_stem()?.to_str()?;
516                         match (old_name, new_name) {
517                             ("mod", _) => None,
518                             (_, "mod") => None,
519                             _ => Some((snap.url_to_file_id(&from).ok()?, new_name.to_string())),
520                         }
521                     }
522                 }
523                 _ => None,
524             }
525         })
526         .filter_map(|(file_id, new_name)| {
527             snap.analysis.will_rename_file(file_id, &new_name).ok()?
528         })
529         .collect();
530
531     // Drop file system edits since we're just renaming things on the same level
532     let mut source_changes = source_changes.into_iter();
533     let mut source_change = source_changes.next().unwrap_or_default();
534     source_change.file_system_edits.clear();
535     // no collect here because we want to merge text edits on same file ids
536     source_change.extend(source_changes.map(|it| it.source_file_edits).flatten());
537     if source_change.source_file_edits.is_empty() {
538         Ok(None)
539     } else {
540         to_proto::workspace_edit(&snap, source_change).map(Some)
541     }
542 }
543
544 pub(crate) fn handle_goto_definition(
545     snap: GlobalStateSnapshot,
546     params: lsp_types::GotoDefinitionParams,
547 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
548     let _p = profile::span("handle_goto_definition");
549     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
550     let nav_info = match snap.analysis.goto_definition(position)? {
551         None => return Ok(None),
552         Some(it) => it,
553     };
554     let src = FileRange { file_id: position.file_id, range: nav_info.range };
555     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
556     Ok(Some(res))
557 }
558
559 pub(crate) fn handle_goto_declaration(
560     snap: GlobalStateSnapshot,
561     params: lsp_types::request::GotoDeclarationParams,
562 ) -> Result<Option<lsp_types::request::GotoDeclarationResponse>> {
563     let _p = profile::span("handle_goto_declaration");
564     let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?;
565     let nav_info = match snap.analysis.goto_declaration(position)? {
566         None => return handle_goto_definition(snap, params),
567         Some(it) => it,
568     };
569     let src = FileRange { file_id: position.file_id, range: nav_info.range };
570     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
571     Ok(Some(res))
572 }
573
574 pub(crate) fn handle_goto_implementation(
575     snap: GlobalStateSnapshot,
576     params: lsp_types::request::GotoImplementationParams,
577 ) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
578     let _p = profile::span("handle_goto_implementation");
579     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
580     let nav_info = match snap.analysis.goto_implementation(position)? {
581         None => return Ok(None),
582         Some(it) => it,
583     };
584     let src = FileRange { file_id: position.file_id, range: nav_info.range };
585     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
586     Ok(Some(res))
587 }
588
589 pub(crate) fn handle_goto_type_definition(
590     snap: GlobalStateSnapshot,
591     params: lsp_types::request::GotoTypeDefinitionParams,
592 ) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
593     let _p = profile::span("handle_goto_type_definition");
594     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
595     let nav_info = match snap.analysis.goto_type_definition(position)? {
596         None => return Ok(None),
597         Some(it) => it,
598     };
599     let src = FileRange { file_id: position.file_id, range: nav_info.range };
600     let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
601     Ok(Some(res))
602 }
603
604 pub(crate) fn handle_parent_module(
605     snap: GlobalStateSnapshot,
606     params: lsp_types::TextDocumentPositionParams,
607 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
608     let _p = profile::span("handle_parent_module");
609     if let Ok(file_path) = &params.text_document.uri.to_file_path() {
610         if file_path.file_name().unwrap_or_default() == "Cargo.toml" {
611             // search workspaces for parent packages or fallback to workspace root
612             let abs_path_buf = match AbsPathBuf::try_from(file_path.to_path_buf()).ok() {
613                 Some(abs_path_buf) => abs_path_buf,
614                 None => return Ok(None),
615             };
616
617             let manifest_path = match ManifestPath::try_from(abs_path_buf).ok() {
618                 Some(manifest_path) => manifest_path,
619                 None => return Ok(None),
620             };
621
622             let links: Vec<LocationLink> = snap
623                 .workspaces
624                 .iter()
625                 .filter_map(|ws| match ws {
626                     ProjectWorkspace::Cargo { cargo, .. } => cargo.parent_manifests(&manifest_path),
627                     _ => None,
628                 })
629                 .flatten()
630                 .map(|parent_manifest_path| LocationLink {
631                     origin_selection_range: None,
632                     target_uri: to_proto::url_from_abs_path(&parent_manifest_path),
633                     target_range: Range::default(),
634                     target_selection_range: Range::default(),
635                 })
636                 .collect::<_>();
637             return Ok(Some(links.into()));
638         }
639
640         // check if invoked at the crate root
641         let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
642         let crate_id = match snap.analysis.crate_for(file_id)?.first() {
643             Some(&crate_id) => crate_id,
644             None => return Ok(None),
645         };
646         let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? {
647             Some(it) => it,
648             None => return Ok(None),
649         };
650
651         if snap.analysis.crate_root(crate_id)? == file_id {
652             let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml);
653             let res = vec![LocationLink {
654                 origin_selection_range: None,
655                 target_uri: cargo_toml_url,
656                 target_range: Range::default(),
657                 target_selection_range: Range::default(),
658             }]
659             .into();
660             return Ok(Some(res));
661         }
662     }
663
664     // locate parent module by semantics
665     let position = from_proto::file_position(&snap, params)?;
666     let navs = snap.analysis.parent_module(position)?;
667     let res = to_proto::goto_definition_response(&snap, None, navs)?;
668     Ok(Some(res))
669 }
670
671 pub(crate) fn handle_runnables(
672     snap: GlobalStateSnapshot,
673     params: lsp_ext::RunnablesParams,
674 ) -> Result<Vec<lsp_ext::Runnable>> {
675     let _p = profile::span("handle_runnables");
676     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
677     let line_index = snap.file_line_index(file_id)?;
678     let offset = params.position.map(|it| from_proto::offset(&line_index, it));
679     let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
680
681     let expect_test = match offset {
682         Some(offset) => {
683             let source_file = snap.analysis.parse(file_id)?;
684             algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset)
685                 .and_then(|it| it.path()?.segment()?.name_ref())
686                 .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file")
687         }
688         None => false,
689     };
690
691     let mut res = Vec::new();
692     for runnable in snap.analysis.runnables(file_id)? {
693         if let Some(offset) = offset {
694             if !runnable.nav.full_range.contains_inclusive(offset) {
695                 continue;
696             }
697         }
698         if should_skip_target(&runnable, cargo_spec.as_ref()) {
699             continue;
700         }
701         let mut runnable = to_proto::runnable(&snap, runnable)?;
702         if expect_test {
703             runnable.label = format!("{} + expect", runnable.label);
704             runnable.args.expect_test = Some(true);
705         }
706         res.push(runnable);
707     }
708
709     // Add `cargo check` and `cargo test` for all targets of the whole package
710     let config = snap.config.runnables();
711     match cargo_spec {
712         Some(spec) => {
713             for cmd in ["check", "test"] {
714                 res.push(lsp_ext::Runnable {
715                     label: format!("cargo {} -p {} --all-targets", cmd, spec.package),
716                     location: None,
717                     kind: lsp_ext::RunnableKind::Cargo,
718                     args: lsp_ext::CargoRunnable {
719                         workspace_root: Some(spec.workspace_root.clone().into()),
720                         override_cargo: config.override_cargo.clone(),
721                         cargo_args: vec![
722                             cmd.to_string(),
723                             "--package".to_string(),
724                             spec.package.clone(),
725                             "--all-targets".to_string(),
726                         ],
727                         cargo_extra_args: config.cargo_extra_args.clone(),
728                         executable_args: Vec::new(),
729                         expect_test: None,
730                     },
731                 })
732             }
733         }
734         None => {
735             if !snap.config.linked_projects().is_empty()
736                 || !snap
737                     .config
738                     .discovered_projects
739                     .as_ref()
740                     .map(|projects| projects.is_empty())
741                     .unwrap_or(true)
742             {
743                 res.push(lsp_ext::Runnable {
744                     label: "cargo check --workspace".to_string(),
745                     location: None,
746                     kind: lsp_ext::RunnableKind::Cargo,
747                     args: lsp_ext::CargoRunnable {
748                         workspace_root: None,
749                         override_cargo: config.override_cargo,
750                         cargo_args: vec!["check".to_string(), "--workspace".to_string()],
751                         cargo_extra_args: config.cargo_extra_args,
752                         executable_args: Vec::new(),
753                         expect_test: None,
754                     },
755                 });
756             }
757         }
758     }
759     Ok(res)
760 }
761
762 pub(crate) fn handle_related_tests(
763     snap: GlobalStateSnapshot,
764     params: lsp_types::TextDocumentPositionParams,
765 ) -> Result<Vec<lsp_ext::TestInfo>> {
766     let _p = profile::span("handle_related_tests");
767     let position = from_proto::file_position(&snap, params)?;
768
769     let tests = snap.analysis.related_tests(position, None)?;
770     let mut res = Vec::new();
771     for it in tests {
772         if let Ok(runnable) = to_proto::runnable(&snap, it) {
773             res.push(lsp_ext::TestInfo { runnable })
774         }
775     }
776
777     Ok(res)
778 }
779
780 pub(crate) fn handle_completion(
781     snap: GlobalStateSnapshot,
782     params: lsp_types::CompletionParams,
783 ) -> Result<Option<lsp_types::CompletionResponse>> {
784     let _p = profile::span("handle_completion");
785     let text_document_position = params.text_document_position.clone();
786     let position = from_proto::file_position(&snap, params.text_document_position)?;
787     let completion_triggered_after_single_colon = {
788         let mut res = false;
789         if let Some(ctx) = params.context {
790             if ctx.trigger_character.as_deref() == Some(":") {
791                 let source_file = snap.analysis.parse(position.file_id)?;
792                 let left_token =
793                     source_file.syntax().token_at_offset(position.offset).left_biased();
794                 match left_token {
795                     Some(left_token) => res = left_token.kind() == T![:],
796                     None => res = true,
797                 }
798             }
799         }
800         res
801     };
802     if completion_triggered_after_single_colon {
803         return Ok(None);
804     }
805
806     let completion_config = &snap.config.completion();
807     let items = match snap.analysis.completions(completion_config, position)? {
808         None => return Ok(None),
809         Some(items) => items,
810     };
811     let line_index = snap.file_line_index(position.file_id)?;
812
813     let items =
814         to_proto::completion_items(&snap.config, &line_index, text_document_position, items);
815
816     let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
817     Ok(Some(completion_list.into()))
818 }
819
820 pub(crate) fn handle_completion_resolve(
821     snap: GlobalStateSnapshot,
822     mut original_completion: CompletionItem,
823 ) -> Result<CompletionItem> {
824     let _p = profile::span("handle_completion_resolve");
825
826     if !all_edits_are_disjoint(&original_completion, &[]) {
827         return Err(invalid_params_error(
828             "Received a completion with overlapping edits, this is not LSP-compliant".to_string(),
829         )
830         .into());
831     }
832
833     let data = match original_completion.data.take() {
834         Some(it) => it,
835         None => return Ok(original_completion),
836     };
837
838     let resolve_data: lsp_ext::CompletionResolveData = serde_json::from_value(data)?;
839
840     let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
841     let line_index = snap.file_line_index(file_id)?;
842     let offset = from_proto::offset(&line_index, resolve_data.position.position);
843
844     let additional_edits = snap
845         .analysis
846         .resolve_completion_edits(
847             &snap.config.completion(),
848             FilePosition { file_id, offset },
849             resolve_data
850                 .imports
851                 .into_iter()
852                 .map(|import| (import.full_import_path, import.imported_name)),
853         )?
854         .into_iter()
855         .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
856         .collect::<Vec<_>>();
857
858     if !all_edits_are_disjoint(&original_completion, &additional_edits) {
859         return Err(LspError::new(
860             ErrorCode::InternalError as i32,
861             "Import edit overlaps with the original completion edits, this is not LSP-compliant"
862                 .into(),
863         )
864         .into());
865     }
866
867     if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
868         original_additional_edits.extend(additional_edits.into_iter())
869     } else {
870         original_completion.additional_text_edits = Some(additional_edits);
871     }
872
873     Ok(original_completion)
874 }
875
876 pub(crate) fn handle_folding_range(
877     snap: GlobalStateSnapshot,
878     params: FoldingRangeParams,
879 ) -> Result<Option<Vec<FoldingRange>>> {
880     let _p = profile::span("handle_folding_range");
881     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
882     let folds = snap.analysis.folding_ranges(file_id)?;
883     let text = snap.analysis.file_text(file_id)?;
884     let line_index = snap.file_line_index(file_id)?;
885     let line_folding_only = snap.config.line_folding_only();
886     let res = folds
887         .into_iter()
888         .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
889         .collect();
890     Ok(Some(res))
891 }
892
893 pub(crate) fn handle_signature_help(
894     snap: GlobalStateSnapshot,
895     params: lsp_types::SignatureHelpParams,
896 ) -> Result<Option<lsp_types::SignatureHelp>> {
897     let _p = profile::span("handle_signature_help");
898     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
899     let call_info = match snap.analysis.call_info(position)? {
900         Some(it) => it,
901         None => return Ok(None),
902     };
903     let concise = !snap.config.call_info_full();
904     let res =
905         to_proto::signature_help(call_info, concise, snap.config.signature_help_label_offsets());
906     Ok(Some(res))
907 }
908
909 pub(crate) fn handle_hover(
910     snap: GlobalStateSnapshot,
911     params: lsp_ext::HoverParams,
912 ) -> Result<Option<lsp_ext::Hover>> {
913     let _p = profile::span("handle_hover");
914     let range = match params.position {
915         PositionOrRange::Position(position) => Range::new(position, position),
916         PositionOrRange::Range(range) => range,
917     };
918
919     let file_range = from_proto::file_range(&snap, params.text_document, range)?;
920     let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
921         None => return Ok(None),
922         Some(info) => info,
923     };
924
925     let line_index = snap.file_line_index(file_range.file_id)?;
926     let range = to_proto::range(&line_index, info.range);
927     let markup_kind =
928         snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind);
929     let hover = lsp_ext::Hover {
930         hover: lsp_types::Hover {
931             contents: HoverContents::Markup(to_proto::markup_content(
932                 info.info.markup,
933                 markup_kind,
934             )),
935             range: Some(range),
936         },
937         actions: if snap.config.hover_actions().none() {
938             Vec::new()
939         } else {
940             prepare_hover_actions(&snap, &info.info.actions)
941         },
942     };
943
944     Ok(Some(hover))
945 }
946
947 pub(crate) fn handle_prepare_rename(
948     snap: GlobalStateSnapshot,
949     params: lsp_types::TextDocumentPositionParams,
950 ) -> Result<Option<PrepareRenameResponse>> {
951     let _p = profile::span("handle_prepare_rename");
952     let position = from_proto::file_position(&snap, params)?;
953
954     let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?;
955
956     let line_index = snap.file_line_index(position.file_id)?;
957     let range = to_proto::range(&line_index, change.range);
958     Ok(Some(PrepareRenameResponse::Range(range)))
959 }
960
961 pub(crate) fn handle_rename(
962     snap: GlobalStateSnapshot,
963     params: RenameParams,
964 ) -> Result<Option<WorkspaceEdit>> {
965     let _p = profile::span("handle_rename");
966     let position = from_proto::file_position(&snap, params.text_document_position)?;
967
968     let mut change =
969         snap.analysis.rename(position, &*params.new_name)?.map_err(to_proto::rename_error)?;
970
971     // this is kind of a hack to prevent double edits from happening when moving files
972     // When a module gets renamed by renaming the mod declaration this causes the file to move
973     // which in turn will trigger a WillRenameFiles request to the server for which we reply with a
974     // a second identical set of renames, the client will then apply both edits causing incorrect edits
975     // with this we only emit source_file_edits in the WillRenameFiles response which will do the rename instead
976     // See https://github.com/microsoft/vscode-languageserver-node/issues/752 for more info
977     if !change.file_system_edits.is_empty() && snap.config.will_rename() {
978         change.source_file_edits.clear();
979     }
980     let workspace_edit = to_proto::workspace_edit(&snap, change)?;
981     Ok(Some(workspace_edit))
982 }
983
984 pub(crate) fn handle_references(
985     snap: GlobalStateSnapshot,
986     params: lsp_types::ReferenceParams,
987 ) -> Result<Option<Vec<Location>>> {
988     let _p = profile::span("handle_references");
989     let position = from_proto::file_position(&snap, params.text_document_position)?;
990
991     let refs = match snap.analysis.find_all_refs(position, None)? {
992         None => return Ok(None),
993         Some(refs) => refs,
994     };
995
996     let include_declaration = params.context.include_declaration;
997     let locations = refs
998         .into_iter()
999         .flat_map(|refs| {
1000             let decl = if include_declaration {
1001                 refs.declaration.map(|decl| FileRange {
1002                     file_id: decl.nav.file_id,
1003                     range: decl.nav.focus_or_full_range(),
1004                 })
1005             } else {
1006                 None
1007             };
1008             refs.references
1009                 .into_iter()
1010                 .flat_map(|(file_id, refs)| {
1011                     refs.into_iter().map(move |(range, _)| FileRange { file_id, range })
1012                 })
1013                 .chain(decl)
1014         })
1015         .filter_map(|frange| to_proto::location(&snap, frange).ok())
1016         .collect();
1017
1018     Ok(Some(locations))
1019 }
1020
1021 pub(crate) fn handle_formatting(
1022     snap: GlobalStateSnapshot,
1023     params: DocumentFormattingParams,
1024 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
1025     let _p = profile::span("handle_formatting");
1026
1027     run_rustfmt(&snap, params.text_document, None)
1028 }
1029
1030 pub(crate) fn handle_range_formatting(
1031     snap: GlobalStateSnapshot,
1032     params: lsp_types::DocumentRangeFormattingParams,
1033 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
1034     let _p = profile::span("handle_range_formatting");
1035
1036     run_rustfmt(&snap, params.text_document, Some(params.range))
1037 }
1038
1039 pub(crate) fn handle_code_action(
1040     snap: GlobalStateSnapshot,
1041     params: lsp_types::CodeActionParams,
1042 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
1043     let _p = profile::span("handle_code_action");
1044
1045     if !snap.config.code_action_literals() {
1046         // We intentionally don't support command-based actions, as those either
1047         // require either custom client-code or server-initiated edits. Server
1048         // initiated edits break causality, so we avoid those.
1049         return Ok(None);
1050     }
1051
1052     let line_index =
1053         snap.file_line_index(from_proto::file_id(&snap, &params.text_document.uri)?)?;
1054     let frange = from_proto::file_range(&snap, params.text_document.clone(), params.range)?;
1055
1056     let mut assists_config = snap.config.assist();
1057     assists_config.allowed = params
1058         .context
1059         .only
1060         .clone()
1061         .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
1062
1063     let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
1064
1065     let code_action_resolve_cap = snap.config.code_action_resolve();
1066     let resolve = if code_action_resolve_cap {
1067         AssistResolveStrategy::None
1068     } else {
1069         AssistResolveStrategy::All
1070     };
1071     let assists = snap.analysis.assists_with_fixes(
1072         &assists_config,
1073         &snap.config.diagnostics(),
1074         resolve,
1075         frange,
1076     )?;
1077     for (index, assist) in assists.into_iter().enumerate() {
1078         let resolve_data =
1079             if code_action_resolve_cap { Some((index, params.clone())) } else { None };
1080         let code_action = to_proto::code_action(&snap, assist, resolve_data)?;
1081         res.push(code_action)
1082     }
1083
1084     // Fixes from `cargo check`.
1085     for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() {
1086         // FIXME: this mapping is awkward and shouldn't exist. Refactor
1087         // `snap.check_fixes` to not convert to LSP prematurely.
1088         let intersect_fix_range = fix
1089             .ranges
1090             .iter()
1091             .copied()
1092             .map(|range| from_proto::text_range(&line_index, range))
1093             .any(|fix_range| fix_range.intersect(frange.range).is_some());
1094         if intersect_fix_range {
1095             res.push(fix.action.clone());
1096         }
1097     }
1098
1099     Ok(Some(res))
1100 }
1101
1102 pub(crate) fn handle_code_action_resolve(
1103     snap: GlobalStateSnapshot,
1104     mut code_action: lsp_ext::CodeAction,
1105 ) -> Result<lsp_ext::CodeAction> {
1106     let _p = profile::span("handle_code_action_resolve");
1107     let params = match code_action.data.take() {
1108         Some(it) => it,
1109         None => return Err(invalid_params_error("code action without data".to_string()).into()),
1110     };
1111
1112     let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
1113     let line_index = snap.file_line_index(file_id)?;
1114     let range = from_proto::text_range(&line_index, params.code_action_params.range);
1115     let frange = FileRange { file_id, range };
1116
1117     let mut assists_config = snap.config.assist();
1118     assists_config.allowed = params
1119         .code_action_params
1120         .context
1121         .only
1122         .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
1123
1124     let (assist_index, assist_resolve) = match parse_action_id(&params.id) {
1125         Ok(parsed_data) => parsed_data,
1126         Err(e) => {
1127             return Err(invalid_params_error(format!(
1128                 "Failed to parse action id string '{}': {}",
1129                 params.id, e
1130             ))
1131             .into())
1132         }
1133     };
1134
1135     let expected_assist_id = assist_resolve.assist_id.clone();
1136     let expected_kind = assist_resolve.assist_kind;
1137
1138     let assists = snap.analysis.assists_with_fixes(
1139         &assists_config,
1140         &snap.config.diagnostics(),
1141         AssistResolveStrategy::Single(assist_resolve),
1142         frange,
1143     )?;
1144
1145     let assist = match assists.get(assist_index) {
1146         Some(assist) => assist,
1147         None => return Err(invalid_params_error(format!(
1148             "Failed to find the assist for index {} provided by the resolve request. Resolve request assist id: {}",
1149             assist_index, params.id,
1150         ))
1151         .into())
1152     };
1153     if assist.id.0 != expected_assist_id || assist.id.1 != expected_kind {
1154         return Err(invalid_params_error(format!(
1155             "Mismatching assist at index {} for the resolve parameters given. Resolve request assist id: {}, actual id: {:?}.",
1156             assist_index, params.id, assist.id
1157         ))
1158         .into());
1159     }
1160     let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit;
1161     code_action.edit = edit;
1162     Ok(code_action)
1163 }
1164
1165 fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> {
1166     let id_parts = action_id.split(':').collect::<Vec<_>>();
1167     match id_parts.as_slice() {
1168         [assist_id_string, assist_kind_string, index_string] => {
1169             let assist_kind: AssistKind = assist_kind_string.parse()?;
1170             let index: usize = match index_string.parse() {
1171                 Ok(index) => index,
1172                 Err(e) => return Err(format!("Incorrect index string: {}", e)),
1173             };
1174             Ok((index, SingleResolve { assist_id: assist_id_string.to_string(), assist_kind }))
1175         }
1176         _ => Err("Action id contains incorrect number of segments".to_string()),
1177     }
1178 }
1179
1180 pub(crate) fn handle_code_lens(
1181     snap: GlobalStateSnapshot,
1182     params: lsp_types::CodeLensParams,
1183 ) -> Result<Option<Vec<CodeLens>>> {
1184     let _p = profile::span("handle_code_lens");
1185
1186     let lens_config = snap.config.lens();
1187     if lens_config.none() {
1188         // early return before any db query!
1189         return Ok(Some(Vec::default()));
1190     }
1191
1192     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1193     let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?;
1194
1195     let annotations = snap.analysis.annotations(
1196         &AnnotationConfig {
1197             binary_target: cargo_target_spec
1198                 .map(|spec| {
1199                     matches!(
1200                         spec.target_kind,
1201                         TargetKind::Bin | TargetKind::Example | TargetKind::Test
1202                     )
1203                 })
1204                 .unwrap_or(false),
1205             annotate_runnables: lens_config.runnable(),
1206             annotate_impls: lens_config.implementations,
1207             annotate_references: lens_config.refs,
1208             annotate_method_references: lens_config.method_refs,
1209             annotate_enum_variant_references: lens_config.enum_variant_refs,
1210         },
1211         file_id,
1212     )?;
1213
1214     let mut res = Vec::new();
1215     for a in annotations {
1216         to_proto::code_lens(&mut res, &snap, a)?;
1217     }
1218
1219     Ok(Some(res))
1220 }
1221
1222 pub(crate) fn handle_code_lens_resolve(
1223     snap: GlobalStateSnapshot,
1224     code_lens: CodeLens,
1225 ) -> Result<CodeLens> {
1226     let annotation = from_proto::annotation(&snap, code_lens.clone())?;
1227     let annotation = snap.analysis.resolve_annotation(annotation)?;
1228
1229     let mut acc = Vec::new();
1230     to_proto::code_lens(&mut acc, &snap, annotation)?;
1231
1232     let res = match acc.pop() {
1233         Some(it) if acc.is_empty() => it,
1234         _ => {
1235             never!();
1236             code_lens
1237         }
1238     };
1239
1240     Ok(res)
1241 }
1242
1243 pub(crate) fn handle_document_highlight(
1244     snap: GlobalStateSnapshot,
1245     params: lsp_types::DocumentHighlightParams,
1246 ) -> Result<Option<Vec<lsp_types::DocumentHighlight>>> {
1247     let _p = profile::span("handle_document_highlight");
1248     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1249     let line_index = snap.file_line_index(position.file_id)?;
1250
1251     let refs = match snap.analysis.highlight_related(snap.config.highlight_related(), position)? {
1252         None => return Ok(None),
1253         Some(refs) => refs,
1254     };
1255     let res = refs
1256         .into_iter()
1257         .map(|ide::HighlightedRange { range, category }| lsp_types::DocumentHighlight {
1258             range: to_proto::range(&line_index, range),
1259             kind: category.map(to_proto::document_highlight_kind),
1260         })
1261         .collect();
1262     Ok(Some(res))
1263 }
1264
1265 pub(crate) fn handle_ssr(
1266     snap: GlobalStateSnapshot,
1267     params: lsp_ext::SsrParams,
1268 ) -> Result<lsp_types::WorkspaceEdit> {
1269     let _p = profile::span("handle_ssr");
1270     let selections = params
1271         .selections
1272         .iter()
1273         .map(|range| from_proto::file_range(&snap, params.position.text_document.clone(), *range))
1274         .collect::<Result<Vec<_>, _>>()?;
1275     let position = from_proto::file_position(&snap, params.position)?;
1276     let source_change = snap.analysis.structural_search_replace(
1277         &params.query,
1278         params.parse_only,
1279         position,
1280         selections,
1281     )??;
1282     to_proto::workspace_edit(&snap, source_change)
1283 }
1284
1285 pub(crate) fn publish_diagnostics(
1286     snap: &GlobalStateSnapshot,
1287     file_id: FileId,
1288 ) -> Result<Vec<Diagnostic>> {
1289     let _p = profile::span("publish_diagnostics");
1290     let line_index = snap.file_line_index(file_id)?;
1291
1292     let diagnostics: Vec<Diagnostic> = snap
1293         .analysis
1294         .diagnostics(&snap.config.diagnostics(), AssistResolveStrategy::None, file_id)?
1295         .into_iter()
1296         .map(|d| Diagnostic {
1297             range: to_proto::range(&line_index, d.range),
1298             severity: Some(to_proto::diagnostic_severity(d.severity)),
1299             code: Some(NumberOrString::String(d.code.as_str().to_string())),
1300             code_description: Some(lsp_types::CodeDescription {
1301                 href: lsp_types::Url::parse(&format!(
1302                     "https://rust-analyzer.github.io/manual.html#{}",
1303                     d.code.as_str()
1304                 ))
1305                 .unwrap(),
1306             }),
1307             source: Some("rust-analyzer".to_string()),
1308             message: d.message,
1309             related_information: None,
1310             tags: if d.unused { Some(vec![DiagnosticTag::UNNECESSARY]) } else { None },
1311             data: None,
1312         })
1313         .collect();
1314     Ok(diagnostics)
1315 }
1316
1317 pub(crate) fn handle_inlay_hints(
1318     snap: GlobalStateSnapshot,
1319     params: InlayHintsParams,
1320 ) -> Result<Vec<InlayHint>> {
1321     let _p = profile::span("handle_inlay_hints");
1322     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1323     let line_index = snap.file_line_index(file_id)?;
1324     Ok(snap
1325         .analysis
1326         .inlay_hints(&snap.config.inlay_hints(), file_id)?
1327         .into_iter()
1328         .map(|it| to_proto::inlay_hint(&line_index, it))
1329         .collect())
1330 }
1331
1332 pub(crate) fn handle_call_hierarchy_prepare(
1333     snap: GlobalStateSnapshot,
1334     params: CallHierarchyPrepareParams,
1335 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1336     let _p = profile::span("handle_call_hierarchy_prepare");
1337     let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1338
1339     let nav_info = match snap.analysis.call_hierarchy(position)? {
1340         None => return Ok(None),
1341         Some(it) => it,
1342     };
1343
1344     let RangeInfo { range: _, info: navs } = nav_info;
1345     let res = navs
1346         .into_iter()
1347         .filter(|it| it.kind == Some(SymbolKind::Function))
1348         .map(|it| to_proto::call_hierarchy_item(&snap, it))
1349         .collect::<Result<Vec<_>>>()?;
1350
1351     Ok(Some(res))
1352 }
1353
1354 pub(crate) fn handle_call_hierarchy_incoming(
1355     snap: GlobalStateSnapshot,
1356     params: CallHierarchyIncomingCallsParams,
1357 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1358     let _p = profile::span("handle_call_hierarchy_incoming");
1359     let item = params.item;
1360
1361     let doc = TextDocumentIdentifier::new(item.uri);
1362     let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
1363     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1364
1365     let call_items = match snap.analysis.incoming_calls(fpos)? {
1366         None => return Ok(None),
1367         Some(it) => it,
1368     };
1369
1370     let mut res = vec![];
1371
1372     for call_item in call_items.into_iter() {
1373         let file_id = call_item.target.file_id;
1374         let line_index = snap.file_line_index(file_id)?;
1375         let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1376         res.push(CallHierarchyIncomingCall {
1377             from: item,
1378             from_ranges: call_item
1379                 .ranges
1380                 .into_iter()
1381                 .map(|it| to_proto::range(&line_index, it))
1382                 .collect(),
1383         });
1384     }
1385
1386     Ok(Some(res))
1387 }
1388
1389 pub(crate) fn handle_call_hierarchy_outgoing(
1390     snap: GlobalStateSnapshot,
1391     params: CallHierarchyOutgoingCallsParams,
1392 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1393     let _p = profile::span("handle_call_hierarchy_outgoing");
1394     let item = params.item;
1395
1396     let doc = TextDocumentIdentifier::new(item.uri);
1397     let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
1398     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1399
1400     let call_items = match snap.analysis.outgoing_calls(fpos)? {
1401         None => return Ok(None),
1402         Some(it) => it,
1403     };
1404
1405     let mut res = vec![];
1406
1407     for call_item in call_items.into_iter() {
1408         let file_id = call_item.target.file_id;
1409         let line_index = snap.file_line_index(file_id)?;
1410         let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1411         res.push(CallHierarchyOutgoingCall {
1412             to: item,
1413             from_ranges: call_item
1414                 .ranges
1415                 .into_iter()
1416                 .map(|it| to_proto::range(&line_index, it))
1417                 .collect(),
1418         });
1419     }
1420
1421     Ok(Some(res))
1422 }
1423
1424 pub(crate) fn handle_semantic_tokens_full(
1425     snap: GlobalStateSnapshot,
1426     params: SemanticTokensParams,
1427 ) -> Result<Option<SemanticTokensResult>> {
1428     let _p = profile::span("handle_semantic_tokens_full");
1429
1430     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1431     let text = snap.analysis.file_text(file_id)?;
1432     let line_index = snap.file_line_index(file_id)?;
1433
1434     let highlights = snap.analysis.highlight(file_id)?;
1435     let highlight_strings = snap.config.highlighting_strings();
1436     let semantic_tokens =
1437         to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
1438
1439     // Unconditionally cache the tokens
1440     snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens.clone());
1441
1442     Ok(Some(semantic_tokens.into()))
1443 }
1444
1445 pub(crate) fn handle_semantic_tokens_full_delta(
1446     snap: GlobalStateSnapshot,
1447     params: SemanticTokensDeltaParams,
1448 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
1449     let _p = profile::span("handle_semantic_tokens_full_delta");
1450
1451     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1452     let text = snap.analysis.file_text(file_id)?;
1453     let line_index = snap.file_line_index(file_id)?;
1454
1455     let highlights = snap.analysis.highlight(file_id)?;
1456     let highlight_strings = snap.config.highlighting_strings();
1457     let semantic_tokens =
1458         to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
1459
1460     let mut cache = snap.semantic_tokens_cache.lock();
1461     let cached_tokens = cache.entry(params.text_document.uri).or_default();
1462
1463     if let Some(prev_id) = &cached_tokens.result_id {
1464         if *prev_id == params.previous_result_id {
1465             let delta = to_proto::semantic_token_delta(cached_tokens, &semantic_tokens);
1466             *cached_tokens = semantic_tokens;
1467             return Ok(Some(delta.into()));
1468         }
1469     }
1470
1471     *cached_tokens = semantic_tokens.clone();
1472
1473     Ok(Some(semantic_tokens.into()))
1474 }
1475
1476 pub(crate) fn handle_semantic_tokens_range(
1477     snap: GlobalStateSnapshot,
1478     params: SemanticTokensRangeParams,
1479 ) -> Result<Option<SemanticTokensRangeResult>> {
1480     let _p = profile::span("handle_semantic_tokens_range");
1481
1482     let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1483     let text = snap.analysis.file_text(frange.file_id)?;
1484     let line_index = snap.file_line_index(frange.file_id)?;
1485
1486     let highlights = snap.analysis.highlight_range(frange)?;
1487     let highlight_strings = snap.config.highlighting_strings();
1488     let semantic_tokens =
1489         to_proto::semantic_tokens(&text, &line_index, highlights, highlight_strings);
1490     Ok(Some(semantic_tokens.into()))
1491 }
1492
1493 pub(crate) fn handle_open_docs(
1494     snap: GlobalStateSnapshot,
1495     params: lsp_types::TextDocumentPositionParams,
1496 ) -> Result<Option<lsp_types::Url>> {
1497     let _p = profile::span("handle_open_docs");
1498     let position = from_proto::file_position(&snap, params)?;
1499
1500     let remote = snap.analysis.external_docs(position)?;
1501
1502     Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1503 }
1504
1505 pub(crate) fn handle_open_cargo_toml(
1506     snap: GlobalStateSnapshot,
1507     params: lsp_ext::OpenCargoTomlParams,
1508 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
1509     let _p = profile::span("handle_open_cargo_toml");
1510     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1511
1512     let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? {
1513         Some(it) => it,
1514         None => return Ok(None),
1515     };
1516
1517     let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml);
1518     let res: lsp_types::GotoDefinitionResponse =
1519         Location::new(cargo_toml_url, Range::default()).into();
1520     Ok(Some(res))
1521 }
1522
1523 pub(crate) fn handle_move_item(
1524     snap: GlobalStateSnapshot,
1525     params: lsp_ext::MoveItemParams,
1526 ) -> Result<Vec<lsp_ext::SnippetTextEdit>> {
1527     let _p = profile::span("handle_move_item");
1528     let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1529     let range = from_proto::file_range(&snap, params.text_document, params.range)?;
1530
1531     let direction = match params.direction {
1532         lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
1533         lsp_ext::MoveItemDirection::Down => ide::Direction::Down,
1534     };
1535
1536     match snap.analysis.move_item(range, direction)? {
1537         Some(text_edit) => {
1538             let line_index = snap.file_line_index(file_id)?;
1539             Ok(to_proto::snippet_text_edit_vec(&line_index, true, text_edit))
1540         }
1541         None => Ok(vec![]),
1542     }
1543 }
1544
1545 fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
1546     lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1547 }
1548
1549 fn show_impl_command_link(
1550     snap: &GlobalStateSnapshot,
1551     position: &FilePosition,
1552 ) -> Option<lsp_ext::CommandLinkGroup> {
1553     if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference {
1554         if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) {
1555             let uri = to_proto::url(snap, position.file_id);
1556             let line_index = snap.file_line_index(position.file_id).ok()?;
1557             let position = to_proto::position(&line_index, position.offset);
1558             let locations: Vec<_> = nav_data
1559                 .info
1560                 .into_iter()
1561                 .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok())
1562                 .collect();
1563             let title = to_proto::implementation_title(locations.len());
1564             let command = to_proto::command::show_references(title, &uri, position, locations);
1565
1566             return Some(lsp_ext::CommandLinkGroup {
1567                 commands: vec![to_command_link(command, "Go to implementations".into())],
1568                 ..Default::default()
1569             });
1570         }
1571     }
1572     None
1573 }
1574
1575 fn show_ref_command_link(
1576     snap: &GlobalStateSnapshot,
1577     position: &FilePosition,
1578 ) -> Option<lsp_ext::CommandLinkGroup> {
1579     if snap.config.hover_actions().references && snap.config.client_commands().show_reference {
1580         if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) {
1581             let uri = to_proto::url(snap, position.file_id);
1582             let line_index = snap.file_line_index(position.file_id).ok()?;
1583             let position = to_proto::position(&line_index, position.offset);
1584             let locations: Vec<_> = ref_search_res
1585                 .into_iter()
1586                 .flat_map(|res| res.references)
1587                 .flat_map(|(file_id, ranges)| {
1588                     ranges.into_iter().filter_map(move |(range, _)| {
1589                         to_proto::location(snap, FileRange { file_id, range }).ok()
1590                     })
1591                 })
1592                 .collect();
1593             let title = to_proto::reference_title(locations.len());
1594             let command = to_proto::command::show_references(title, &uri, position, locations);
1595
1596             return Some(lsp_ext::CommandLinkGroup {
1597                 commands: vec![to_command_link(command, "Go to references".into())],
1598                 ..Default::default()
1599             });
1600         }
1601     }
1602     None
1603 }
1604
1605 fn runnable_action_links(
1606     snap: &GlobalStateSnapshot,
1607     runnable: Runnable,
1608 ) -> Option<lsp_ext::CommandLinkGroup> {
1609     let hover_actions_config = snap.config.hover_actions();
1610     if !hover_actions_config.runnable() {
1611         return None;
1612     }
1613
1614     let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?;
1615     if should_skip_target(&runnable, cargo_spec.as_ref()) {
1616         return None;
1617     }
1618
1619     let client_commands_config = snap.config.client_commands();
1620     if !(client_commands_config.run_single || client_commands_config.debug_single) {
1621         return None;
1622     }
1623
1624     let title = runnable.title();
1625     let r = to_proto::runnable(snap, runnable).ok()?;
1626
1627     let mut group = lsp_ext::CommandLinkGroup::default();
1628
1629     if hover_actions_config.run && client_commands_config.run_single {
1630         let run_command = to_proto::command::run_single(&r, &title);
1631         group.commands.push(to_command_link(run_command, r.label.clone()));
1632     }
1633
1634     if hover_actions_config.debug && client_commands_config.debug_single {
1635         let dbg_command = to_proto::command::debug_single(&r);
1636         group.commands.push(to_command_link(dbg_command, r.label));
1637     }
1638
1639     Some(group)
1640 }
1641
1642 fn goto_type_action_links(
1643     snap: &GlobalStateSnapshot,
1644     nav_targets: &[HoverGotoTypeData],
1645 ) -> Option<lsp_ext::CommandLinkGroup> {
1646     if !snap.config.hover_actions().goto_type_def
1647         || nav_targets.is_empty()
1648         || !snap.config.client_commands().goto_location
1649     {
1650         return None;
1651     }
1652
1653     Some(lsp_ext::CommandLinkGroup {
1654         title: Some("Go to ".into()),
1655         commands: nav_targets
1656             .iter()
1657             .filter_map(|it| {
1658                 to_proto::command::goto_location(snap, &it.nav)
1659                     .map(|cmd| to_command_link(cmd, it.mod_path.clone()))
1660             })
1661             .collect(),
1662     })
1663 }
1664
1665 fn prepare_hover_actions(
1666     snap: &GlobalStateSnapshot,
1667     actions: &[HoverAction],
1668 ) -> Vec<lsp_ext::CommandLinkGroup> {
1669     actions
1670         .iter()
1671         .filter_map(|it| match it {
1672             HoverAction::Implementation(position) => show_impl_command_link(snap, position),
1673             HoverAction::Reference(position) => show_ref_command_link(snap, position),
1674             HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()),
1675             HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
1676         })
1677         .collect()
1678 }
1679
1680 fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool {
1681     match runnable.kind {
1682         RunnableKind::Bin => {
1683             // Do not suggest binary run on other target than binary
1684             match &cargo_spec {
1685                 Some(spec) => !matches!(
1686                     spec.target_kind,
1687                     TargetKind::Bin | TargetKind::Example | TargetKind::Test
1688                 ),
1689                 None => true,
1690             }
1691         }
1692         _ => false,
1693     }
1694 }
1695
1696 fn run_rustfmt(
1697     snap: &GlobalStateSnapshot,
1698     text_document: TextDocumentIdentifier,
1699     range: Option<lsp_types::Range>,
1700 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
1701     let file_id = from_proto::file_id(snap, &text_document.uri)?;
1702     let file = snap.analysis.file_text(file_id)?;
1703     let crate_ids = snap.analysis.crate_for(file_id)?;
1704
1705     let line_index = snap.file_line_index(file_id)?;
1706
1707     let mut rustfmt = match snap.config.rustfmt() {
1708         RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
1709             let mut cmd = process::Command::new(toolchain::rustfmt());
1710             cmd.args(extra_args);
1711             // try to chdir to the file so we can respect `rustfmt.toml`
1712             // FIXME: use `rustfmt --config-path` once
1713             // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
1714             match text_document.uri.to_file_path() {
1715                 Ok(mut path) => {
1716                     // pop off file name
1717                     if path.pop() && path.is_dir() {
1718                         cmd.current_dir(path);
1719                     }
1720                 }
1721                 Err(_) => {
1722                     tracing::error!(
1723                         "Unable to get file path for {}, rustfmt.toml might be ignored",
1724                         text_document.uri
1725                     );
1726                 }
1727             }
1728             if let Some(&crate_id) = crate_ids.first() {
1729                 // Assume all crates are in the same edition
1730                 let edition = snap.analysis.crate_edition(crate_id)?;
1731                 cmd.arg("--edition");
1732                 cmd.arg(edition.to_string());
1733             }
1734
1735             if let Some(range) = range {
1736                 if !enable_range_formatting {
1737                     return Err(LspError::new(
1738                         ErrorCode::InvalidRequest as i32,
1739                         String::from(
1740                             "rustfmt range formatting is unstable. \
1741                             Opt-in by using a nightly build of rustfmt and setting \
1742                             `rustfmt.enableRangeFormatting` to true in your LSP configuration",
1743                         ),
1744                     )
1745                     .into());
1746                 }
1747
1748                 let frange = from_proto::file_range(snap, text_document, range)?;
1749                 let start_line = line_index.index.line_col(frange.range.start()).line;
1750                 let end_line = line_index.index.line_col(frange.range.end()).line;
1751
1752                 cmd.arg("--unstable-features");
1753                 cmd.arg("--file-lines");
1754                 cmd.arg(
1755                     json!([{
1756                         "file": "stdin",
1757                         "range": [start_line, end_line]
1758                     }])
1759                     .to_string(),
1760                 );
1761             }
1762
1763             cmd
1764         }
1765         RustfmtConfig::CustomCommand { command, args } => {
1766             let mut cmd = process::Command::new(command);
1767             cmd.args(args);
1768             cmd
1769         }
1770     };
1771
1772     let mut rustfmt = rustfmt
1773         .stdin(Stdio::piped())
1774         .stdout(Stdio::piped())
1775         .stderr(Stdio::piped())
1776         .spawn()
1777         .context(format!("Failed to spawn {:?}", rustfmt))?;
1778
1779     rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
1780
1781     let output = rustfmt.wait_with_output()?;
1782     let captured_stdout = String::from_utf8(output.stdout)?;
1783     let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
1784
1785     if !output.status.success() {
1786         let rustfmt_not_installed =
1787             captured_stderr.contains("not installed") || captured_stderr.contains("not available");
1788
1789         return match output.status.code() {
1790             Some(1) if !rustfmt_not_installed => {
1791                 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
1792                 // likely cause exiting with 1. Most Language Servers swallow parse errors on
1793                 // formatting because otherwise an error is surfaced to the user on top of the
1794                 // syntax error diagnostics they're already receiving. This is especially jarring
1795                 // if they have format on save enabled.
1796                 tracing::info!("rustfmt exited with status 1, assuming parse error and ignoring");
1797                 Ok(None)
1798             }
1799             _ => {
1800                 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1801                 Err(LspError::new(
1802                     -32900,
1803                     format!(
1804                         r#"rustfmt exited with:
1805                            Status: {}
1806                            stdout: {}
1807                            stderr: {}"#,
1808                         output.status, captured_stdout, captured_stderr,
1809                     ),
1810                 )
1811                 .into())
1812             }
1813         };
1814     }
1815
1816     let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
1817
1818     if line_index.endings != new_line_endings {
1819         // If line endings are different, send the entire file.
1820         // Diffing would not work here, as the line endings might be the only
1821         // difference.
1822         Ok(Some(to_proto::text_edit_vec(
1823             &line_index,
1824             TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
1825         )))
1826     } else if *file == new_text {
1827         // The document is already formatted correctly -- no edits needed.
1828         Ok(None)
1829     } else {
1830         Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
1831     }
1832 }