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