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