1 //! This module is responsible for implementing handlers for Language Server
2 //! Protocol. The majority of requests are fulfilled by calling into the
7 process::{self, Stdio},
10 use lsp_server::ErrorCode;
12 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
13 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
14 CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
15 DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location,
16 MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams,
17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
21 FileId, FilePosition, FileRange, Query, RangeInfo, RunnableKind, SearchScope, TextEdit,
24 use ra_project_model::TargetKind;
25 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize};
26 use serde::{Deserialize, Serialize};
27 use serde_json::to_value;
31 cargo_target_spec::CargoTargetSpec,
32 config::RustfmtConfig,
33 diagnostics::DiagnosticTask,
34 from_json, from_proto,
35 lsp_ext::{self, InlayHint, InlayHintsParams},
41 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
42 let _p = profile("handle_analyzer_status");
43 let mut buf = world.status();
44 format_to!(buf, "\n\nrequests:\n");
45 let requests = world.latest_requests.read();
46 for (is_last, r) in requests.iter() {
47 let mark = if is_last { "*" } else { " " };
48 format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis());
53 pub fn handle_syntax_tree(
55 params: lsp_ext::SyntaxTreeParams,
57 let _p = profile("handle_syntax_tree");
58 let id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
59 let line_index = world.analysis().file_line_index(id)?;
60 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
61 let res = world.analysis().syntax_tree(id, text_range)?;
65 pub fn handle_expand_macro(
67 params: lsp_ext::ExpandMacroParams,
68 ) -> Result<Option<lsp_ext::ExpandedMacro>> {
69 let _p = profile("handle_expand_macro");
70 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
71 let line_index = world.analysis().file_line_index(file_id)?;
72 let offset = from_proto::offset(&line_index, params.position);
74 let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
75 Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
78 pub fn handle_selection_range(
80 params: lsp_types::SelectionRangeParams,
81 ) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
82 let _p = profile("handle_selection_range");
83 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
84 let line_index = world.analysis().file_line_index(file_id)?;
85 let res: Result<Vec<lsp_types::SelectionRange>> = params
89 let offset = from_proto::offset(&line_index, position);
90 let mut ranges = Vec::new();
92 let mut range = TextRange::new(offset, offset);
95 let frange = FileRange { file_id, range };
96 let next = world.analysis().extend_selection(frange)?;
104 let mut range = lsp_types::SelectionRange {
105 range: to_proto::range(&line_index, *ranges.last().unwrap()),
108 for &r in ranges.iter().rev().skip(1) {
109 range = lsp_types::SelectionRange {
110 range: to_proto::range(&line_index, r),
111 parent: Some(Box::new(range)),
121 pub fn handle_matching_brace(
122 world: WorldSnapshot,
123 params: lsp_ext::MatchingBraceParams,
124 ) -> Result<Vec<Position>> {
125 let _p = profile("handle_matching_brace");
126 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
127 let line_index = world.analysis().file_line_index(file_id)?;
132 let offset = from_proto::offset(&line_index, position);
133 let offset = match world.analysis().matching_brace(FilePosition { file_id, offset }) {
134 Ok(Some(matching_brace_offset)) => matching_brace_offset,
135 Err(_) | Ok(None) => offset,
137 to_proto::position(&line_index, offset)
143 pub fn handle_join_lines(
144 world: WorldSnapshot,
145 params: lsp_ext::JoinLinesParams,
146 ) -> Result<Vec<lsp_types::TextEdit>> {
147 let _p = profile("handle_join_lines");
148 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
149 let line_index = world.analysis().file_line_index(file_id)?;
150 let line_endings = world.file_line_endings(file_id);
151 let mut res = TextEdit::default();
152 for range in params.ranges {
153 let range = from_proto::text_range(&line_index, range);
154 let edit = world.analysis().join_lines(FileRange { file_id, range })?;
155 match res.union(edit) {
158 // just ignore overlapping edits
162 let res = to_proto::text_edit_vec(&line_index, line_endings, res);
166 pub fn handle_on_enter(
167 world: WorldSnapshot,
168 params: lsp_types::TextDocumentPositionParams,
169 ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
170 let _p = profile("handle_on_enter");
171 let position = from_proto::file_position(&world, params)?;
172 let edit = match world.analysis().on_enter(position)? {
173 None => return Ok(None),
176 let line_index = world.analysis().file_line_index(position.file_id)?;
177 let line_endings = world.file_line_endings(position.file_id);
178 let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
182 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
183 pub fn handle_on_type_formatting(
184 world: WorldSnapshot,
185 params: lsp_types::DocumentOnTypeFormattingParams,
186 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
187 let _p = profile("handle_on_type_formatting");
188 let mut position = from_proto::file_position(&world, params.text_document_position)?;
189 let line_index = world.analysis().file_line_index(position.file_id)?;
190 let line_endings = world.file_line_endings(position.file_id);
192 // in `ra_ide`, the `on_type` invariant is that
193 // `text.char_at(position) == typed_char`.
194 position.offset -= TextSize::of('.');
195 let char_typed = params.ch.chars().next().unwrap_or('\0');
197 let text = world.analysis().file_text(position.file_id)?;
198 text[usize::from(position.offset)..].starts_with(char_typed)
201 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
202 // but it requires precise cursor positioning to work, and one can't
203 // position the cursor with on_type formatting. So, let's just toggle this
204 // feature off here, hoping that we'll enable it one day, 😿.
205 if char_typed == '>' {
209 let edit = world.analysis().on_char_typed(position, char_typed)?;
210 let mut edit = match edit {
212 None => return Ok(None),
215 // This should be a single-file edit
216 let edit = edit.source_file_edits.pop().unwrap();
218 let change = to_proto::text_edit_vec(&line_index, line_endings, edit.edit);
222 pub fn handle_document_symbol(
223 world: WorldSnapshot,
224 params: lsp_types::DocumentSymbolParams,
225 ) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
226 let _p = profile("handle_document_symbol");
227 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
228 let line_index = world.analysis().file_line_index(file_id)?;
230 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
232 for symbol in world.analysis().file_structure(file_id)? {
233 let doc_symbol = DocumentSymbol {
235 detail: symbol.detail,
236 kind: to_proto::symbol_kind(symbol.kind),
237 deprecated: Some(symbol.deprecated),
238 range: to_proto::range(&line_index, symbol.node_range),
239 selection_range: to_proto::range(&line_index, symbol.navigation_range),
242 parents.push((doc_symbol, symbol.parent));
244 let mut document_symbols = Vec::new();
245 while let Some((node, parent)) = parents.pop() {
247 None => document_symbols.push(node),
249 let children = &mut parents[i].0.children;
250 if children.is_none() {
251 *children = Some(Vec::new());
253 children.as_mut().unwrap().push(node);
258 let res = if world.config.client_caps.hierarchical_symbols {
259 document_symbols.into()
261 let url = to_proto::url(&world, file_id)?;
262 let mut symbol_information = Vec::<SymbolInformation>::new();
263 for symbol in document_symbols {
264 flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
266 symbol_information.into()
268 return Ok(Some(res));
270 fn flatten_document_symbol(
271 symbol: &DocumentSymbol,
272 container_name: Option<String>,
274 res: &mut Vec<SymbolInformation>,
276 res.push(SymbolInformation {
277 name: symbol.name.clone(),
279 deprecated: symbol.deprecated,
280 location: Location::new(url.clone(), symbol.range),
284 for child in symbol.children.iter().flatten() {
285 flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
290 pub fn handle_workspace_symbol(
291 world: WorldSnapshot,
292 params: lsp_types::WorkspaceSymbolParams,
293 ) -> Result<Option<Vec<SymbolInformation>>> {
294 let _p = profile("handle_workspace_symbol");
295 let all_symbols = params.query.contains('#');
296 let libs = params.query.contains('*');
298 let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
299 let mut q = Query::new(query);
309 let mut res = exec_query(&world, query)?;
310 if res.is_empty() && !all_symbols {
311 let mut query = Query::new(params.query);
313 res = exec_query(&world, query)?;
316 return Ok(Some(res));
318 fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
319 let mut res = Vec::new();
320 for nav in world.analysis().symbol_search(query)? {
321 let info = SymbolInformation {
322 name: nav.name().to_string(),
323 kind: to_proto::symbol_kind(nav.kind()),
324 location: to_proto::location(world, nav.file_range())?,
325 container_name: nav.container_name().map(|v| v.to_string()),
334 pub fn handle_goto_definition(
335 world: WorldSnapshot,
336 params: lsp_types::GotoDefinitionParams,
337 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
338 let _p = profile("handle_goto_definition");
339 let position = from_proto::file_position(&world, params.text_document_position_params)?;
340 let nav_info = match world.analysis().goto_definition(position)? {
341 None => return Ok(None),
344 let src = FileRange { file_id: position.file_id, range: nav_info.range };
345 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
349 pub fn handle_goto_implementation(
350 world: WorldSnapshot,
351 params: lsp_types::request::GotoImplementationParams,
352 ) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
353 let _p = profile("handle_goto_implementation");
354 let position = from_proto::file_position(&world, params.text_document_position_params)?;
355 let nav_info = match world.analysis().goto_implementation(position)? {
356 None => return Ok(None),
359 let src = FileRange { file_id: position.file_id, range: nav_info.range };
360 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
364 pub fn handle_goto_type_definition(
365 world: WorldSnapshot,
366 params: lsp_types::request::GotoTypeDefinitionParams,
367 ) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
368 let _p = profile("handle_goto_type_definition");
369 let position = from_proto::file_position(&world, params.text_document_position_params)?;
370 let nav_info = match world.analysis().goto_type_definition(position)? {
371 None => return Ok(None),
374 let src = FileRange { file_id: position.file_id, range: nav_info.range };
375 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?;
379 pub fn handle_parent_module(
380 world: WorldSnapshot,
381 params: lsp_types::TextDocumentPositionParams,
382 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
383 let _p = profile("handle_parent_module");
384 let position = from_proto::file_position(&world, params)?;
385 let navs = world.analysis().parent_module(position)?;
386 let res = to_proto::goto_definition_response(&world, None, navs)?;
390 pub fn handle_runnables(
391 world: WorldSnapshot,
392 params: lsp_ext::RunnablesParams,
393 ) -> Result<Vec<lsp_ext::Runnable>> {
394 let _p = profile("handle_runnables");
395 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
396 let line_index = world.analysis().file_line_index(file_id)?;
397 let offset = params.position.map(|it| from_proto::offset(&line_index, it));
398 let mut res = Vec::new();
399 let workspace_root = world.workspace_root_for(file_id);
400 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
401 for runnable in world.analysis().runnables(file_id)? {
402 if let Some(offset) = offset {
403 if !runnable.nav.full_range().contains_inclusive(offset) {
407 // Do not suggest binary run on other target than binary
408 if let RunnableKind::Bin = runnable.kind {
409 if let Some(spec) = &cargo_spec {
410 match spec.target_kind {
411 TargetKind::Bin => {}
416 res.push(to_proto::runnable(&world, file_id, runnable)?);
419 // Add `cargo check` and `cargo test` for the whole package
422 for &cmd in ["check", "test"].iter() {
423 res.push(lsp_ext::Runnable {
424 label: format!("cargo {} -p {}", cmd, spec.package),
426 kind: lsp_ext::RunnableKind::Cargo,
427 args: lsp_ext::CargoRunnable {
428 workspace_root: workspace_root.map(|root| root.to_owned()),
431 "--package".to_string(),
432 spec.package.clone(),
434 executable_args: Vec::new(),
440 res.push(lsp_ext::Runnable {
441 label: "cargo check --workspace".to_string(),
443 kind: lsp_ext::RunnableKind::Cargo,
444 args: lsp_ext::CargoRunnable {
445 workspace_root: workspace_root.map(|root| root.to_owned()),
446 cargo_args: vec!["check".to_string(), "--workspace".to_string()],
447 executable_args: Vec::new(),
455 pub fn handle_completion(
456 world: WorldSnapshot,
457 params: lsp_types::CompletionParams,
458 ) -> Result<Option<lsp_types::CompletionResponse>> {
459 let _p = profile("handle_completion");
460 let position = from_proto::file_position(&world, params.text_document_position)?;
461 let completion_triggered_after_single_colon = {
463 if let Some(ctx) = params.context {
464 if ctx.trigger_character.unwrap_or_default() == ":" {
465 let source_file = world.analysis().parse(position.file_id)?;
466 let syntax = source_file.syntax();
467 let text = syntax.text();
468 if let Some(next_char) = text.char_at(position.offset) {
469 let diff = TextSize::of(next_char) + TextSize::of(':');
470 let prev_char = position.offset - diff;
471 if text.char_at(prev_char) != Some(':') {
479 if completion_triggered_after_single_colon {
483 let items = match world.analysis().completions(&world.config.completion, position)? {
484 None => return Ok(None),
485 Some(items) => items,
487 let line_index = world.analysis().file_line_index(position.file_id)?;
488 let line_endings = world.file_line_endings(position.file_id);
489 let items: Vec<CompletionItem> = items
491 .map(|item| to_proto::completion_item(&line_index, line_endings, item))
494 Ok(Some(items.into()))
497 pub fn handle_folding_range(
498 world: WorldSnapshot,
499 params: FoldingRangeParams,
500 ) -> Result<Option<Vec<FoldingRange>>> {
501 let _p = profile("handle_folding_range");
502 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
503 let folds = world.analysis().folding_ranges(file_id)?;
504 let text = world.analysis().file_text(file_id)?;
505 let line_index = world.analysis().file_line_index(file_id)?;
506 let line_folding_only = world.config.client_caps.line_folding_only;
509 .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
514 pub fn handle_signature_help(
515 world: WorldSnapshot,
516 params: lsp_types::SignatureHelpParams,
517 ) -> Result<Option<lsp_types::SignatureHelp>> {
518 let _p = profile("handle_signature_help");
519 let position = from_proto::file_position(&world, params.text_document_position_params)?;
520 let call_info = match world.analysis().call_info(position)? {
521 None => return Ok(None),
524 let concise = !world.config.call_info_full;
525 let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
526 if concise && call_info.signature.has_self_param {
527 active_parameter = active_parameter.map(|it| it.saturating_sub(1));
529 let sig_info = to_proto::signature_information(call_info.signature, concise);
531 Ok(Some(lsp_types::SignatureHelp {
532 signatures: vec![sig_info],
533 active_signature: Some(0),
538 pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result<Option<Hover>> {
539 let _p = profile("handle_hover");
540 let position = from_proto::file_position(&world, params.text_document_position_params)?;
541 let info = match world.analysis().hover(position)? {
542 None => return Ok(None),
545 let line_index = world.analysis.file_line_index(position.file_id)?;
546 let range = to_proto::range(&line_index, info.range);
548 contents: HoverContents::Markup(MarkupContent {
549 kind: MarkupKind::Markdown,
550 value: crate::markdown::format_docs(&info.info.to_markup()),
557 pub fn handle_prepare_rename(
558 world: WorldSnapshot,
559 params: lsp_types::TextDocumentPositionParams,
560 ) -> Result<Option<PrepareRenameResponse>> {
561 let _p = profile("handle_prepare_rename");
562 let position = from_proto::file_position(&world, params)?;
564 let optional_change = world.analysis().rename(position, "dummy")?;
565 let range = match optional_change {
566 None => return Ok(None),
567 Some(it) => it.range,
570 let line_index = world.analysis().file_line_index(position.file_id)?;
571 let range = to_proto::range(&line_index, range);
572 Ok(Some(PrepareRenameResponse::Range(range)))
575 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
576 let _p = profile("handle_rename");
577 let position = from_proto::file_position(&world, params.text_document_position)?;
579 if params.new_name.is_empty() {
580 return Err(LspError::new(
581 ErrorCode::InvalidParams as i32,
582 "New Name cannot be empty".into(),
587 let optional_change = world.analysis().rename(position, &*params.new_name)?;
588 let source_change = match optional_change {
589 None => return Ok(None),
592 let workspace_edit = to_proto::workspace_edit(&world, source_change)?;
593 Ok(Some(workspace_edit))
596 pub fn handle_references(
597 world: WorldSnapshot,
598 params: lsp_types::ReferenceParams,
599 ) -> Result<Option<Vec<Location>>> {
600 let _p = profile("handle_references");
601 let position = from_proto::file_position(&world, params.text_document_position)?;
603 let refs = match world.analysis().find_all_refs(position, None)? {
604 None => return Ok(None),
608 let locations = if params.context.include_declaration {
610 .filter_map(|reference| to_proto::location(&world, reference.file_range).ok())
613 // Only iterate over the references if include_declaration was false
616 .filter_map(|reference| to_proto::location(&world, reference.file_range).ok())
623 pub fn handle_formatting(
624 world: WorldSnapshot,
625 params: DocumentFormattingParams,
626 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
627 let _p = profile("handle_formatting");
628 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
629 let file = world.analysis().file_text(file_id)?;
630 let crate_ids = world.analysis().crate_for(file_id)?;
632 let file_line_index = world.analysis().file_line_index(file_id)?;
633 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
635 let mut rustfmt = match &world.config.rustfmt {
636 RustfmtConfig::Rustfmt { extra_args } => {
637 let mut cmd = process::Command::new("rustfmt");
638 cmd.args(extra_args);
639 if let Some(&crate_id) = crate_ids.first() {
640 // Assume all crates are in the same edition
641 let edition = world.analysis().crate_edition(crate_id)?;
642 cmd.arg("--edition");
643 cmd.arg(edition.to_string());
647 RustfmtConfig::CustomCommand { command, args } => {
648 let mut cmd = process::Command::new(command);
654 if let Ok(path) = params.text_document.uri.to_file_path() {
655 if let Some(parent) = path.parent() {
656 rustfmt.current_dir(parent);
659 let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
661 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
663 let output = rustfmt.wait_with_output()?;
664 let captured_stdout = String::from_utf8(output.stdout)?;
666 if !output.status.success() {
667 match output.status.code() {
669 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
670 // likely cause exiting with 1. Most Language Servers swallow parse errors on
671 // formatting because otherwise an error is surfaced to the user on top of the
672 // syntax error diagnostics they're already receiving. This is especially jarring
673 // if they have format on save enabled.
674 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
678 // Something else happened - e.g. `rustfmt` is missing or caught a signal
679 return Err(LspError::new(
682 r#"rustfmt exited with:
685 output.status, captured_stdout,
693 Ok(Some(vec![lsp_types::TextEdit {
694 range: Range::new(Position::new(0, 0), end_position),
695 new_text: captured_stdout,
699 pub fn handle_code_action(
700 world: WorldSnapshot,
701 params: lsp_types::CodeActionParams,
702 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
703 let _p = profile("handle_code_action");
704 // We intentionally don't support command-based actions, as those either
705 // requires custom client-code anyway, or requires server-initiated edits.
706 // Server initiated edits break causality, so we avoid those as well.
707 if !world.config.client_caps.code_action_literals {
711 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
712 let line_index = world.analysis().file_line_index(file_id)?;
713 let range = from_proto::text_range(&line_index, params.range);
714 let frange = FileRange { file_id, range };
716 let diagnostics = world.analysis().diagnostics(file_id)?;
717 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
719 let fixes_from_diagnostics = diagnostics
721 .filter_map(|d| Some((d.range, d.fix?)))
722 .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
723 .map(|(_range, fix)| fix);
725 for fix in fixes_from_diagnostics {
726 let title = fix.label;
727 let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?;
729 lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None };
733 for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
734 let fix_range = from_proto::text_range(&line_index, fix.range);
735 if fix_range.intersect(range).is_none() {
738 res.push(fix.action.clone());
741 for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() {
742 res.push(to_proto::code_action(&world, assist)?.into());
747 pub fn handle_code_lens(
748 world: WorldSnapshot,
749 params: lsp_types::CodeLensParams,
750 ) -> Result<Option<Vec<CodeLens>>> {
751 let _p = profile("handle_code_lens");
752 let mut lenses: Vec<CodeLens> = Default::default();
754 if world.config.lens.none() {
755 // early return before any db query!
756 return Ok(Some(lenses));
759 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
760 let line_index = world.analysis().file_line_index(file_id)?;
761 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
763 if world.config.lens.runnable() {
765 for runnable in world.analysis().runnables(file_id)? {
766 let (run_title, debugee) = match &runnable.kind {
767 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
768 ("â–¶\u{fe0e} Run Test", true)
770 RunnableKind::DocTest { .. } => {
771 // cargo does not support -no-run for doctests
772 ("â–¶\u{fe0e} Run Doctest", false)
774 RunnableKind::Bench { .. } => {
775 // Nothing wrong with bench debugging
778 RunnableKind::Bin => {
779 // Do not suggest binary run on other target than binary
781 Some(spec) => match spec.target_kind {
782 TargetKind::Bin => ("Run", true),
790 let range = to_proto::range(&line_index, runnable.nav.range());
791 let r = to_proto::runnable(&world, file_id, runnable)?;
792 if world.config.lens.run {
793 let lens = CodeLens {
795 command: Some(Command {
796 title: run_title.to_string(),
797 command: "rust-analyzer.runSingle".into(),
798 arguments: Some(vec![to_value(&r).unwrap()]),
805 if debugee && world.config.lens.debug {
806 let debug_lens = CodeLens {
808 command: Some(Command {
809 title: "Debug".into(),
810 command: "rust-analyzer.debugSingle".into(),
811 arguments: Some(vec![to_value(r).unwrap()]),
815 lenses.push(debug_lens);
820 if world.config.lens.impementations {
825 .file_structure(file_id)?
827 .filter(|it| match it.kind {
828 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
832 let range = to_proto::range(&line_index, it.node_range);
833 let pos = range.start;
834 let lens_params = lsp_types::request::GotoImplementationParams {
835 text_document_position_params: lsp_types::TextDocumentPositionParams::new(
836 params.text_document.clone(),
839 work_done_progress_params: Default::default(),
840 partial_result_params: Default::default(),
845 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
853 #[derive(Debug, Serialize, Deserialize)]
854 #[serde(rename_all = "camelCase")]
855 enum CodeLensResolveData {
856 Impls(lsp_types::request::GotoImplementationParams),
859 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
860 let _p = profile("handle_code_lens_resolve");
861 let data = code_lens.data.unwrap();
862 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
864 Some(CodeLensResolveData::Impls(lens_params)) => {
865 let locations: Vec<Location> =
866 match handle_goto_implementation(world, lens_params.clone())? {
867 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
868 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
869 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
871 .map(|link| Location::new(link.target_uri, link.target_selection_range))
876 let title = if locations.len() == 1 {
877 "1 implementation".into()
879 format!("{} implementations", locations.len())
882 // We cannot use the 'editor.action.showReferences' command directly
883 // because that command requires vscode types which we convert in the handler
884 // on the client side.
887 command: "rust-analyzer.showReferences".into(),
888 arguments: Some(vec![
889 to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
890 to_value(code_lens.range.start).unwrap(),
891 to_value(locations).unwrap(),
894 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
896 None => Ok(CodeLens {
897 range: code_lens.range,
898 command: Some(Command { title: "Error".into(), ..Default::default() }),
904 pub fn handle_document_highlight(
905 world: WorldSnapshot,
906 params: lsp_types::DocumentHighlightParams,
907 ) -> Result<Option<Vec<DocumentHighlight>>> {
908 let _p = profile("handle_document_highlight");
909 let position = from_proto::file_position(&world, params.text_document_position_params)?;
910 let line_index = world.analysis().file_line_index(position.file_id)?;
912 let refs = match world
914 .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
916 None => return Ok(None),
922 .filter(|reference| reference.file_range.file_id == position.file_id)
923 .map(|reference| DocumentHighlight {
924 range: to_proto::range(&line_index, reference.file_range.range),
925 kind: reference.access.map(to_proto::document_highlight_kind),
932 world: WorldSnapshot,
933 params: lsp_ext::SsrParams,
934 ) -> Result<lsp_types::WorkspaceEdit> {
935 let _p = profile("handle_ssr");
937 world.analysis().structural_search_replace(¶ms.query, params.parse_only)??;
938 to_proto::workspace_edit(&world, source_change)
941 pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
942 let _p = profile("publish_diagnostics");
943 let line_index = world.analysis().file_line_index(file_id)?;
944 let diagnostics: Vec<Diagnostic> = world
946 .diagnostics(file_id)?
948 .map(|d| Diagnostic {
949 range: to_proto::range(&line_index, d.range),
950 severity: Some(to_proto::diagnostic_severity(d.severity)),
952 source: Some("rust-analyzer".to_string()),
954 related_information: None,
958 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
961 pub fn handle_inlay_hints(
962 world: WorldSnapshot,
963 params: InlayHintsParams,
964 ) -> Result<Vec<InlayHint>> {
965 let _p = profile("handle_inlay_hints");
966 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
967 let analysis = world.analysis();
968 let line_index = analysis.file_line_index(file_id)?;
970 .inlay_hints(file_id, &world.config.inlay_hints)?
972 .map(|it| to_proto::inlay_int(&line_index, it))
976 pub fn handle_call_hierarchy_prepare(
977 world: WorldSnapshot,
978 params: CallHierarchyPrepareParams,
979 ) -> Result<Option<Vec<CallHierarchyItem>>> {
980 let _p = profile("handle_call_hierarchy_prepare");
981 let position = from_proto::file_position(&world, params.text_document_position_params)?;
983 let nav_info = match world.analysis().call_hierarchy(position)? {
984 None => return Ok(None),
988 let RangeInfo { range: _, info: navs } = nav_info;
991 .filter(|it| it.kind() == SyntaxKind::FN_DEF)
992 .map(|it| to_proto::call_hierarchy_item(&world, it))
993 .collect::<Result<Vec<_>>>()?;
998 pub fn handle_call_hierarchy_incoming(
999 world: WorldSnapshot,
1000 params: CallHierarchyIncomingCallsParams,
1001 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1002 let _p = profile("handle_call_hierarchy_incoming");
1003 let item = params.item;
1005 let doc = TextDocumentIdentifier::new(item.uri);
1006 let frange = from_proto::file_range(&world, doc, item.range)?;
1007 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1009 let call_items = match world.analysis().incoming_calls(fpos)? {
1010 None => return Ok(None),
1014 let mut res = vec![];
1016 for call_item in call_items.into_iter() {
1017 let file_id = call_item.target.file_id();
1018 let line_index = world.analysis().file_line_index(file_id)?;
1019 let item = to_proto::call_hierarchy_item(&world, call_item.target)?;
1020 res.push(CallHierarchyIncomingCall {
1022 from_ranges: call_item
1025 .map(|it| to_proto::range(&line_index, it))
1033 pub fn handle_call_hierarchy_outgoing(
1034 world: WorldSnapshot,
1035 params: CallHierarchyOutgoingCallsParams,
1036 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1037 let _p = profile("handle_call_hierarchy_outgoing");
1038 let item = params.item;
1040 let doc = TextDocumentIdentifier::new(item.uri);
1041 let frange = from_proto::file_range(&world, doc, item.range)?;
1042 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1044 let call_items = match world.analysis().outgoing_calls(fpos)? {
1045 None => return Ok(None),
1049 let mut res = vec![];
1051 for call_item in call_items.into_iter() {
1052 let file_id = call_item.target.file_id();
1053 let line_index = world.analysis().file_line_index(file_id)?;
1054 let item = to_proto::call_hierarchy_item(&world, call_item.target)?;
1055 res.push(CallHierarchyOutgoingCall {
1057 from_ranges: call_item
1060 .map(|it| to_proto::range(&line_index, it))
1068 pub fn handle_semantic_tokens(
1069 world: WorldSnapshot,
1070 params: SemanticTokensParams,
1071 ) -> Result<Option<SemanticTokensResult>> {
1072 let _p = profile("handle_semantic_tokens");
1074 let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?;
1075 let text = world.analysis().file_text(file_id)?;
1076 let line_index = world.analysis().file_line_index(file_id)?;
1078 let highlights = world.analysis().highlight(file_id)?;
1079 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1080 Ok(Some(semantic_tokens.into()))
1083 pub fn handle_semantic_tokens_range(
1084 world: WorldSnapshot,
1085 params: SemanticTokensRangeParams,
1086 ) -> Result<Option<SemanticTokensRangeResult>> {
1087 let _p = profile("handle_semantic_tokens_range");
1089 let frange = from_proto::file_range(&world, params.text_document, params.range)?;
1090 let text = world.analysis().file_text(frange.file_id)?;
1091 let line_index = world.analysis().file_line_index(frange.file_id)?;
1093 let highlights = world.analysis().highlight_range(frange)?;
1094 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1095 Ok(Some(semantic_tokens.into()))