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