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