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