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