]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/main_loop/handlers.rs
Move type inlay hint truncation to language server
[rust.git] / crates / ra_lsp_server / src / main_loop / handlers.rs
1 //! FIXME: write short doc here
2
3 use std::{fmt::Write as _, io::Write as _};
4
5 use lsp_server::ErrorCode;
6 use lsp_types::{
7     CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
8     DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
9     Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
10     Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
11 };
12 use ra_ide_api::{
13     AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope,
14 };
15 use ra_prof::profile;
16 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
17 use rustc_hash::FxHashMap;
18 use serde::{Deserialize, Serialize};
19 use serde_json::to_value;
20
21 use crate::{
22     cargo_target_spec::{runnable_args, CargoTargetSpec},
23     conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
24     req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
25     world::WorldSnapshot,
26     LspError, Result,
27 };
28
29 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
30     let _p = profile("handle_analyzer_status");
31     let mut buf = world.status();
32     writeln!(buf, "\n\nrequests:").unwrap();
33     let requests = world.latest_requests.read();
34     for (is_last, r) in requests.iter() {
35         let mark = if is_last { "*" } else { " " };
36         writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap();
37     }
38     Ok(buf)
39 }
40
41 pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
42     let _p = profile("handle_syntax_tree");
43     let id = params.text_document.try_conv_with(&world)?;
44     let line_index = world.analysis().file_line_index(id)?;
45     let text_range = params.range.map(|p| p.conv_with(&line_index));
46     let res = world.analysis().syntax_tree(id, text_range)?;
47     Ok(res)
48 }
49
50 pub fn handle_selection_range(
51     world: WorldSnapshot,
52     params: req::SelectionRangeParams,
53 ) -> Result<Vec<req::SelectionRange>> {
54     let _p = profile("handle_selection_range");
55     let file_id = params.text_document.try_conv_with(&world)?;
56     let line_index = world.analysis().file_line_index(file_id)?;
57     params
58         .positions
59         .into_iter()
60         .map_conv_with(&line_index)
61         .map(|position| {
62             let mut ranges = Vec::new();
63             {
64                 let mut range = TextRange::from_to(position, position);
65                 loop {
66                     ranges.push(range);
67                     let frange = FileRange { file_id, range };
68                     let next = world.analysis().extend_selection(frange)?;
69                     if next == range {
70                         break;
71                     } else {
72                         range = next
73                     }
74                 }
75             }
76             let mut range = req::SelectionRange {
77                 range: ranges.last().unwrap().conv_with(&line_index),
78                 parent: None,
79             };
80             for r in ranges.iter().rev().skip(1) {
81                 range = req::SelectionRange {
82                     range: r.conv_with(&line_index),
83                     parent: Some(Box::new(range)),
84                 }
85             }
86             Ok(range)
87         })
88         .collect()
89 }
90
91 pub fn handle_find_matching_brace(
92     world: WorldSnapshot,
93     params: req::FindMatchingBraceParams,
94 ) -> Result<Vec<Position>> {
95     let _p = profile("handle_find_matching_brace");
96     let file_id = params.text_document.try_conv_with(&world)?;
97     let line_index = world.analysis().file_line_index(file_id)?;
98     let res = params
99         .offsets
100         .into_iter()
101         .map_conv_with(&line_index)
102         .map(|offset| {
103             if let Ok(Some(matching_brace_offset)) =
104                 world.analysis().matching_brace(FilePosition { file_id, offset })
105             {
106                 matching_brace_offset
107             } else {
108                 offset
109             }
110         })
111         .map_conv_with(&line_index)
112         .collect();
113     Ok(res)
114 }
115
116 pub fn handle_join_lines(
117     world: WorldSnapshot,
118     params: req::JoinLinesParams,
119 ) -> Result<req::SourceChange> {
120     let _p = profile("handle_join_lines");
121     let frange = (&params.text_document, params.range).try_conv_with(&world)?;
122     world.analysis().join_lines(frange)?.try_conv_with(&world)
123 }
124
125 pub fn handle_on_enter(
126     world: WorldSnapshot,
127     params: req::TextDocumentPositionParams,
128 ) -> Result<Option<req::SourceChange>> {
129     let _p = profile("handle_on_enter");
130     let position = params.try_conv_with(&world)?;
131     match world.analysis().on_enter(position)? {
132         None => Ok(None),
133         Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
134     }
135 }
136
137 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
138 pub fn handle_on_type_formatting(
139     world: WorldSnapshot,
140     params: req::DocumentOnTypeFormattingParams,
141 ) -> Result<Option<Vec<TextEdit>>> {
142     let _p = profile("handle_on_type_formatting");
143     let mut position = params.text_document_position.try_conv_with(&world)?;
144     let line_index = world.analysis().file_line_index(position.file_id)?;
145     let line_endings = world.file_line_endings(position.file_id);
146
147     // in `ra_ide_api`, the `on_type` invariant is that
148     // `text.char_at(position) == typed_char`.
149     position.offset = position.offset - TextUnit::of_char('.');
150     let char_typed = params.ch.chars().next().unwrap_or('\0');
151
152     // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
153     // but it requires precise cursor positioning to work, and one can't
154     // position the cursor with on_type formatting. So, let's just toggle this
155     // feature off here, hoping that we'll enable it one day, 😿.
156     if char_typed == '>' {
157         return Ok(None);
158     }
159
160     let edit = world.analysis().on_char_typed(position, char_typed)?;
161     let mut edit = match edit {
162         Some(it) => it,
163         None => return Ok(None),
164     };
165
166     // This should be a single-file edit
167     let edit = edit.source_file_edits.pop().unwrap();
168
169     let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
170     Ok(Some(change))
171 }
172
173 pub fn handle_document_symbol(
174     world: WorldSnapshot,
175     params: req::DocumentSymbolParams,
176 ) -> Result<Option<req::DocumentSymbolResponse>> {
177     let _p = profile("handle_document_symbol");
178     let file_id = params.text_document.try_conv_with(&world)?;
179     let line_index = world.analysis().file_line_index(file_id)?;
180
181     let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
182
183     for symbol in world.analysis().file_structure(file_id)? {
184         let doc_symbol = DocumentSymbol {
185             name: symbol.label,
186             detail: symbol.detail,
187             kind: symbol.kind.conv(),
188             deprecated: Some(symbol.deprecated),
189             range: symbol.node_range.conv_with(&line_index),
190             selection_range: symbol.navigation_range.conv_with(&line_index),
191             children: None,
192         };
193         parents.push((doc_symbol, symbol.parent));
194     }
195     let mut res = Vec::new();
196     while let Some((node, parent)) = parents.pop() {
197         match parent {
198             None => res.push(node),
199             Some(i) => {
200                 let children = &mut parents[i].0.children;
201                 if children.is_none() {
202                     *children = Some(Vec::new());
203                 }
204                 children.as_mut().unwrap().push(node);
205             }
206         }
207     }
208
209     Ok(Some(res.into()))
210 }
211
212 pub fn handle_workspace_symbol(
213     world: WorldSnapshot,
214     params: req::WorkspaceSymbolParams,
215 ) -> Result<Option<Vec<SymbolInformation>>> {
216     let _p = profile("handle_workspace_symbol");
217     let all_symbols = params.query.contains('#');
218     let libs = params.query.contains('*');
219     let query = {
220         let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
221         let mut q = Query::new(query);
222         if !all_symbols {
223             q.only_types();
224         }
225         if libs {
226             q.libs();
227         }
228         q.limit(128);
229         q
230     };
231     let mut res = exec_query(&world, query)?;
232     if res.is_empty() && !all_symbols {
233         let mut query = Query::new(params.query);
234         query.limit(128);
235         res = exec_query(&world, query)?;
236     }
237
238     return Ok(Some(res));
239
240     fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
241         let mut res = Vec::new();
242         for nav in world.analysis().symbol_search(query)? {
243             let info = SymbolInformation {
244                 name: nav.name().to_string(),
245                 kind: nav.kind().conv(),
246                 location: nav.try_conv_with(world)?,
247                 container_name: nav.container_name().map(|v| v.to_string()),
248                 deprecated: None,
249             };
250             res.push(info);
251         }
252         Ok(res)
253     }
254 }
255
256 pub fn handle_goto_definition(
257     world: WorldSnapshot,
258     params: req::TextDocumentPositionParams,
259 ) -> Result<Option<req::GotoDefinitionResponse>> {
260     let _p = profile("handle_goto_definition");
261     let position = params.try_conv_with(&world)?;
262     let nav_info = match world.analysis().goto_definition(position)? {
263         None => return Ok(None),
264         Some(it) => it,
265     };
266     let res = (position.file_id, nav_info).try_conv_with(&world)?;
267     Ok(Some(res))
268 }
269
270 pub fn handle_goto_implementation(
271     world: WorldSnapshot,
272     params: req::TextDocumentPositionParams,
273 ) -> Result<Option<req::GotoImplementationResponse>> {
274     let _p = profile("handle_goto_implementation");
275     let position = params.try_conv_with(&world)?;
276     let nav_info = match world.analysis().goto_implementation(position)? {
277         None => return Ok(None),
278         Some(it) => it,
279     };
280     let res = (position.file_id, nav_info).try_conv_with(&world)?;
281     Ok(Some(res))
282 }
283
284 pub fn handle_goto_type_definition(
285     world: WorldSnapshot,
286     params: req::TextDocumentPositionParams,
287 ) -> Result<Option<req::GotoTypeDefinitionResponse>> {
288     let _p = profile("handle_goto_type_definition");
289     let position = params.try_conv_with(&world)?;
290     let nav_info = match world.analysis().goto_type_definition(position)? {
291         None => return Ok(None),
292         Some(it) => it,
293     };
294     let res = (position.file_id, nav_info).try_conv_with(&world)?;
295     Ok(Some(res))
296 }
297
298 pub fn handle_parent_module(
299     world: WorldSnapshot,
300     params: req::TextDocumentPositionParams,
301 ) -> Result<Vec<Location>> {
302     let _p = profile("handle_parent_module");
303     let position = params.try_conv_with(&world)?;
304     world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
305 }
306
307 pub fn handle_runnables(
308     world: WorldSnapshot,
309     params: req::RunnablesParams,
310 ) -> Result<Vec<req::Runnable>> {
311     let _p = profile("handle_runnables");
312     let file_id = params.text_document.try_conv_with(&world)?;
313     let line_index = world.analysis().file_line_index(file_id)?;
314     let offset = params.position.map(|it| it.conv_with(&line_index));
315     let mut res = Vec::new();
316     let workspace_root = world.workspace_root_for(file_id);
317     for runnable in world.analysis().runnables(file_id)? {
318         if let Some(offset) = offset {
319             if !runnable.range.contains_inclusive(offset) {
320                 continue;
321             }
322         }
323         res.push(to_lsp_runnable(&world, file_id, runnable)?);
324     }
325     let mut check_args = vec!["check".to_string()];
326     let label;
327     match CargoTargetSpec::for_file(&world, file_id)? {
328         Some(spec) => {
329             label = format!("cargo check -p {}", spec.package);
330             spec.push_to(&mut check_args);
331         }
332         None => {
333             label = "cargo check --all".to_string();
334             check_args.push("--all".to_string())
335         }
336     }
337     // Always add `cargo check`.
338     res.push(req::Runnable {
339         range: Default::default(),
340         label,
341         bin: "cargo".to_string(),
342         args: check_args,
343         env: FxHashMap::default(),
344         cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
345     });
346     Ok(res)
347 }
348
349 pub fn handle_decorations(
350     world: WorldSnapshot,
351     params: TextDocumentIdentifier,
352 ) -> Result<Vec<Decoration>> {
353     let _p = profile("handle_decorations");
354     let file_id = params.try_conv_with(&world)?;
355     highlight(&world, file_id)
356 }
357
358 pub fn handle_completion(
359     world: WorldSnapshot,
360     params: req::CompletionParams,
361 ) -> Result<Option<req::CompletionResponse>> {
362     let _p = profile("handle_completion");
363     let position = params.text_document_position.try_conv_with(&world)?;
364     let completion_triggered_after_single_colon = {
365         let mut res = false;
366         if let Some(ctx) = params.context {
367             if ctx.trigger_character.unwrap_or_default() == ":" {
368                 let source_file = world.analysis().parse(position.file_id)?;
369                 let syntax = source_file.syntax();
370                 let text = syntax.text();
371                 if let Some(next_char) = text.char_at(position.offset) {
372                     let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':');
373                     let prev_char = position.offset - diff;
374                     if text.char_at(prev_char) != Some(':') {
375                         res = true;
376                     }
377                 }
378             }
379         }
380         res
381     };
382     if completion_triggered_after_single_colon {
383         return Ok(None);
384     }
385
386     let items = match world.analysis().completions(position)? {
387         None => return Ok(None),
388         Some(items) => items,
389     };
390     let line_index = world.analysis().file_line_index(position.file_id)?;
391     let line_endings = world.file_line_endings(position.file_id);
392     let items: Vec<CompletionItem> =
393         items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
394
395     Ok(Some(items.into()))
396 }
397
398 pub fn handle_folding_range(
399     world: WorldSnapshot,
400     params: FoldingRangeParams,
401 ) -> Result<Option<Vec<FoldingRange>>> {
402     let _p = profile("handle_folding_range");
403     let file_id = params.text_document.try_conv_with(&world)?;
404     let folds = world.analysis().folding_ranges(file_id)?;
405     let text = world.analysis().file_text(file_id)?;
406     let line_index = world.analysis().file_line_index(file_id)?;
407     let ctx = FoldConvCtx {
408         text: &text,
409         line_index: &line_index,
410         line_folding_only: world.options.line_folding_only,
411     };
412     let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
413     Ok(res)
414 }
415
416 pub fn handle_signature_help(
417     world: WorldSnapshot,
418     params: req::TextDocumentPositionParams,
419 ) -> Result<Option<req::SignatureHelp>> {
420     let _p = profile("handle_signature_help");
421     let position = params.try_conv_with(&world)?;
422     if let Some(call_info) = world.analysis().call_info(position)? {
423         let active_parameter = call_info.active_parameter.map(|it| it as i64);
424         let sig_info = call_info.signature.conv();
425
426         Ok(Some(req::SignatureHelp {
427             signatures: vec![sig_info],
428             active_signature: Some(0),
429             active_parameter,
430         }))
431     } else {
432         Ok(None)
433     }
434 }
435
436 pub fn handle_hover(
437     world: WorldSnapshot,
438     params: req::TextDocumentPositionParams,
439 ) -> Result<Option<Hover>> {
440     let _p = profile("handle_hover");
441     let position = params.try_conv_with(&world)?;
442     let info = match world.analysis().hover(position)? {
443         None => return Ok(None),
444         Some(info) => info,
445     };
446     let line_index = world.analysis.file_line_index(position.file_id)?;
447     let range = info.range.conv_with(&line_index);
448     let res = Hover {
449         contents: HoverContents::Markup(MarkupContent {
450             kind: MarkupKind::Markdown,
451             value: crate::markdown::format_docs(&info.info.to_markup()),
452         }),
453         range: Some(range),
454     };
455     Ok(Some(res))
456 }
457
458 pub fn handle_prepare_rename(
459     world: WorldSnapshot,
460     params: req::TextDocumentPositionParams,
461 ) -> Result<Option<PrepareRenameResponse>> {
462     let _p = profile("handle_prepare_rename");
463     let position = params.try_conv_with(&world)?;
464
465     // We support renaming references like handle_rename does.
466     // In the future we may want to reject the renaming of things like keywords here too.
467     let optional_change = world.analysis().rename(position, "dummy")?;
468     let range = match optional_change {
469         None => return Ok(None),
470         Some(it) => it.range,
471     };
472
473     let file_id = params.text_document.try_conv_with(&world)?;
474     let line_index = world.analysis().file_line_index(file_id)?;
475     let range = range.conv_with(&line_index);
476     Ok(Some(PrepareRenameResponse::Range(range)))
477 }
478
479 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
480     let _p = profile("handle_rename");
481     let position = params.text_document_position.try_conv_with(&world)?;
482
483     if params.new_name.is_empty() {
484         return Err(LspError::new(
485             ErrorCode::InvalidParams as i32,
486             "New Name cannot be empty".into(),
487         )
488         .into());
489     }
490
491     let optional_change = world.analysis().rename(position, &*params.new_name)?;
492     let change = match optional_change {
493         None => return Ok(None),
494         Some(it) => it.info,
495     };
496
497     let source_change_req = change.try_conv_with(&world)?;
498
499     Ok(Some(source_change_req.workspace_edit))
500 }
501
502 pub fn handle_references(
503     world: WorldSnapshot,
504     params: req::ReferenceParams,
505 ) -> Result<Option<Vec<Location>>> {
506     let _p = profile("handle_references");
507     let position = params.text_document_position.try_conv_with(&world)?;
508
509     let refs = match world.analysis().find_all_refs(position, None)? {
510         None => return Ok(None),
511         Some(refs) => refs,
512     };
513
514     let locations = if params.context.include_declaration {
515         refs.into_iter()
516             .filter_map(|r| {
517                 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
518                 to_location(r.file_id, r.range, &world, &line_index).ok()
519             })
520             .collect()
521     } else {
522         // Only iterate over the references if include_declaration was false
523         refs.references()
524             .iter()
525             .filter_map(|r| {
526                 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
527                 to_location(r.file_id, r.range, &world, &line_index).ok()
528             })
529             .collect()
530     };
531
532     Ok(Some(locations))
533 }
534
535 pub fn handle_formatting(
536     world: WorldSnapshot,
537     params: DocumentFormattingParams,
538 ) -> Result<Option<Vec<TextEdit>>> {
539     let _p = profile("handle_formatting");
540     let file_id = params.text_document.try_conv_with(&world)?;
541     let file = world.analysis().file_text(file_id)?;
542
543     let file_line_index = world.analysis().file_line_index(file_id)?;
544     let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
545
546     use std::process;
547     let mut rustfmt = process::Command::new("rustfmt");
548     rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped());
549
550     if let Ok(path) = params.text_document.uri.to_file_path() {
551         if let Some(parent) = path.parent() {
552             rustfmt.current_dir(parent);
553         }
554     }
555     let mut rustfmt = rustfmt.spawn()?;
556
557     rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
558
559     let output = rustfmt.wait_with_output()?;
560     let captured_stdout = String::from_utf8(output.stdout)?;
561
562     if !output.status.success() {
563         match output.status.code() {
564             Some(1) => {
565                 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
566                 // likely cause exiting with 1. Most Language Servers swallow parse errors on
567                 // formatting because otherwise an error is surfaced to the user on top of the
568                 // syntax error diagnostics they're already receiving. This is especially jarring
569                 // if they have format on save enabled.
570                 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
571                 return Ok(None);
572             }
573             _ => {
574                 // Something else happened - e.g. `rustfmt` is missing or caught a signal
575                 return Err(LspError::new(
576                     -32900,
577                     format!(
578                         r#"rustfmt exited with:
579                            Status: {}
580                            stdout: {}"#,
581                         output.status, captured_stdout,
582                     ),
583                 )
584                 .into());
585             }
586         }
587     }
588
589     Ok(Some(vec![TextEdit {
590         range: Range::new(Position::new(0, 0), end_position),
591         new_text: captured_stdout,
592     }]))
593 }
594
595 pub fn handle_code_action(
596     world: WorldSnapshot,
597     params: req::CodeActionParams,
598 ) -> Result<Option<CodeActionResponse>> {
599     let _p = profile("handle_code_action");
600     let file_id = params.text_document.try_conv_with(&world)?;
601     let line_index = world.analysis().file_line_index(file_id)?;
602     let range = params.range.conv_with(&line_index);
603
604     let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
605     let diagnostics = world.analysis().diagnostics(file_id)?;
606     let mut res = CodeActionResponse::default();
607
608     let fixes_from_diagnostics = diagnostics
609         .into_iter()
610         .filter_map(|d| Some((d.range, d.fix?)))
611         .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
612         .map(|(_range, fix)| fix);
613
614     for source_edit in fixes_from_diagnostics {
615         let title = source_edit.label.clone();
616         let edit = source_edit.try_conv_with(&world)?;
617
618         let command = Command {
619             title,
620             command: "rust-analyzer.applySourceChange".to_string(),
621             arguments: Some(vec![to_value(edit).unwrap()]),
622         };
623         let action = CodeAction {
624             title: command.title.clone(),
625             kind: None,
626             diagnostics: None,
627             edit: None,
628             command: Some(command),
629         };
630         res.push(action.into());
631     }
632
633     for assist in assists {
634         let title = assist.change.label.clone();
635         let edit = assist.change.try_conv_with(&world)?;
636
637         let command = Command {
638             title,
639             command: "rust-analyzer.applySourceChange".to_string(),
640             arguments: Some(vec![to_value(edit).unwrap()]),
641         };
642         let action = CodeAction {
643             title: command.title.clone(),
644             kind: match assist.id {
645                 AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
646                 _ => None,
647             },
648             diagnostics: None,
649             edit: None,
650             command: Some(command),
651         };
652         res.push(action.into());
653     }
654
655     Ok(Some(res))
656 }
657
658 pub fn handle_code_lens(
659     world: WorldSnapshot,
660     params: req::CodeLensParams,
661 ) -> Result<Option<Vec<CodeLens>>> {
662     let _p = profile("handle_code_lens");
663     let file_id = params.text_document.try_conv_with(&world)?;
664     let line_index = world.analysis().file_line_index(file_id)?;
665
666     let mut lenses: Vec<CodeLens> = Default::default();
667
668     // Gather runnables
669     for runnable in world.analysis().runnables(file_id)? {
670         let title = match &runnable.kind {
671             RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️Run Test",
672             RunnableKind::Bench { .. } => "Run Bench",
673             RunnableKind::Bin => "Run",
674         }
675         .to_string();
676         let r = to_lsp_runnable(&world, file_id, runnable)?;
677         let lens = CodeLens {
678             range: r.range,
679             command: Some(Command {
680                 title,
681                 command: "rust-analyzer.runSingle".into(),
682                 arguments: Some(vec![to_value(r).unwrap()]),
683             }),
684             data: None,
685         };
686
687         lenses.push(lens);
688     }
689
690     // Handle impls
691     lenses.extend(
692         world
693             .analysis()
694             .file_structure(file_id)?
695             .into_iter()
696             .filter(|it| match it.kind {
697                 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
698                 _ => false,
699             })
700             .map(|it| {
701                 let range = it.node_range.conv_with(&line_index);
702                 let pos = range.start;
703                 let lens_params =
704                     req::TextDocumentPositionParams::new(params.text_document.clone(), pos);
705                 CodeLens {
706                     range,
707                     command: None,
708                     data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
709                 }
710             }),
711     );
712
713     Ok(Some(lenses))
714 }
715
716 #[derive(Debug, Serialize, Deserialize)]
717 #[serde(rename_all = "camelCase")]
718 enum CodeLensResolveData {
719     Impls(req::TextDocumentPositionParams),
720 }
721
722 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
723     let _p = profile("handle_code_lens_resolve");
724     let data = code_lens.data.unwrap();
725     let resolve = serde_json::from_value(data)?;
726     match resolve {
727         Some(CodeLensResolveData::Impls(lens_params)) => {
728             let locations: Vec<Location> =
729                 match handle_goto_implementation(world, lens_params.clone())? {
730                     Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
731                     Some(req::GotoDefinitionResponse::Array(locs)) => locs,
732                     Some(req::GotoDefinitionResponse::Link(links)) => links
733                         .into_iter()
734                         .map(|link| Location::new(link.target_uri, link.target_selection_range))
735                         .collect(),
736                     _ => vec![],
737                 };
738
739             let title = if locations.len() == 1 {
740                 "1 implementation".into()
741             } else {
742                 format!("{} implementations", locations.len())
743             };
744
745             // We cannot use the 'editor.action.showReferences' command directly
746             // because that command requires vscode types which we convert in the handler
747             // on the client side.
748             let cmd = Command {
749                 title,
750                 command: "rust-analyzer.showReferences".into(),
751                 arguments: Some(vec![
752                     to_value(&lens_params.text_document.uri).unwrap(),
753                     to_value(code_lens.range.start).unwrap(),
754                     to_value(locations).unwrap(),
755                 ]),
756             };
757             Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
758         }
759         None => Ok(CodeLens {
760             range: code_lens.range,
761             command: Some(Command { title: "Error".into(), ..Default::default() }),
762             data: None,
763         }),
764     }
765 }
766
767 pub fn handle_document_highlight(
768     world: WorldSnapshot,
769     params: req::TextDocumentPositionParams,
770 ) -> Result<Option<Vec<DocumentHighlight>>> {
771     let _p = profile("handle_document_highlight");
772     let file_id = params.text_document.try_conv_with(&world)?;
773     let line_index = world.analysis().file_line_index(file_id)?;
774
775     let refs = match world
776         .analysis()
777         .find_all_refs(params.try_conv_with(&world)?, Some(SearchScope::single_file(file_id)))?
778     {
779         None => return Ok(None),
780         Some(refs) => refs,
781     };
782
783     Ok(Some(
784         refs.into_iter()
785             .filter(|r| r.file_id == file_id)
786             .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
787             .collect(),
788     ))
789 }
790
791 pub fn publish_diagnostics(
792     world: &WorldSnapshot,
793     file_id: FileId,
794 ) -> Result<req::PublishDiagnosticsParams> {
795     let _p = profile("publish_diagnostics");
796     let uri = world.file_id_to_uri(file_id)?;
797     let line_index = world.analysis().file_line_index(file_id)?;
798     let diagnostics = world
799         .analysis()
800         .diagnostics(file_id)?
801         .into_iter()
802         .map(|d| Diagnostic {
803             range: d.range.conv_with(&line_index),
804             severity: Some(d.severity.conv()),
805             code: None,
806             source: Some("rust-analyzer".to_string()),
807             message: d.message,
808             related_information: None,
809         })
810         .collect();
811     Ok(req::PublishDiagnosticsParams { uri, diagnostics })
812 }
813
814 pub fn publish_decorations(
815     world: &WorldSnapshot,
816     file_id: FileId,
817 ) -> Result<req::PublishDecorationsParams> {
818     let _p = profile("publish_decorations");
819     let uri = world.file_id_to_uri(file_id)?;
820     Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
821 }
822
823 fn to_lsp_runnable(
824     world: &WorldSnapshot,
825     file_id: FileId,
826     runnable: Runnable,
827 ) -> Result<req::Runnable> {
828     let args = runnable_args(world, file_id, &runnable.kind)?;
829     let line_index = world.analysis().file_line_index(file_id)?;
830     let label = match &runnable.kind {
831         RunnableKind::Test { name } => format!("test {}", name),
832         RunnableKind::TestMod { path } => format!("test-mod {}", path),
833         RunnableKind::Bench { name } => format!("bench {}", name),
834         RunnableKind::Bin => "run binary".to_string(),
835     };
836     Ok(req::Runnable {
837         range: runnable.range.conv_with(&line_index),
838         label,
839         bin: "cargo".to_string(),
840         args,
841         env: {
842             let mut m = FxHashMap::default();
843             m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
844             m
845         },
846         cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
847     })
848 }
849 fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
850     let line_index = world.analysis().file_line_index(file_id)?;
851     let res = world
852         .analysis()
853         .highlight(file_id)?
854         .into_iter()
855         .map(|h| Decoration {
856             range: h.range.conv_with(&line_index),
857             tag: h.tag,
858             binding_hash: h.binding_hash.map(|x| x.to_string()),
859         })
860         .collect();
861     Ok(res)
862 }
863
864 pub fn handle_inlay_hints(
865     world: WorldSnapshot,
866     params: InlayHintsParams,
867 ) -> Result<Vec<InlayHint>> {
868     let _p = profile("handle_inlay_hints");
869     let file_id = params.text_document.try_conv_with(&world)?;
870     let analysis = world.analysis();
871     let line_index = analysis.file_line_index(file_id)?;
872     Ok(analysis
873         .inlay_hints(file_id, world.options.max_inlay_hint_length)?
874         .into_iter()
875         .map(|api_type| InlayHint {
876             label: api_type.label.to_string(),
877             range: api_type.range.conv_with(&line_index),
878             kind: match api_type.kind {
879                 ra_ide_api::InlayKind::TypeHint => InlayKind::TypeHint,
880             },
881         })
882         .collect())
883 }