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 global_state::GlobalStateSnapshot,
36 lsp_ext::{self, InlayHint, InlayHintsParams},
37 to_proto, LspError, Result,
40 pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> {
41 let _p = profile("handle_analyzer_status");
42 let mut buf = snap.status();
43 format_to!(buf, "\n\nrequests:\n");
44 let requests = snap.latest_requests.read();
45 for (is_last, r) in requests.iter() {
46 let mark = if is_last { "*" } else { " " };
47 format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis());
52 pub fn handle_syntax_tree(
53 snap: GlobalStateSnapshot,
54 params: lsp_ext::SyntaxTreeParams,
56 let _p = profile("handle_syntax_tree");
57 let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
58 let line_index = snap.analysis().file_line_index(id)?;
59 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
60 let res = snap.analysis().syntax_tree(id, text_range)?;
64 pub fn handle_expand_macro(
65 snap: GlobalStateSnapshot,
66 params: lsp_ext::ExpandMacroParams,
67 ) -> Result<Option<lsp_ext::ExpandedMacro>> {
68 let _p = profile("handle_expand_macro");
69 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
70 let line_index = snap.analysis().file_line_index(file_id)?;
71 let offset = from_proto::offset(&line_index, params.position);
73 let res = snap.analysis().expand_macro(FilePosition { file_id, offset })?;
74 Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
77 pub fn handle_selection_range(
78 snap: GlobalStateSnapshot,
79 params: lsp_types::SelectionRangeParams,
80 ) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
81 let _p = profile("handle_selection_range");
82 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
83 let line_index = snap.analysis().file_line_index(file_id)?;
84 let res: Result<Vec<lsp_types::SelectionRange>> = params
88 let offset = from_proto::offset(&line_index, position);
89 let mut ranges = Vec::new();
91 let mut range = TextRange::new(offset, offset);
94 let frange = FileRange { file_id, range };
95 let next = snap.analysis().extend_selection(frange)?;
103 let mut range = lsp_types::SelectionRange {
104 range: to_proto::range(&line_index, *ranges.last().unwrap()),
107 for &r in ranges.iter().rev().skip(1) {
108 range = lsp_types::SelectionRange {
109 range: to_proto::range(&line_index, r),
110 parent: Some(Box::new(range)),
120 pub fn handle_matching_brace(
121 snap: GlobalStateSnapshot,
122 params: lsp_ext::MatchingBraceParams,
123 ) -> Result<Vec<Position>> {
124 let _p = profile("handle_matching_brace");
125 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
126 let line_index = snap.analysis().file_line_index(file_id)?;
131 let offset = from_proto::offset(&line_index, position);
132 let offset = match snap.analysis().matching_brace(FilePosition { file_id, offset }) {
133 Ok(Some(matching_brace_offset)) => matching_brace_offset,
134 Err(_) | Ok(None) => offset,
136 to_proto::position(&line_index, offset)
142 pub fn handle_join_lines(
143 snap: GlobalStateSnapshot,
144 params: lsp_ext::JoinLinesParams,
145 ) -> Result<Vec<lsp_types::TextEdit>> {
146 let _p = profile("handle_join_lines");
147 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
148 let line_index = snap.analysis().file_line_index(file_id)?;
149 let line_endings = snap.file_line_endings(file_id);
150 let mut res = TextEdit::default();
151 for range in params.ranges {
152 let range = from_proto::text_range(&line_index, range);
153 let edit = snap.analysis().join_lines(FileRange { file_id, range })?;
154 match res.union(edit) {
157 // just ignore overlapping edits
161 let res = to_proto::text_edit_vec(&line_index, line_endings, res);
165 pub fn handle_on_enter(
166 snap: GlobalStateSnapshot,
167 params: lsp_types::TextDocumentPositionParams,
168 ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
169 let _p = profile("handle_on_enter");
170 let position = from_proto::file_position(&snap, params)?;
171 let edit = match snap.analysis().on_enter(position)? {
172 None => return Ok(None),
175 let line_index = snap.analysis().file_line_index(position.file_id)?;
176 let line_endings = snap.file_line_endings(position.file_id);
177 let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
181 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
182 pub fn handle_on_type_formatting(
183 snap: GlobalStateSnapshot,
184 params: lsp_types::DocumentOnTypeFormattingParams,
185 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
186 let _p = profile("handle_on_type_formatting");
187 let mut position = from_proto::file_position(&snap, params.text_document_position)?;
188 let line_index = snap.analysis().file_line_index(position.file_id)?;
189 let line_endings = snap.file_line_endings(position.file_id);
191 // in `ra_ide`, the `on_type` invariant is that
192 // `text.char_at(position) == typed_char`.
193 position.offset -= TextSize::of('.');
194 let char_typed = params.ch.chars().next().unwrap_or('\0');
196 let text = snap.analysis().file_text(position.file_id)?;
197 text[usize::from(position.offset)..].starts_with(char_typed)
200 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
201 // but it requires precise cursor positioning to work, and one can't
202 // position the cursor with on_type formatting. So, let's just toggle this
203 // feature off here, hoping that we'll enable it one day, 😿.
204 if char_typed == '>' {
208 let edit = snap.analysis().on_char_typed(position, char_typed)?;
209 let mut edit = match edit {
211 None => return Ok(None),
214 // This should be a single-file edit
215 let edit = edit.source_file_edits.pop().unwrap();
217 let change = to_proto::text_edit_vec(&line_index, line_endings, edit.edit);
221 pub fn handle_document_symbol(
222 snap: GlobalStateSnapshot,
223 params: lsp_types::DocumentSymbolParams,
224 ) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
225 let _p = profile("handle_document_symbol");
226 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
227 let line_index = snap.analysis().file_line_index(file_id)?;
229 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
231 for symbol in snap.analysis().file_structure(file_id)? {
232 let doc_symbol = DocumentSymbol {
234 detail: symbol.detail,
235 kind: to_proto::symbol_kind(symbol.kind),
236 deprecated: Some(symbol.deprecated),
237 range: to_proto::range(&line_index, symbol.node_range),
238 selection_range: to_proto::range(&line_index, symbol.navigation_range),
241 parents.push((doc_symbol, symbol.parent));
243 let mut document_symbols = Vec::new();
244 while let Some((node, parent)) = parents.pop() {
246 None => document_symbols.push(node),
248 let children = &mut parents[i].0.children;
249 if children.is_none() {
250 *children = Some(Vec::new());
252 children.as_mut().unwrap().push(node);
257 let res = if snap.config.client_caps.hierarchical_symbols {
258 document_symbols.into()
260 let url = to_proto::url(&snap, file_id)?;
261 let mut symbol_information = Vec::<SymbolInformation>::new();
262 for symbol in document_symbols {
263 flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
265 symbol_information.into()
267 return Ok(Some(res));
269 fn flatten_document_symbol(
270 symbol: &DocumentSymbol,
271 container_name: Option<String>,
273 res: &mut Vec<SymbolInformation>,
275 res.push(SymbolInformation {
276 name: symbol.name.clone(),
278 deprecated: symbol.deprecated,
279 location: Location::new(url.clone(), symbol.range),
283 for child in symbol.children.iter().flatten() {
284 flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
289 pub fn handle_workspace_symbol(
290 snap: GlobalStateSnapshot,
291 params: lsp_types::WorkspaceSymbolParams,
292 ) -> Result<Option<Vec<SymbolInformation>>> {
293 let _p = profile("handle_workspace_symbol");
294 let all_symbols = params.query.contains('#');
295 let libs = params.query.contains('*');
297 let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
298 let mut q = Query::new(query);
308 let mut res = exec_query(&snap, query)?;
309 if res.is_empty() && !all_symbols {
310 let mut query = Query::new(params.query);
312 res = exec_query(&snap, query)?;
315 return Ok(Some(res));
317 fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
318 let mut res = Vec::new();
319 for nav in snap.analysis().symbol_search(query)? {
320 let info = SymbolInformation {
321 name: nav.name().to_string(),
322 kind: to_proto::symbol_kind(nav.kind()),
323 location: to_proto::location(snap, nav.file_range())?,
324 container_name: nav.container_name().map(|v| v.to_string()),
333 pub fn handle_goto_definition(
334 snap: GlobalStateSnapshot,
335 params: lsp_types::GotoDefinitionParams,
336 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
337 let _p = profile("handle_goto_definition");
338 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
339 let nav_info = match snap.analysis().goto_definition(position)? {
340 None => return Ok(None),
343 let src = FileRange { file_id: position.file_id, range: nav_info.range };
344 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
348 pub fn handle_goto_implementation(
349 snap: GlobalStateSnapshot,
350 params: lsp_types::request::GotoImplementationParams,
351 ) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
352 let _p = profile("handle_goto_implementation");
353 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
354 let nav_info = match snap.analysis().goto_implementation(position)? {
355 None => return Ok(None),
358 let src = FileRange { file_id: position.file_id, range: nav_info.range };
359 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
363 pub fn handle_goto_type_definition(
364 snap: GlobalStateSnapshot,
365 params: lsp_types::request::GotoTypeDefinitionParams,
366 ) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
367 let _p = profile("handle_goto_type_definition");
368 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
369 let nav_info = match snap.analysis().goto_type_definition(position)? {
370 None => return Ok(None),
373 let src = FileRange { file_id: position.file_id, range: nav_info.range };
374 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
378 pub fn handle_parent_module(
379 snap: GlobalStateSnapshot,
380 params: lsp_types::TextDocumentPositionParams,
381 ) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
382 let _p = profile("handle_parent_module");
383 let position = from_proto::file_position(&snap, params)?;
384 let navs = snap.analysis().parent_module(position)?;
385 let res = to_proto::goto_definition_response(&snap, None, navs)?;
389 pub fn handle_runnables(
390 snap: GlobalStateSnapshot,
391 params: lsp_ext::RunnablesParams,
392 ) -> Result<Vec<lsp_ext::Runnable>> {
393 let _p = profile("handle_runnables");
394 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
395 let line_index = snap.analysis().file_line_index(file_id)?;
396 let offset = params.position.map(|it| from_proto::offset(&line_index, it));
397 let mut res = Vec::new();
398 let workspace_root = snap.workspace_root_for(file_id);
399 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
400 for runnable in snap.analysis().runnables(file_id)? {
401 if let Some(offset) = offset {
402 if !runnable.nav.full_range().contains_inclusive(offset) {
406 // Do not suggest binary run on other target than binary
407 if let RunnableKind::Bin = runnable.kind {
408 if let Some(spec) = &cargo_spec {
409 match spec.target_kind {
410 TargetKind::Bin => {}
415 res.push(to_proto::runnable(&snap, file_id, runnable)?);
418 // Add `cargo check` and `cargo test` for the whole package
421 for &cmd in ["check", "test"].iter() {
422 res.push(lsp_ext::Runnable {
423 label: format!("cargo {} -p {}", cmd, spec.package),
425 kind: lsp_ext::RunnableKind::Cargo,
426 args: lsp_ext::CargoRunnable {
427 workspace_root: workspace_root.map(|root| root.to_owned()),
430 "--package".to_string(),
431 spec.package.clone(),
433 executable_args: Vec::new(),
439 res.push(lsp_ext::Runnable {
440 label: "cargo check --workspace".to_string(),
442 kind: lsp_ext::RunnableKind::Cargo,
443 args: lsp_ext::CargoRunnable {
444 workspace_root: workspace_root.map(|root| root.to_owned()),
445 cargo_args: vec!["check".to_string(), "--workspace".to_string()],
446 executable_args: Vec::new(),
454 pub fn handle_completion(
455 snap: GlobalStateSnapshot,
456 params: lsp_types::CompletionParams,
457 ) -> Result<Option<lsp_types::CompletionResponse>> {
458 let _p = profile("handle_completion");
459 let position = from_proto::file_position(&snap, params.text_document_position)?;
460 let completion_triggered_after_single_colon = {
462 if let Some(ctx) = params.context {
463 if ctx.trigger_character.unwrap_or_default() == ":" {
464 let source_file = snap.analysis().parse(position.file_id)?;
465 let syntax = source_file.syntax();
466 let text = syntax.text();
467 if let Some(next_char) = text.char_at(position.offset) {
468 let diff = TextSize::of(next_char) + TextSize::of(':');
469 let prev_char = position.offset - diff;
470 if text.char_at(prev_char) != Some(':') {
478 if completion_triggered_after_single_colon {
482 let items = match snap.analysis().completions(&snap.config.completion, position)? {
483 None => return Ok(None),
484 Some(items) => items,
486 let line_index = snap.analysis().file_line_index(position.file_id)?;
487 let line_endings = snap.file_line_endings(position.file_id);
488 let items: Vec<CompletionItem> = items
490 .map(|item| to_proto::completion_item(&line_index, line_endings, item))
493 Ok(Some(items.into()))
496 pub fn handle_folding_range(
497 snap: GlobalStateSnapshot,
498 params: FoldingRangeParams,
499 ) -> Result<Option<Vec<FoldingRange>>> {
500 let _p = profile("handle_folding_range");
501 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
502 let folds = snap.analysis().folding_ranges(file_id)?;
503 let text = snap.analysis().file_text(file_id)?;
504 let line_index = snap.analysis().file_line_index(file_id)?;
505 let line_folding_only = snap.config.client_caps.line_folding_only;
508 .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
513 pub fn handle_signature_help(
514 snap: GlobalStateSnapshot,
515 params: lsp_types::SignatureHelpParams,
516 ) -> Result<Option<lsp_types::SignatureHelp>> {
517 let _p = profile("handle_signature_help");
518 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
519 let call_info = match snap.analysis().call_info(position)? {
520 None => return Ok(None),
523 let concise = !snap.config.call_info_full;
524 let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
525 if concise && call_info.signature.has_self_param {
526 active_parameter = active_parameter.map(|it| it.saturating_sub(1));
528 let sig_info = to_proto::signature_information(call_info.signature, concise);
530 Ok(Some(lsp_types::SignatureHelp {
531 signatures: vec![sig_info],
532 active_signature: Some(0),
538 snap: GlobalStateSnapshot,
539 params: lsp_types::HoverParams,
540 ) -> Result<Option<Hover>> {
541 let _p = profile("handle_hover");
542 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
543 let info = match snap.analysis().hover(position)? {
544 None => return Ok(None),
547 let line_index = snap.analysis.file_line_index(position.file_id)?;
548 let range = to_proto::range(&line_index, info.range);
550 contents: HoverContents::Markup(MarkupContent {
551 kind: MarkupKind::Markdown,
552 value: crate::markdown::format_docs(&info.info.to_markup()),
559 pub fn handle_prepare_rename(
560 snap: GlobalStateSnapshot,
561 params: lsp_types::TextDocumentPositionParams,
562 ) -> Result<Option<PrepareRenameResponse>> {
563 let _p = profile("handle_prepare_rename");
564 let position = from_proto::file_position(&snap, params)?;
566 let optional_change = snap.analysis().rename(position, "dummy")?;
567 let range = match optional_change {
568 None => return Ok(None),
569 Some(it) => it.range,
572 let line_index = snap.analysis().file_line_index(position.file_id)?;
573 let range = to_proto::range(&line_index, range);
574 Ok(Some(PrepareRenameResponse::Range(range)))
577 pub fn handle_rename(
578 snap: GlobalStateSnapshot,
579 params: RenameParams,
580 ) -> Result<Option<WorkspaceEdit>> {
581 let _p = profile("handle_rename");
582 let position = from_proto::file_position(&snap, params.text_document_position)?;
584 if params.new_name.is_empty() {
585 return Err(LspError::new(
586 ErrorCode::InvalidParams as i32,
587 "New Name cannot be empty".into(),
592 let optional_change = snap.analysis().rename(position, &*params.new_name)?;
593 let source_change = match optional_change {
594 None => return Ok(None),
597 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
598 Ok(Some(workspace_edit))
601 pub fn handle_references(
602 snap: GlobalStateSnapshot,
603 params: lsp_types::ReferenceParams,
604 ) -> Result<Option<Vec<Location>>> {
605 let _p = profile("handle_references");
606 let position = from_proto::file_position(&snap, params.text_document_position)?;
608 let refs = match snap.analysis().find_all_refs(position, None)? {
609 None => return Ok(None),
613 let locations = if params.context.include_declaration {
615 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
618 // Only iterate over the references if include_declaration was false
621 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
628 pub fn handle_formatting(
629 snap: GlobalStateSnapshot,
630 params: DocumentFormattingParams,
631 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
632 let _p = profile("handle_formatting");
633 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
634 let file = snap.analysis().file_text(file_id)?;
635 let crate_ids = snap.analysis().crate_for(file_id)?;
637 let file_line_index = snap.analysis().file_line_index(file_id)?;
638 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
640 let mut rustfmt = match &snap.config.rustfmt {
641 RustfmtConfig::Rustfmt { extra_args } => {
642 let mut cmd = process::Command::new("rustfmt");
643 cmd.args(extra_args);
644 if let Some(&crate_id) = crate_ids.first() {
645 // Assume all crates are in the same edition
646 let edition = snap.analysis().crate_edition(crate_id)?;
647 cmd.arg("--edition");
648 cmd.arg(edition.to_string());
652 RustfmtConfig::CustomCommand { command, args } => {
653 let mut cmd = process::Command::new(command);
659 if let Ok(path) = params.text_document.uri.to_file_path() {
660 if let Some(parent) = path.parent() {
661 rustfmt.current_dir(parent);
664 let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
666 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
668 let output = rustfmt.wait_with_output()?;
669 let captured_stdout = String::from_utf8(output.stdout)?;
671 if !output.status.success() {
672 match output.status.code() {
674 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
675 // likely cause exiting with 1. Most Language Servers swallow parse errors on
676 // formatting because otherwise an error is surfaced to the user on top of the
677 // syntax error diagnostics they're already receiving. This is especially jarring
678 // if they have format on save enabled.
679 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
683 // Something else happened - e.g. `rustfmt` is missing or caught a signal
684 return Err(LspError::new(
687 r#"rustfmt exited with:
690 output.status, captured_stdout,
698 Ok(Some(vec![lsp_types::TextEdit {
699 range: Range::new(Position::new(0, 0), end_position),
700 new_text: captured_stdout,
705 snap: &GlobalStateSnapshot,
706 params: &lsp_types::CodeActionParams,
707 res: &mut Vec<lsp_ext::CodeAction>,
709 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
710 let line_index = snap.analysis().file_line_index(file_id)?;
711 let range = from_proto::text_range(&line_index, params.range);
712 let diagnostics = snap.analysis().diagnostics(file_id)?;
714 let fixes_from_diagnostics = diagnostics
716 .filter_map(|d| Some((d.range, d.fix?)))
717 .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
718 .map(|(_range, fix)| fix);
719 for fix in fixes_from_diagnostics {
720 let title = fix.label;
721 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
722 let action = lsp_ext::CodeAction {
726 kind: Some(lsp_types::code_action_kind::QUICKFIX.into()),
733 for fix in snap.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());
743 pub fn handle_code_action(
744 snap: GlobalStateSnapshot,
745 params: lsp_types::CodeActionParams,
746 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
747 let _p = profile("handle_code_action");
748 // We intentionally don't support command-based actions, as those either
749 // requires custom client-code anyway, or requires server-initiated edits.
750 // Server initiated edits break causality, so we avoid those as well.
751 if !snap.config.client_caps.code_action_literals {
755 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
756 let line_index = snap.analysis().file_line_index(file_id)?;
757 let range = from_proto::text_range(&line_index, params.range);
758 let frange = FileRange { file_id, range };
759 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
761 handle_fixes(&snap, ¶ms, &mut res)?;
763 if snap.config.client_caps.resolve_code_action {
764 for (index, assist) in
765 snap.analysis().unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
767 res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
770 for assist in snap.analysis().resolved_assists(&snap.config.assist, frange)?.into_iter() {
771 res.push(to_proto::resolved_code_action(&snap, assist)?);
778 pub fn handle_resolve_code_action(
779 snap: GlobalStateSnapshot,
780 params: lsp_ext::ResolveCodeActionParams,
781 ) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
782 let _p = profile("handle_resolve_code_action");
783 let file_id = from_proto::file_id(&snap, ¶ms.code_action_params.text_document.uri)?;
784 let line_index = snap.analysis().file_line_index(file_id)?;
785 let range = from_proto::text_range(&line_index, params.code_action_params.range);
786 let frange = FileRange { file_id, range };
788 let assists = snap.analysis().resolved_assists(&snap.config.assist, frange)?;
789 let id_components = params.id.split(":").collect::<Vec<&str>>();
790 let index = id_components.last().unwrap().parse::<usize>().unwrap();
791 let id_string = id_components.first().unwrap();
792 let assist = &assists[index];
793 assert!(assist.assist.id.0 == *id_string);
794 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
797 pub fn handle_code_lens(
798 snap: GlobalStateSnapshot,
799 params: lsp_types::CodeLensParams,
800 ) -> Result<Option<Vec<CodeLens>>> {
801 let _p = profile("handle_code_lens");
802 let mut lenses: Vec<CodeLens> = Default::default();
804 if snap.config.lens.none() {
805 // early return before any db query!
806 return Ok(Some(lenses));
809 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
810 let line_index = snap.analysis().file_line_index(file_id)?;
811 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
813 if snap.config.lens.runnable() {
815 for runnable in snap.analysis().runnables(file_id)? {
816 let (run_title, debugee) = match &runnable.kind {
817 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
818 ("â–¶\u{fe0e} Run Test", true)
820 RunnableKind::DocTest { .. } => {
821 // cargo does not support -no-run for doctests
822 ("â–¶\u{fe0e} Run Doctest", false)
824 RunnableKind::Bench { .. } => {
825 // Nothing wrong with bench debugging
828 RunnableKind::Bin => {
829 // Do not suggest binary run on other target than binary
831 Some(spec) => match spec.target_kind {
832 TargetKind::Bin => ("Run", true),
840 let range = to_proto::range(&line_index, runnable.nav.range());
841 let r = to_proto::runnable(&snap, file_id, runnable)?;
842 if snap.config.lens.run {
843 let lens = CodeLens {
845 command: Some(Command {
846 title: run_title.to_string(),
847 command: "rust-analyzer.runSingle".into(),
848 arguments: Some(vec![to_value(&r).unwrap()]),
855 if debugee && snap.config.lens.debug {
856 let debug_lens = CodeLens {
858 command: Some(Command {
859 title: "Debug".into(),
860 command: "rust-analyzer.debugSingle".into(),
861 arguments: Some(vec![to_value(r).unwrap()]),
865 lenses.push(debug_lens);
870 if snap.config.lens.impementations {
874 .file_structure(file_id)?
876 .filter(|it| match it.kind {
877 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
881 let range = to_proto::range(&line_index, it.node_range);
882 let pos = range.start;
883 let lens_params = lsp_types::request::GotoImplementationParams {
884 text_document_position_params: lsp_types::TextDocumentPositionParams::new(
885 params.text_document.clone(),
888 work_done_progress_params: Default::default(),
889 partial_result_params: Default::default(),
894 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
902 #[derive(Debug, Serialize, Deserialize)]
903 #[serde(rename_all = "camelCase")]
904 enum CodeLensResolveData {
905 Impls(lsp_types::request::GotoImplementationParams),
908 pub fn handle_code_lens_resolve(
909 snap: GlobalStateSnapshot,
911 ) -> Result<CodeLens> {
912 let _p = profile("handle_code_lens_resolve");
913 let data = code_lens.data.unwrap();
914 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
916 Some(CodeLensResolveData::Impls(lens_params)) => {
917 let locations: Vec<Location> =
918 match handle_goto_implementation(snap, lens_params.clone())? {
919 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
920 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
921 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
923 .map(|link| Location::new(link.target_uri, link.target_selection_range))
928 let title = if locations.len() == 1 {
929 "1 implementation".into()
931 format!("{} implementations", locations.len())
934 // We cannot use the 'editor.action.showReferences' command directly
935 // because that command requires vscode types which we convert in the handler
936 // on the client side.
939 command: "rust-analyzer.showReferences".into(),
940 arguments: Some(vec![
941 to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
942 to_value(code_lens.range.start).unwrap(),
943 to_value(locations).unwrap(),
946 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
948 None => Ok(CodeLens {
949 range: code_lens.range,
950 command: Some(Command { title: "Error".into(), ..Default::default() }),
956 pub fn handle_document_highlight(
957 snap: GlobalStateSnapshot,
958 params: lsp_types::DocumentHighlightParams,
959 ) -> Result<Option<Vec<DocumentHighlight>>> {
960 let _p = profile("handle_document_highlight");
961 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
962 let line_index = snap.analysis().file_line_index(position.file_id)?;
964 let refs = match snap
966 .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
968 None => return Ok(None),
974 .filter(|reference| reference.file_range.file_id == position.file_id)
975 .map(|reference| DocumentHighlight {
976 range: to_proto::range(&line_index, reference.file_range.range),
977 kind: reference.access.map(to_proto::document_highlight_kind),
984 snap: GlobalStateSnapshot,
985 params: lsp_ext::SsrParams,
986 ) -> Result<lsp_types::WorkspaceEdit> {
987 let _p = profile("handle_ssr");
989 snap.analysis().structural_search_replace(¶ms.query, params.parse_only)??;
990 to_proto::workspace_edit(&snap, source_change)
993 pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
994 let _p = profile("publish_diagnostics");
995 let line_index = snap.analysis().file_line_index(file_id)?;
996 let diagnostics: Vec<Diagnostic> = snap
998 .diagnostics(file_id)?
1000 .map(|d| Diagnostic {
1001 range: to_proto::range(&line_index, d.range),
1002 severity: Some(to_proto::diagnostic_severity(d.severity)),
1004 source: Some("rust-analyzer".to_string()),
1006 related_information: None,
1010 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
1013 pub fn handle_inlay_hints(
1014 snap: GlobalStateSnapshot,
1015 params: InlayHintsParams,
1016 ) -> Result<Vec<InlayHint>> {
1017 let _p = profile("handle_inlay_hints");
1018 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
1019 let analysis = snap.analysis();
1020 let line_index = analysis.file_line_index(file_id)?;
1022 .inlay_hints(file_id, &snap.config.inlay_hints)?
1024 .map(|it| to_proto::inlay_int(&line_index, it))
1028 pub fn handle_call_hierarchy_prepare(
1029 snap: GlobalStateSnapshot,
1030 params: CallHierarchyPrepareParams,
1031 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1032 let _p = profile("handle_call_hierarchy_prepare");
1033 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1035 let nav_info = match snap.analysis().call_hierarchy(position)? {
1036 None => return Ok(None),
1040 let RangeInfo { range: _, info: navs } = nav_info;
1043 .filter(|it| it.kind() == SyntaxKind::FN_DEF)
1044 .map(|it| to_proto::call_hierarchy_item(&snap, it))
1045 .collect::<Result<Vec<_>>>()?;
1050 pub fn handle_call_hierarchy_incoming(
1051 snap: GlobalStateSnapshot,
1052 params: CallHierarchyIncomingCallsParams,
1053 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1054 let _p = profile("handle_call_hierarchy_incoming");
1055 let item = params.item;
1057 let doc = TextDocumentIdentifier::new(item.uri);
1058 let frange = from_proto::file_range(&snap, doc, item.range)?;
1059 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1061 let call_items = match snap.analysis().incoming_calls(fpos)? {
1062 None => return Ok(None),
1066 let mut res = vec![];
1068 for call_item in call_items.into_iter() {
1069 let file_id = call_item.target.file_id();
1070 let line_index = snap.analysis().file_line_index(file_id)?;
1071 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1072 res.push(CallHierarchyIncomingCall {
1074 from_ranges: call_item
1077 .map(|it| to_proto::range(&line_index, it))
1085 pub fn handle_call_hierarchy_outgoing(
1086 snap: GlobalStateSnapshot,
1087 params: CallHierarchyOutgoingCallsParams,
1088 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1089 let _p = profile("handle_call_hierarchy_outgoing");
1090 let item = params.item;
1092 let doc = TextDocumentIdentifier::new(item.uri);
1093 let frange = from_proto::file_range(&snap, doc, item.range)?;
1094 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1096 let call_items = match snap.analysis().outgoing_calls(fpos)? {
1097 None => return Ok(None),
1101 let mut res = vec![];
1103 for call_item in call_items.into_iter() {
1104 let file_id = call_item.target.file_id();
1105 let line_index = snap.analysis().file_line_index(file_id)?;
1106 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1107 res.push(CallHierarchyOutgoingCall {
1109 from_ranges: call_item
1112 .map(|it| to_proto::range(&line_index, it))
1120 pub fn handle_semantic_tokens(
1121 snap: GlobalStateSnapshot,
1122 params: SemanticTokensParams,
1123 ) -> Result<Option<SemanticTokensResult>> {
1124 let _p = profile("handle_semantic_tokens");
1126 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
1127 let text = snap.analysis().file_text(file_id)?;
1128 let line_index = snap.analysis().file_line_index(file_id)?;
1130 let highlights = snap.analysis().highlight(file_id)?;
1131 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1132 Ok(Some(semantic_tokens.into()))
1135 pub fn handle_semantic_tokens_range(
1136 snap: GlobalStateSnapshot,
1137 params: SemanticTokensRangeParams,
1138 ) -> Result<Option<SemanticTokensRangeResult>> {
1139 let _p = profile("handle_semantic_tokens_range");
1141 let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1142 let text = snap.analysis().file_text(frange.file_id)?;
1143 let line_index = snap.analysis().file_line_index(frange.file_id)?;
1145 let highlights = snap.analysis().highlight_range(frange)?;
1146 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1147 Ok(Some(semantic_tokens.into()))