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, HoverContents, Location, MarkupContent,
16 MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams,
17 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
18 TextDocumentIdentifier, Url, WorkspaceEdit,
21 FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope,
25 use ra_project_model::TargetKind;
26 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize};
27 use serde::{Deserialize, Serialize};
28 use serde_json::to_value;
29 use stdx::{format_to, split1};
32 cargo_target_spec::CargoTargetSpec,
33 config::RustfmtConfig,
34 diagnostics::DiagnosticTask,
35 from_json, from_proto,
36 global_state::GlobalStateSnapshot,
37 lsp_ext::{self, InlayHint, InlayHintsParams},
38 to_proto, LspError, Result,
41 pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> {
42 let _p = profile("handle_analyzer_status");
43 let mut buf = snap.status();
44 format_to!(buf, "\n\nrequests:\n");
45 let requests = snap.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(
54 snap: GlobalStateSnapshot,
55 params: lsp_ext::SyntaxTreeParams,
57 let _p = profile("handle_syntax_tree");
58 let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
59 let line_index = snap.analysis().file_line_index(id)?;
60 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
61 let res = snap.analysis().syntax_tree(id, text_range)?;
65 pub fn handle_expand_macro(
66 snap: GlobalStateSnapshot,
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(&snap, ¶ms.text_document.uri)?;
71 let line_index = snap.analysis().file_line_index(file_id)?;
72 let offset = from_proto::offset(&line_index, params.position);
74 let res = snap.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(
79 snap: GlobalStateSnapshot,
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(&snap, ¶ms.text_document.uri)?;
84 let line_index = snap.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 = snap.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 snap: GlobalStateSnapshot,
123 params: lsp_ext::MatchingBraceParams,
124 ) -> Result<Vec<Position>> {
125 let _p = profile("handle_matching_brace");
126 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
127 let line_index = snap.analysis().file_line_index(file_id)?;
132 let offset = from_proto::offset(&line_index, position);
133 let offset = match snap.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 snap: GlobalStateSnapshot,
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(&snap, ¶ms.text_document.uri)?;
149 let line_index = snap.analysis().file_line_index(file_id)?;
150 let line_endings = snap.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 = snap.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 snap: GlobalStateSnapshot,
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(&snap, params)?;
172 let edit = match snap.analysis().on_enter(position)? {
173 None => return Ok(None),
176 let line_index = snap.analysis().file_line_index(position.file_id)?;
177 let line_endings = snap.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 snap: GlobalStateSnapshot,
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(&snap, params.text_document_position)?;
189 let line_index = snap.analysis().file_line_index(position.file_id)?;
190 let line_endings = snap.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 = snap.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 = snap.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 snap: GlobalStateSnapshot,
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(&snap, ¶ms.text_document.uri)?;
228 let line_index = snap.analysis().file_line_index(file_id)?;
230 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
232 for symbol in snap.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 snap.config.client_caps.hierarchical_symbols {
259 document_symbols.into()
261 let url = to_proto::url(&snap, 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 snap: GlobalStateSnapshot,
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(&snap, query)?;
310 if res.is_empty() && !all_symbols {
311 let mut query = Query::new(params.query);
313 res = exec_query(&snap, query)?;
316 return Ok(Some(res));
318 fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
319 let mut res = Vec::new();
320 for nav in snap.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(snap, nav.file_range())?,
325 container_name: nav.container_name().map(|v| v.to_string()),
334 pub fn handle_goto_definition(
335 snap: GlobalStateSnapshot,
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(&snap, params.text_document_position_params)?;
340 let nav_info = match snap.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(&snap, Some(src), nav_info.info)?;
349 pub fn handle_goto_implementation(
350 snap: GlobalStateSnapshot,
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(&snap, params.text_document_position_params)?;
355 let nav_info = match snap.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(&snap, Some(src), nav_info.info)?;
364 pub fn handle_goto_type_definition(
365 snap: GlobalStateSnapshot,
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(&snap, params.text_document_position_params)?;
370 let nav_info = match snap.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(&snap, Some(src), nav_info.info)?;
379 pub fn handle_parent_module(
380 snap: GlobalStateSnapshot,
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(&snap, params)?;
385 let navs = snap.analysis().parent_module(position)?;
386 let res = to_proto::goto_definition_response(&snap, None, navs)?;
390 pub fn handle_runnables(
391 snap: GlobalStateSnapshot,
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(&snap, ¶ms.text_document.uri)?;
396 let line_index = snap.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 = snap.workspace_root_for(file_id);
400 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
401 for runnable in snap.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(&snap, 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 snap: GlobalStateSnapshot,
457 params: lsp_types::CompletionParams,
458 ) -> Result<Option<lsp_types::CompletionResponse>> {
459 let _p = profile("handle_completion");
460 let position = from_proto::file_position(&snap, 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 = snap.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 snap.analysis().completions(&snap.config.completion, position)? {
484 None => return Ok(None),
485 Some(items) => items,
487 let line_index = snap.analysis().file_line_index(position.file_id)?;
488 let line_endings = snap.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 snap: GlobalStateSnapshot,
499 params: FoldingRangeParams,
500 ) -> Result<Option<Vec<FoldingRange>>> {
501 let _p = profile("handle_folding_range");
502 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
503 let folds = snap.analysis().folding_ranges(file_id)?;
504 let text = snap.analysis().file_text(file_id)?;
505 let line_index = snap.analysis().file_line_index(file_id)?;
506 let line_folding_only = snap.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 snap: GlobalStateSnapshot,
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(&snap, params.text_document_position_params)?;
520 let call_info = match snap.analysis().call_info(position)? {
521 None => return Ok(None),
524 let concise = !snap.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),
539 snap: GlobalStateSnapshot,
540 params: lsp_types::HoverParams,
541 ) -> Result<Option<lsp_ext::Hover>> {
542 let _p = profile("handle_hover");
543 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
544 let info = match snap.analysis().hover(position)? {
545 None => return Ok(None),
548 let line_index = snap.analysis.file_line_index(position.file_id)?;
549 let range = to_proto::range(&line_index, info.range);
550 let res = lsp_ext::Hover {
551 contents: HoverContents::Markup(MarkupContent {
552 kind: MarkupKind::Markdown,
553 value: crate::markdown::format_docs(&info.info.to_markup()),
556 actions: Some(prepare_hover_actions(&world, info.info.actions())),
561 pub fn handle_prepare_rename(
562 snap: GlobalStateSnapshot,
563 params: lsp_types::TextDocumentPositionParams,
564 ) -> Result<Option<PrepareRenameResponse>> {
565 let _p = profile("handle_prepare_rename");
566 let position = from_proto::file_position(&snap, params)?;
568 let optional_change = snap.analysis().rename(position, "dummy")?;
569 let range = match optional_change {
570 None => return Ok(None),
571 Some(it) => it.range,
574 let line_index = snap.analysis().file_line_index(position.file_id)?;
575 let range = to_proto::range(&line_index, range);
576 Ok(Some(PrepareRenameResponse::Range(range)))
579 pub fn handle_rename(
580 snap: GlobalStateSnapshot,
581 params: RenameParams,
582 ) -> Result<Option<WorkspaceEdit>> {
583 let _p = profile("handle_rename");
584 let position = from_proto::file_position(&snap, params.text_document_position)?;
586 if params.new_name.is_empty() {
587 return Err(LspError::new(
588 ErrorCode::InvalidParams as i32,
589 "New Name cannot be empty".into(),
594 let optional_change = snap.analysis().rename(position, &*params.new_name)?;
595 let source_change = match optional_change {
596 None => return Ok(None),
599 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
600 Ok(Some(workspace_edit))
603 pub fn handle_references(
604 snap: GlobalStateSnapshot,
605 params: lsp_types::ReferenceParams,
606 ) -> Result<Option<Vec<Location>>> {
607 let _p = profile("handle_references");
608 let position = from_proto::file_position(&snap, params.text_document_position)?;
610 let refs = match snap.analysis().find_all_refs(position, None)? {
611 None => return Ok(None),
615 let locations = if params.context.include_declaration {
617 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
620 // Only iterate over the references if include_declaration was false
623 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
630 pub fn handle_formatting(
631 snap: GlobalStateSnapshot,
632 params: DocumentFormattingParams,
633 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
634 let _p = profile("handle_formatting");
635 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
636 let file = snap.analysis().file_text(file_id)?;
637 let crate_ids = snap.analysis().crate_for(file_id)?;
639 let file_line_index = snap.analysis().file_line_index(file_id)?;
640 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
642 let mut rustfmt = match &snap.config.rustfmt {
643 RustfmtConfig::Rustfmt { extra_args } => {
644 let mut cmd = process::Command::new("rustfmt");
645 cmd.args(extra_args);
646 if let Some(&crate_id) = crate_ids.first() {
647 // Assume all crates are in the same edition
648 let edition = snap.analysis().crate_edition(crate_id)?;
649 cmd.arg("--edition");
650 cmd.arg(edition.to_string());
654 RustfmtConfig::CustomCommand { command, args } => {
655 let mut cmd = process::Command::new(command);
661 if let Ok(path) = params.text_document.uri.to_file_path() {
662 if let Some(parent) = path.parent() {
663 rustfmt.current_dir(parent);
666 let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
668 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
670 let output = rustfmt.wait_with_output()?;
671 let captured_stdout = String::from_utf8(output.stdout)?;
673 if !output.status.success() {
674 match output.status.code() {
676 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
677 // likely cause exiting with 1. Most Language Servers swallow parse errors on
678 // formatting because otherwise an error is surfaced to the user on top of the
679 // syntax error diagnostics they're already receiving. This is especially jarring
680 // if they have format on save enabled.
681 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
685 // Something else happened - e.g. `rustfmt` is missing or caught a signal
686 return Err(LspError::new(
689 r#"rustfmt exited with:
692 output.status, captured_stdout,
700 Ok(Some(vec![lsp_types::TextEdit {
701 range: Range::new(Position::new(0, 0), end_position),
702 new_text: captured_stdout,
707 snap: &GlobalStateSnapshot,
708 params: &lsp_types::CodeActionParams,
709 res: &mut Vec<lsp_ext::CodeAction>,
711 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
712 let line_index = snap.analysis().file_line_index(file_id)?;
713 let range = from_proto::text_range(&line_index, params.range);
714 let diagnostics = snap.analysis().diagnostics(file_id)?;
716 let fixes_from_diagnostics = diagnostics
718 .filter_map(|d| Some((d.range, d.fix?)))
719 .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
720 .map(|(_range, fix)| fix);
721 for fix in fixes_from_diagnostics {
722 let title = fix.label;
723 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
724 let action = lsp_ext::CodeAction {
728 kind: Some(lsp_types::code_action_kind::QUICKFIX.into()),
735 for fix in snap.check_fixes.get(&file_id).into_iter().flatten() {
736 let fix_range = from_proto::text_range(&line_index, fix.range);
737 if fix_range.intersect(range).is_none() {
740 res.push(fix.action.clone());
745 pub fn handle_code_action(
746 snap: GlobalStateSnapshot,
747 params: lsp_types::CodeActionParams,
748 ) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
749 let _p = profile("handle_code_action");
750 // We intentionally don't support command-based actions, as those either
751 // requires custom client-code anyway, or requires server-initiated edits.
752 // Server initiated edits break causality, so we avoid those as well.
753 if !snap.config.client_caps.code_action_literals {
757 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
758 let line_index = snap.analysis().file_line_index(file_id)?;
759 let range = from_proto::text_range(&line_index, params.range);
760 let frange = FileRange { file_id, range };
761 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
763 handle_fixes(&snap, ¶ms, &mut res)?;
765 if snap.config.client_caps.resolve_code_action {
766 for (index, assist) in
767 snap.analysis().unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
769 res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
772 for assist in snap.analysis().resolved_assists(&snap.config.assist, frange)?.into_iter() {
773 res.push(to_proto::resolved_code_action(&snap, assist)?);
780 pub fn handle_resolve_code_action(
781 snap: GlobalStateSnapshot,
782 params: lsp_ext::ResolveCodeActionParams,
783 ) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
784 let _p = profile("handle_resolve_code_action");
785 let file_id = from_proto::file_id(&snap, ¶ms.code_action_params.text_document.uri)?;
786 let line_index = snap.analysis().file_line_index(file_id)?;
787 let range = from_proto::text_range(&line_index, params.code_action_params.range);
788 let frange = FileRange { file_id, range };
790 let assists = snap.analysis().resolved_assists(&snap.config.assist, frange)?;
791 let (id_string, index) = split1(¶ms.id, ':').unwrap();
792 let index = index.parse::<usize>().unwrap();
793 let assist = &assists[index];
794 assert!(assist.assist.id.0 == id_string);
795 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
798 pub fn handle_code_lens(
799 snap: GlobalStateSnapshot,
800 params: lsp_types::CodeLensParams,
801 ) -> Result<Option<Vec<CodeLens>>> {
802 let _p = profile("handle_code_lens");
803 let mut lenses: Vec<CodeLens> = Default::default();
805 if snap.config.lens.none() {
806 // early return before any db query!
807 return Ok(Some(lenses));
810 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
811 let line_index = snap.analysis().file_line_index(file_id)?;
812 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
814 if snap.config.lens.runnable() {
816 for runnable in snap.analysis().runnables(file_id)? {
817 let (run_title, debugee) = match &runnable.kind {
818 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
819 ("â–¶\u{fe0e} Run Test", true)
821 RunnableKind::DocTest { .. } => {
822 // cargo does not support -no-run for doctests
823 ("â–¶\u{fe0e} Run Doctest", false)
825 RunnableKind::Bench { .. } => {
826 // Nothing wrong with bench debugging
829 RunnableKind::Bin => {
830 // Do not suggest binary run on other target than binary
832 Some(spec) => match spec.target_kind {
833 TargetKind::Bin => ("Run", true),
841 let range = to_proto::range(&line_index, runnable.nav.range());
842 let r = to_proto::runnable(&snap, file_id, runnable)?;
843 if snap.config.lens.run {
844 let lens = CodeLens {
846 command: Some(Command {
847 title: run_title.to_string(),
848 command: "rust-analyzer.runSingle".into(),
849 arguments: Some(vec![to_value(&r).unwrap()]),
856 if debugee && snap.config.lens.debug {
857 let debug_lens = CodeLens {
859 command: Some(Command {
860 title: "Debug".into(),
861 command: "rust-analyzer.debugSingle".into(),
862 arguments: Some(vec![to_value(r).unwrap()]),
866 lenses.push(debug_lens);
871 if snap.config.lens.impementations {
875 .file_structure(file_id)?
877 .filter(|it| match it.kind {
878 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
882 let range = to_proto::range(&line_index, it.node_range);
883 let pos = range.start;
884 let lens_params = lsp_types::request::GotoImplementationParams {
885 text_document_position_params: lsp_types::TextDocumentPositionParams::new(
886 params.text_document.clone(),
889 work_done_progress_params: Default::default(),
890 partial_result_params: Default::default(),
895 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
903 #[derive(Debug, Serialize, Deserialize)]
904 #[serde(rename_all = "camelCase")]
905 enum CodeLensResolveData {
906 Impls(lsp_types::request::GotoImplementationParams),
909 pub fn handle_code_lens_resolve(
910 snap: GlobalStateSnapshot,
912 ) -> Result<CodeLens> {
913 let _p = profile("handle_code_lens_resolve");
914 let data = code_lens.data.unwrap();
915 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
917 Some(CodeLensResolveData::Impls(lens_params)) => {
918 let locations: Vec<Location> =
919 match handle_goto_implementation(snap, lens_params.clone())? {
920 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
921 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
922 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
924 .map(|link| Location::new(link.target_uri, link.target_selection_range))
929 let title = implementation_title(locations.len());
930 let cmd = show_references_command(
932 &lens_params.text_document_position_params.text_document.uri,
933 code_lens.range.start,
936 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
938 None => Ok(CodeLens {
939 range: code_lens.range,
940 command: Some(Command { title: "Error".into(), ..Default::default() }),
946 pub fn handle_document_highlight(
947 snap: GlobalStateSnapshot,
948 params: lsp_types::DocumentHighlightParams,
949 ) -> Result<Option<Vec<DocumentHighlight>>> {
950 let _p = profile("handle_document_highlight");
951 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
952 let line_index = snap.analysis().file_line_index(position.file_id)?;
954 let refs = match snap
956 .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
958 None => return Ok(None),
964 .filter(|reference| reference.file_range.file_id == position.file_id)
965 .map(|reference| DocumentHighlight {
966 range: to_proto::range(&line_index, reference.file_range.range),
967 kind: reference.access.map(to_proto::document_highlight_kind),
974 snap: GlobalStateSnapshot,
975 params: lsp_ext::SsrParams,
976 ) -> Result<lsp_types::WorkspaceEdit> {
977 let _p = profile("handle_ssr");
979 snap.analysis().structural_search_replace(¶ms.query, params.parse_only)??;
980 to_proto::workspace_edit(&snap, source_change)
983 pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
984 let _p = profile("publish_diagnostics");
985 let line_index = snap.analysis().file_line_index(file_id)?;
986 let diagnostics: Vec<Diagnostic> = snap
988 .diagnostics(file_id)?
990 .map(|d| Diagnostic {
991 range: to_proto::range(&line_index, d.range),
992 severity: Some(to_proto::diagnostic_severity(d.severity)),
994 source: Some("rust-analyzer".to_string()),
996 related_information: None,
1000 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
1003 pub fn handle_inlay_hints(
1004 snap: GlobalStateSnapshot,
1005 params: InlayHintsParams,
1006 ) -> Result<Vec<InlayHint>> {
1007 let _p = profile("handle_inlay_hints");
1008 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
1009 let analysis = snap.analysis();
1010 let line_index = analysis.file_line_index(file_id)?;
1012 .inlay_hints(file_id, &snap.config.inlay_hints)?
1014 .map(|it| to_proto::inlay_int(&line_index, it))
1018 pub fn handle_call_hierarchy_prepare(
1019 snap: GlobalStateSnapshot,
1020 params: CallHierarchyPrepareParams,
1021 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1022 let _p = profile("handle_call_hierarchy_prepare");
1023 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1025 let nav_info = match snap.analysis().call_hierarchy(position)? {
1026 None => return Ok(None),
1030 let RangeInfo { range: _, info: navs } = nav_info;
1033 .filter(|it| it.kind() == SyntaxKind::FN_DEF)
1034 .map(|it| to_proto::call_hierarchy_item(&snap, it))
1035 .collect::<Result<Vec<_>>>()?;
1040 pub fn handle_call_hierarchy_incoming(
1041 snap: GlobalStateSnapshot,
1042 params: CallHierarchyIncomingCallsParams,
1043 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1044 let _p = profile("handle_call_hierarchy_incoming");
1045 let item = params.item;
1047 let doc = TextDocumentIdentifier::new(item.uri);
1048 let frange = from_proto::file_range(&snap, doc, item.range)?;
1049 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1051 let call_items = match snap.analysis().incoming_calls(fpos)? {
1052 None => return Ok(None),
1056 let mut res = vec![];
1058 for call_item in call_items.into_iter() {
1059 let file_id = call_item.target.file_id();
1060 let line_index = snap.analysis().file_line_index(file_id)?;
1061 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1062 res.push(CallHierarchyIncomingCall {
1064 from_ranges: call_item
1067 .map(|it| to_proto::range(&line_index, it))
1075 pub fn handle_call_hierarchy_outgoing(
1076 snap: GlobalStateSnapshot,
1077 params: CallHierarchyOutgoingCallsParams,
1078 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1079 let _p = profile("handle_call_hierarchy_outgoing");
1080 let item = params.item;
1082 let doc = TextDocumentIdentifier::new(item.uri);
1083 let frange = from_proto::file_range(&snap, doc, item.range)?;
1084 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1086 let call_items = match snap.analysis().outgoing_calls(fpos)? {
1087 None => return Ok(None),
1091 let mut res = vec![];
1093 for call_item in call_items.into_iter() {
1094 let file_id = call_item.target.file_id();
1095 let line_index = snap.analysis().file_line_index(file_id)?;
1096 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1097 res.push(CallHierarchyOutgoingCall {
1099 from_ranges: call_item
1102 .map(|it| to_proto::range(&line_index, it))
1110 pub fn handle_semantic_tokens(
1111 snap: GlobalStateSnapshot,
1112 params: SemanticTokensParams,
1113 ) -> Result<Option<SemanticTokensResult>> {
1114 let _p = profile("handle_semantic_tokens");
1116 let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
1117 let text = snap.analysis().file_text(file_id)?;
1118 let line_index = snap.analysis().file_line_index(file_id)?;
1120 let highlights = snap.analysis().highlight(file_id)?;
1121 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1122 Ok(Some(semantic_tokens.into()))
1125 pub fn handle_semantic_tokens_range(
1126 snap: GlobalStateSnapshot,
1127 params: SemanticTokensRangeParams,
1128 ) -> Result<Option<SemanticTokensRangeResult>> {
1129 let _p = profile("handle_semantic_tokens_range");
1131 let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1132 let text = snap.analysis().file_text(frange.file_id)?;
1133 let line_index = snap.analysis().file_line_index(frange.file_id)?;
1135 let highlights = snap.analysis().highlight_range(frange)?;
1136 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1137 Ok(Some(semantic_tokens.into()))
1140 fn implementation_title(count: usize) -> String {
1142 "1 implementation".into()
1144 format!("{} implementations", count)
1148 fn show_references_command(
1150 uri: &lsp_types::Url,
1151 position: lsp_types::Position,
1152 locations: Vec<lsp_types::Location>,
1154 // We cannot use the 'editor.action.showReferences' command directly
1155 // because that command requires vscode types which we convert in the handler
1156 // on the client side.
1160 command: "rust-analyzer.showReferences".into(),
1161 arguments: Some(vec![
1162 to_value(uri).unwrap(),
1163 to_value(position).unwrap(),
1164 to_value(locations).unwrap(),
1169 fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
1170 lsp_ext::CommandLink {
1171 tooltip: Some(tooltip),
1172 title: command.title,
1173 command: command.command,
1174 arguments: command.arguments,
1178 fn show_impl_command_link(
1179 world: &WorldSnapshot,
1180 position: &FilePosition,
1181 ) -> Option<lsp_ext::CommandLinkGroup> {
1182 if world.config.hover.implementations {
1183 if let Some(nav_data) = world.analysis().goto_implementation(*position).unwrap_or(None) {
1184 let uri = to_proto::url(world, position.file_id).ok()?;
1185 let line_index = world.analysis().file_line_index(position.file_id).ok()?;
1186 let position = to_proto::position(&line_index, position.offset);
1187 let locations: Vec<_> = nav_data
1190 .filter_map(|it| to_proto::location(world, it.file_range()).ok())
1192 let title = implementation_title(locations.len());
1193 let command = show_references_command(title, &uri, position, locations);
1195 return Some(lsp_ext::CommandLinkGroup {
1196 commands: vec![to_command_link(command, "Go to implementations".into())],
1197 ..Default::default()
1204 fn prepare_hover_actions(
1205 world: &WorldSnapshot,
1206 actions: &[HoverAction],
1207 ) -> Vec<lsp_ext::CommandLinkGroup> {
1208 if world.config.hover.none() || !world.config.client_caps.hover_actions {
1214 .filter_map(|it| match it {
1215 HoverAction::Implementaion(position) => show_impl_command_link(world, position),