]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/main_loop/handlers.rs
New runnables API
[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     lsp_ext::{self, InlayHint, InlayHintsParams},
36     to_proto,
37     world::WorldSnapshot,
38     LspError, Result,
39 };
40
41 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
42     let _p = profile("handle_analyzer_status");
43     let mut buf = world.status();
44     format_to!(buf, "\n\nrequests:\n");
45     let requests = world.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     world: WorldSnapshot,
55     params: lsp_ext::SyntaxTreeParams,
56 ) -> Result<String> {
57     let _p = profile("handle_syntax_tree");
58     let id = from_proto::file_id(&world, &params.text_document.uri)?;
59     let line_index = world.analysis().file_line_index(id)?;
60     let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
61     let res = world.analysis().syntax_tree(id, text_range)?;
62     Ok(res)
63 }
64
65 pub fn handle_expand_macro(
66     world: WorldSnapshot,
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(&world, &params.text_document.uri)?;
71     let line_index = world.analysis().file_line_index(file_id)?;
72     let offset = from_proto::offset(&line_index, params.position);
73
74     let res = world.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     world: WorldSnapshot,
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(&world, &params.text_document.uri)?;
84     let line_index = world.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 = world.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     world: WorldSnapshot,
123     params: lsp_ext::MatchingBraceParams,
124 ) -> Result<Vec<Position>> {
125     let _p = profile("handle_matching_brace");
126     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
127     let line_index = world.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 world.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     world: WorldSnapshot,
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(&world, &params.text_document.uri)?;
149     let line_index = world.analysis().file_line_index(file_id)?;
150     let line_endings = world.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 = world.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     world: WorldSnapshot,
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(&world, params)?;
172     let edit = match world.analysis().on_enter(position)? {
173         None => return Ok(None),
174         Some(it) => it,
175     };
176     let line_index = world.analysis().file_line_index(position.file_id)?;
177     let line_endings = world.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     world: WorldSnapshot,
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(&world, params.text_document_position)?;
189     let line_index = world.analysis().file_line_index(position.file_id)?;
190     let line_endings = world.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 = world.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 = world.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     world: WorldSnapshot,
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(&world, &params.text_document.uri)?;
228     let line_index = world.analysis().file_line_index(file_id)?;
229
230     let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
231
232     for symbol in world.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 world.config.client_caps.hierarchical_symbols {
259         document_symbols.into()
260     } else {
261         let url = to_proto::url(&world, 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     world: WorldSnapshot,
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(&world, query)?;
310     if res.is_empty() && !all_symbols {
311         let mut query = Query::new(params.query);
312         query.limit(128);
313         res = exec_query(&world, query)?;
314     }
315
316     return Ok(Some(res));
317
318     fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
319         let mut res = Vec::new();
320         for nav in world.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(world, 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     world: WorldSnapshot,
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(&world, params.text_document_position_params)?;
340     let nav_info = match world.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(&world, Some(src), nav_info.info)?;
346     Ok(Some(res))
347 }
348
349 pub fn handle_goto_implementation(
350     world: WorldSnapshot,
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(&world, params.text_document_position_params)?;
355     let nav_info = match world.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(&world, Some(src), nav_info.info)?;
361     Ok(Some(res))
362 }
363
364 pub fn handle_goto_type_definition(
365     world: WorldSnapshot,
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(&world, params.text_document_position_params)?;
370     let nav_info = match world.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(&world, Some(src), nav_info.info)?;
376     Ok(Some(res))
377 }
378
379 pub fn handle_parent_module(
380     world: WorldSnapshot,
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(&world, params)?;
385     let navs = world.analysis().parent_module(position)?;
386     let res = to_proto::goto_definition_response(&world, None, navs)?;
387     Ok(Some(res))
388 }
389
390 pub fn handle_runnables(
391     world: WorldSnapshot,
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(&world, &params.text_document.uri)?;
396     let line_index = world.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 = world.workspace_root_for(file_id);
400     let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
401     for runnable in world.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(&world, 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     world: WorldSnapshot,
457     params: lsp_types::CompletionParams,
458 ) -> Result<Option<lsp_types::CompletionResponse>> {
459     let _p = profile("handle_completion");
460     let position = from_proto::file_position(&world, 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 = world.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 world.analysis().completions(&world.config.completion, position)? {
484         None => return Ok(None),
485         Some(items) => items,
486     };
487     let line_index = world.analysis().file_line_index(position.file_id)?;
488     let line_endings = world.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     world: WorldSnapshot,
499     params: FoldingRangeParams,
500 ) -> Result<Option<Vec<FoldingRange>>> {
501     let _p = profile("handle_folding_range");
502     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
503     let folds = world.analysis().folding_ranges(file_id)?;
504     let text = world.analysis().file_text(file_id)?;
505     let line_index = world.analysis().file_line_index(file_id)?;
506     let line_folding_only = world.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     world: WorldSnapshot,
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(&world, params.text_document_position_params)?;
520     let call_info = match world.analysis().call_info(position)? {
521         None => return Ok(None),
522         Some(it) => it,
523     };
524     let concise = !world.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(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result<Option<Hover>> {
539     let _p = profile("handle_hover");
540     let position = from_proto::file_position(&world, params.text_document_position_params)?;
541     let info = match world.analysis().hover(position)? {
542         None => return Ok(None),
543         Some(info) => info,
544     };
545     let line_index = world.analysis.file_line_index(position.file_id)?;
546     let range = to_proto::range(&line_index, info.range);
547     let res = Hover {
548         contents: HoverContents::Markup(MarkupContent {
549             kind: MarkupKind::Markdown,
550             value: crate::markdown::format_docs(&info.info.to_markup()),
551         }),
552         range: Some(range),
553     };
554     Ok(Some(res))
555 }
556
557 pub fn handle_prepare_rename(
558     world: WorldSnapshot,
559     params: lsp_types::TextDocumentPositionParams,
560 ) -> Result<Option<PrepareRenameResponse>> {
561     let _p = profile("handle_prepare_rename");
562     let position = from_proto::file_position(&world, params)?;
563
564     let optional_change = world.analysis().rename(position, "dummy")?;
565     let range = match optional_change {
566         None => return Ok(None),
567         Some(it) => it.range,
568     };
569
570     let line_index = world.analysis().file_line_index(position.file_id)?;
571     let range = to_proto::range(&line_index, range);
572     Ok(Some(PrepareRenameResponse::Range(range)))
573 }
574
575 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
576     let _p = profile("handle_rename");
577     let position = from_proto::file_position(&world, params.text_document_position)?;
578
579     if params.new_name.is_empty() {
580         return Err(LspError::new(
581             ErrorCode::InvalidParams as i32,
582             "New Name cannot be empty".into(),
583         )
584         .into());
585     }
586
587     let optional_change = world.analysis().rename(position, &*params.new_name)?;
588     let source_change = match optional_change {
589         None => return Ok(None),
590         Some(it) => it.info,
591     };
592     let workspace_edit = to_proto::workspace_edit(&world, source_change)?;
593     Ok(Some(workspace_edit))
594 }
595
596 pub fn handle_references(
597     world: WorldSnapshot,
598     params: lsp_types::ReferenceParams,
599 ) -> Result<Option<Vec<Location>>> {
600     let _p = profile("handle_references");
601     let position = from_proto::file_position(&world, params.text_document_position)?;
602
603     let refs = match world.analysis().find_all_refs(position, None)? {
604         None => return Ok(None),
605         Some(refs) => refs,
606     };
607
608     let locations = if params.context.include_declaration {
609         refs.into_iter()
610             .filter_map(|reference| to_proto::location(&world, reference.file_range).ok())
611             .collect()
612     } else {
613         // Only iterate over the references if include_declaration was false
614         refs.references()
615             .iter()
616             .filter_map(|reference| to_proto::location(&world, reference.file_range).ok())
617             .collect()
618     };
619
620     Ok(Some(locations))
621 }
622
623 pub fn handle_formatting(
624     world: WorldSnapshot,
625     params: DocumentFormattingParams,
626 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
627     let _p = profile("handle_formatting");
628     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
629     let file = world.analysis().file_text(file_id)?;
630     let crate_ids = world.analysis().crate_for(file_id)?;
631
632     let file_line_index = world.analysis().file_line_index(file_id)?;
633     let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
634
635     let mut rustfmt = match &world.config.rustfmt {
636         RustfmtConfig::Rustfmt { extra_args } => {
637             let mut cmd = process::Command::new("rustfmt");
638             cmd.args(extra_args);
639             if let Some(&crate_id) = crate_ids.first() {
640                 // Assume all crates are in the same edition
641                 let edition = world.analysis().crate_edition(crate_id)?;
642                 cmd.arg("--edition");
643                 cmd.arg(edition.to_string());
644             }
645             cmd
646         }
647         RustfmtConfig::CustomCommand { command, args } => {
648             let mut cmd = process::Command::new(command);
649             cmd.args(args);
650             cmd
651         }
652     };
653
654     if let Ok(path) = params.text_document.uri.to_file_path() {
655         if let Some(parent) = path.parent() {
656             rustfmt.current_dir(parent);
657         }
658     }
659     let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
660
661     rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
662
663     let output = rustfmt.wait_with_output()?;
664     let captured_stdout = String::from_utf8(output.stdout)?;
665
666     if !output.status.success() {
667         match output.status.code() {
668             Some(1) => {
669                 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
670                 // likely cause exiting with 1. Most Language Servers swallow parse errors on
671                 // formatting because otherwise an error is surfaced to the user on top of the
672                 // syntax error diagnostics they're already receiving. This is especially jarring
673                 // if they have format on save enabled.
674                 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
675                 return Ok(None);
676             }
677             _ => {
678                 // Something else happened - e.g. `rustfmt` is missing or caught a signal
679                 return Err(LspError::new(
680                     -32900,
681                     format!(
682                         r#"rustfmt exited with:
683                            Status: {}
684                            stdout: {}"#,
685                         output.status, captured_stdout,
686                     ),
687                 )
688                 .into());
689             }
690         }
691     }
692
693     Ok(Some(vec![lsp_types::TextEdit {
694         range: Range::new(Position::new(0, 0), end_position),
695         new_text: captured_stdout,
696     }]))
697 }
698
699 pub fn handle_code_action(
700     world: WorldSnapshot,
701     params: lsp_types::CodeActionParams,
702 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
703     let _p = profile("handle_code_action");
704     // We intentionally don't support command-based actions, as those either
705     // requires custom client-code anyway, or requires server-initiated edits.
706     // Server initiated edits break causality, so we avoid those as well.
707     if !world.config.client_caps.code_action_literals {
708         return Ok(None);
709     }
710
711     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
712     let line_index = world.analysis().file_line_index(file_id)?;
713     let range = from_proto::text_range(&line_index, params.range);
714     let frange = FileRange { file_id, range };
715
716     let diagnostics = world.analysis().diagnostics(file_id)?;
717     let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
718
719     let fixes_from_diagnostics = diagnostics
720         .into_iter()
721         .filter_map(|d| Some((d.range, d.fix?)))
722         .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
723         .map(|(_range, fix)| fix);
724
725     for fix in fixes_from_diagnostics {
726         let title = fix.label;
727         let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?;
728         let action =
729             lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None };
730         res.push(action);
731     }
732
733     for fix in world.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
741     for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() {
742         res.push(to_proto::code_action(&world, assist)?.into());
743     }
744     Ok(Some(res))
745 }
746
747 pub fn handle_code_lens(
748     world: WorldSnapshot,
749     params: lsp_types::CodeLensParams,
750 ) -> Result<Option<Vec<CodeLens>>> {
751     let _p = profile("handle_code_lens");
752     let mut lenses: Vec<CodeLens> = Default::default();
753
754     if world.config.lens.none() {
755         // early return before any db query!
756         return Ok(Some(lenses));
757     }
758
759     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
760     let line_index = world.analysis().file_line_index(file_id)?;
761     let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
762
763     if world.config.lens.runnable() {
764         // Gather runnables
765         for runnable in world.analysis().runnables(file_id)? {
766             let (run_title, debugee) = match &runnable.kind {
767                 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
768                     ("â–¶\u{fe0e} Run Test", true)
769                 }
770                 RunnableKind::DocTest { .. } => {
771                     // cargo does not support -no-run for doctests
772                     ("â–¶\u{fe0e} Run Doctest", false)
773                 }
774                 RunnableKind::Bench { .. } => {
775                     // Nothing wrong with bench debugging
776                     ("Run Bench", true)
777                 }
778                 RunnableKind::Bin => {
779                     // Do not suggest binary run on other target than binary
780                     match &cargo_spec {
781                         Some(spec) => match spec.target_kind {
782                             TargetKind::Bin => ("Run", true),
783                             _ => continue,
784                         },
785                         None => continue,
786                     }
787                 }
788             };
789
790             let range = to_proto::range(&line_index, runnable.nav.range());
791             let r = to_proto::runnable(&world, file_id, runnable)?;
792             if world.config.lens.run {
793                 let lens = CodeLens {
794                     range,
795                     command: Some(Command {
796                         title: run_title.to_string(),
797                         command: "rust-analyzer.runSingle".into(),
798                         arguments: Some(vec![to_value(&r).unwrap()]),
799                     }),
800                     data: None,
801                 };
802                 lenses.push(lens);
803             }
804
805             if debugee && world.config.lens.debug {
806                 let debug_lens = CodeLens {
807                     range,
808                     command: Some(Command {
809                         title: "Debug".into(),
810                         command: "rust-analyzer.debugSingle".into(),
811                         arguments: Some(vec![to_value(r).unwrap()]),
812                     }),
813                     data: None,
814                 };
815                 lenses.push(debug_lens);
816             }
817         }
818     }
819
820     if world.config.lens.impementations {
821         // Handle impls
822         lenses.extend(
823             world
824                 .analysis()
825                 .file_structure(file_id)?
826                 .into_iter()
827                 .filter(|it| match it.kind {
828                     SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
829                     _ => false,
830                 })
831                 .map(|it| {
832                     let range = to_proto::range(&line_index, it.node_range);
833                     let pos = range.start;
834                     let lens_params = lsp_types::request::GotoImplementationParams {
835                         text_document_position_params: lsp_types::TextDocumentPositionParams::new(
836                             params.text_document.clone(),
837                             pos,
838                         ),
839                         work_done_progress_params: Default::default(),
840                         partial_result_params: Default::default(),
841                     };
842                     CodeLens {
843                         range,
844                         command: None,
845                         data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
846                     }
847                 }),
848         );
849     }
850     Ok(Some(lenses))
851 }
852
853 #[derive(Debug, Serialize, Deserialize)]
854 #[serde(rename_all = "camelCase")]
855 enum CodeLensResolveData {
856     Impls(lsp_types::request::GotoImplementationParams),
857 }
858
859 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
860     let _p = profile("handle_code_lens_resolve");
861     let data = code_lens.data.unwrap();
862     let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
863     match resolve {
864         Some(CodeLensResolveData::Impls(lens_params)) => {
865             let locations: Vec<Location> =
866                 match handle_goto_implementation(world, lens_params.clone())? {
867                     Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
868                     Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
869                     Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
870                         .into_iter()
871                         .map(|link| Location::new(link.target_uri, link.target_selection_range))
872                         .collect(),
873                     _ => vec![],
874                 };
875
876             let title = if locations.len() == 1 {
877                 "1 implementation".into()
878             } else {
879                 format!("{} implementations", locations.len())
880             };
881
882             // We cannot use the 'editor.action.showReferences' command directly
883             // because that command requires vscode types which we convert in the handler
884             // on the client side.
885             let cmd = Command {
886                 title,
887                 command: "rust-analyzer.showReferences".into(),
888                 arguments: Some(vec![
889                     to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
890                     to_value(code_lens.range.start).unwrap(),
891                     to_value(locations).unwrap(),
892                 ]),
893             };
894             Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
895         }
896         None => Ok(CodeLens {
897             range: code_lens.range,
898             command: Some(Command { title: "Error".into(), ..Default::default() }),
899             data: None,
900         }),
901     }
902 }
903
904 pub fn handle_document_highlight(
905     world: WorldSnapshot,
906     params: lsp_types::DocumentHighlightParams,
907 ) -> Result<Option<Vec<DocumentHighlight>>> {
908     let _p = profile("handle_document_highlight");
909     let position = from_proto::file_position(&world, params.text_document_position_params)?;
910     let line_index = world.analysis().file_line_index(position.file_id)?;
911
912     let refs = match world
913         .analysis()
914         .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
915     {
916         None => return Ok(None),
917         Some(refs) => refs,
918     };
919
920     let res = refs
921         .into_iter()
922         .filter(|reference| reference.file_range.file_id == position.file_id)
923         .map(|reference| DocumentHighlight {
924             range: to_proto::range(&line_index, reference.file_range.range),
925             kind: reference.access.map(to_proto::document_highlight_kind),
926         })
927         .collect();
928     Ok(Some(res))
929 }
930
931 pub fn handle_ssr(
932     world: WorldSnapshot,
933     params: lsp_ext::SsrParams,
934 ) -> Result<lsp_types::WorkspaceEdit> {
935     let _p = profile("handle_ssr");
936     let source_change =
937         world.analysis().structural_search_replace(&params.query, params.parse_only)??;
938     to_proto::workspace_edit(&world, source_change)
939 }
940
941 pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
942     let _p = profile("publish_diagnostics");
943     let line_index = world.analysis().file_line_index(file_id)?;
944     let diagnostics: Vec<Diagnostic> = world
945         .analysis()
946         .diagnostics(file_id)?
947         .into_iter()
948         .map(|d| Diagnostic {
949             range: to_proto::range(&line_index, d.range),
950             severity: Some(to_proto::diagnostic_severity(d.severity)),
951             code: None,
952             source: Some("rust-analyzer".to_string()),
953             message: d.message,
954             related_information: None,
955             tags: None,
956         })
957         .collect();
958     Ok(DiagnosticTask::SetNative(file_id, diagnostics))
959 }
960
961 pub fn handle_inlay_hints(
962     world: WorldSnapshot,
963     params: InlayHintsParams,
964 ) -> Result<Vec<InlayHint>> {
965     let _p = profile("handle_inlay_hints");
966     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
967     let analysis = world.analysis();
968     let line_index = analysis.file_line_index(file_id)?;
969     Ok(analysis
970         .inlay_hints(file_id, &world.config.inlay_hints)?
971         .into_iter()
972         .map(|it| to_proto::inlay_int(&line_index, it))
973         .collect())
974 }
975
976 pub fn handle_call_hierarchy_prepare(
977     world: WorldSnapshot,
978     params: CallHierarchyPrepareParams,
979 ) -> Result<Option<Vec<CallHierarchyItem>>> {
980     let _p = profile("handle_call_hierarchy_prepare");
981     let position = from_proto::file_position(&world, params.text_document_position_params)?;
982
983     let nav_info = match world.analysis().call_hierarchy(position)? {
984         None => return Ok(None),
985         Some(it) => it,
986     };
987
988     let RangeInfo { range: _, info: navs } = nav_info;
989     let res = navs
990         .into_iter()
991         .filter(|it| it.kind() == SyntaxKind::FN_DEF)
992         .map(|it| to_proto::call_hierarchy_item(&world, it))
993         .collect::<Result<Vec<_>>>()?;
994
995     Ok(Some(res))
996 }
997
998 pub fn handle_call_hierarchy_incoming(
999     world: WorldSnapshot,
1000     params: CallHierarchyIncomingCallsParams,
1001 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1002     let _p = profile("handle_call_hierarchy_incoming");
1003     let item = params.item;
1004
1005     let doc = TextDocumentIdentifier::new(item.uri);
1006     let frange = from_proto::file_range(&world, doc, item.range)?;
1007     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1008
1009     let call_items = match world.analysis().incoming_calls(fpos)? {
1010         None => return Ok(None),
1011         Some(it) => it,
1012     };
1013
1014     let mut res = vec![];
1015
1016     for call_item in call_items.into_iter() {
1017         let file_id = call_item.target.file_id();
1018         let line_index = world.analysis().file_line_index(file_id)?;
1019         let item = to_proto::call_hierarchy_item(&world, call_item.target)?;
1020         res.push(CallHierarchyIncomingCall {
1021             from: item,
1022             from_ranges: call_item
1023                 .ranges
1024                 .into_iter()
1025                 .map(|it| to_proto::range(&line_index, it))
1026                 .collect(),
1027         });
1028     }
1029
1030     Ok(Some(res))
1031 }
1032
1033 pub fn handle_call_hierarchy_outgoing(
1034     world: WorldSnapshot,
1035     params: CallHierarchyOutgoingCallsParams,
1036 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1037     let _p = profile("handle_call_hierarchy_outgoing");
1038     let item = params.item;
1039
1040     let doc = TextDocumentIdentifier::new(item.uri);
1041     let frange = from_proto::file_range(&world, doc, item.range)?;
1042     let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1043
1044     let call_items = match world.analysis().outgoing_calls(fpos)? {
1045         None => return Ok(None),
1046         Some(it) => it,
1047     };
1048
1049     let mut res = vec![];
1050
1051     for call_item in call_items.into_iter() {
1052         let file_id = call_item.target.file_id();
1053         let line_index = world.analysis().file_line_index(file_id)?;
1054         let item = to_proto::call_hierarchy_item(&world, call_item.target)?;
1055         res.push(CallHierarchyOutgoingCall {
1056             to: item,
1057             from_ranges: call_item
1058                 .ranges
1059                 .into_iter()
1060                 .map(|it| to_proto::range(&line_index, it))
1061                 .collect(),
1062         });
1063     }
1064
1065     Ok(Some(res))
1066 }
1067
1068 pub fn handle_semantic_tokens(
1069     world: WorldSnapshot,
1070     params: SemanticTokensParams,
1071 ) -> Result<Option<SemanticTokensResult>> {
1072     let _p = profile("handle_semantic_tokens");
1073
1074     let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
1075     let text = world.analysis().file_text(file_id)?;
1076     let line_index = world.analysis().file_line_index(file_id)?;
1077
1078     let highlights = world.analysis().highlight(file_id)?;
1079     let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1080     Ok(Some(semantic_tokens.into()))
1081 }
1082
1083 pub fn handle_semantic_tokens_range(
1084     world: WorldSnapshot,
1085     params: SemanticTokensRangeParams,
1086 ) -> Result<Option<SemanticTokensRangeResult>> {
1087     let _p = profile("handle_semantic_tokens_range");
1088
1089     let frange = from_proto::file_range(&world, params.text_document, params.range)?;
1090     let text = world.analysis().file_text(frange.file_id)?;
1091     let line_index = world.analysis().file_line_index(frange.file_id)?;
1092
1093     let highlights = world.analysis().highlight_range(frange)?;
1094     let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1095     Ok(Some(semantic_tokens.into()))
1096 }