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 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
17 Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
19 TextEdit, Url, WorkspaceEdit,
22 Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
26 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize};
27 use rustc_hash::FxHashMap;
28 use serde::{Deserialize, Serialize};
29 use serde_json::to_value;
33 cargo_target_spec::CargoTargetSpec,
34 config::RustfmtConfig,
36 to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith,
39 diagnostics::DiagnosticTask,
41 req::{self, InlayHint, InlayHintsParams},
42 semantic_tokens::SemanticTokensBuilder,
47 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
48 let _p = profile("handle_analyzer_status");
49 let mut buf = world.status();
50 format_to!(buf, "\n\nrequests:");
51 let requests = world.latest_requests.read();
52 for (is_last, r) in requests.iter() {
53 let mark = if is_last { "*" } else { " " };
54 format_to!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis());
59 pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
60 let _p = profile("handle_syntax_tree");
61 let id = params.text_document.try_conv_with(&world)?;
62 let line_index = world.analysis().file_line_index(id)?;
63 let text_range = params.range.map(|p| p.conv_with(&line_index));
64 let res = world.analysis().syntax_tree(id, text_range)?;
68 pub fn handle_expand_macro(
70 params: req::ExpandMacroParams,
71 ) -> Result<Option<req::ExpandedMacro>> {
72 let _p = profile("handle_expand_macro");
73 let file_id = params.text_document.try_conv_with(&world)?;
74 let line_index = world.analysis().file_line_index(file_id)?;
75 let offset = params.position.map(|p| p.conv_with(&line_index));
80 let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
81 Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion }))
86 pub fn handle_selection_range(
88 params: req::SelectionRangeParams,
89 ) -> Result<Option<Vec<req::SelectionRange>>> {
90 let _p = profile("handle_selection_range");
91 let file_id = params.text_document.try_conv_with(&world)?;
92 let line_index = world.analysis().file_line_index(file_id)?;
93 let res: Result<Vec<req::SelectionRange>> = params
96 .map_conv_with(&line_index)
98 let mut ranges = Vec::new();
100 let mut range = TextRange::new(position, position);
103 let frange = FileRange { file_id, range };
104 let next = world.analysis().extend_selection(frange)?;
112 let mut range = req::SelectionRange {
113 range: ranges.last().unwrap().conv_with(&line_index),
116 for r in ranges.iter().rev().skip(1) {
117 range = req::SelectionRange {
118 range: r.conv_with(&line_index),
119 parent: Some(Box::new(range)),
129 pub fn handle_find_matching_brace(
130 world: WorldSnapshot,
131 params: req::FindMatchingBraceParams,
132 ) -> Result<Vec<Position>> {
133 let _p = profile("handle_find_matching_brace");
134 let file_id = params.text_document.try_conv_with(&world)?;
135 let line_index = world.analysis().file_line_index(file_id)?;
139 .map_conv_with(&line_index)
141 if let Ok(Some(matching_brace_offset)) =
142 world.analysis().matching_brace(FilePosition { file_id, offset })
144 matching_brace_offset
149 .map_conv_with(&line_index)
154 pub fn handle_join_lines(
155 world: WorldSnapshot,
156 params: req::JoinLinesParams,
157 ) -> Result<req::SourceChange> {
158 let _p = profile("handle_join_lines");
159 let frange = (¶ms.text_document, params.range).try_conv_with(&world)?;
160 world.analysis().join_lines(frange)?.try_conv_with(&world)
163 pub fn handle_on_enter(
164 world: WorldSnapshot,
165 params: req::TextDocumentPositionParams,
166 ) -> Result<Option<req::SourceChange>> {
167 let _p = profile("handle_on_enter");
168 let position = params.try_conv_with(&world)?;
169 match world.analysis().on_enter(position)? {
171 Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
175 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
176 pub fn handle_on_type_formatting(
177 world: WorldSnapshot,
178 params: req::DocumentOnTypeFormattingParams,
179 ) -> Result<Option<Vec<TextEdit>>> {
180 let _p = profile("handle_on_type_formatting");
181 let mut position = params.text_document_position.try_conv_with(&world)?;
182 let line_index = world.analysis().file_line_index(position.file_id)?;
183 let line_endings = world.file_line_endings(position.file_id);
185 // in `ra_ide`, the `on_type` invariant is that
186 // `text.char_at(position) == typed_char`.
187 position.offset -= TextSize::of('.');
188 let char_typed = params.ch.chars().next().unwrap_or('\0');
190 let text = world.analysis().file_text(position.file_id)?;
191 text[usize::from(position.offset)..].starts_with(char_typed)
194 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
195 // but it requires precise cursor positioning to work, and one can't
196 // position the cursor with on_type formatting. So, let's just toggle this
197 // feature off here, hoping that we'll enable it one day, 😿.
198 if char_typed == '>' {
202 let edit = world.analysis().on_char_typed(position, char_typed)?;
203 let mut edit = match edit {
205 None => return Ok(None),
208 // This should be a single-file edit
209 let edit = edit.source_file_edits.pop().unwrap();
211 let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
215 pub fn handle_document_symbol(
216 world: WorldSnapshot,
217 params: req::DocumentSymbolParams,
218 ) -> Result<Option<req::DocumentSymbolResponse>> {
219 let _p = profile("handle_document_symbol");
220 let file_id = params.text_document.try_conv_with(&world)?;
221 let line_index = world.analysis().file_line_index(file_id)?;
222 let url = file_id.try_conv_with(&world)?;
224 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
226 for symbol in world.analysis().file_structure(file_id)? {
227 let doc_symbol = DocumentSymbol {
229 detail: symbol.detail,
230 kind: symbol.kind.conv(),
231 deprecated: Some(symbol.deprecated),
232 range: symbol.node_range.conv_with(&line_index),
233 selection_range: symbol.navigation_range.conv_with(&line_index),
236 parents.push((doc_symbol, symbol.parent));
238 let mut document_symbols = Vec::new();
239 while let Some((node, parent)) = parents.pop() {
241 None => document_symbols.push(node),
243 let children = &mut parents[i].0.children;
244 if children.is_none() {
245 *children = Some(Vec::new());
247 children.as_mut().unwrap().push(node);
252 if world.config.client_caps.hierarchical_symbols {
253 Ok(Some(document_symbols.into()))
255 let mut symbol_information = Vec::<SymbolInformation>::new();
256 for symbol in document_symbols {
257 flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
260 Ok(Some(symbol_information.into()))
264 fn flatten_document_symbol(
265 symbol: &DocumentSymbol,
266 container_name: Option<String>,
268 res: &mut Vec<SymbolInformation>,
270 res.push(SymbolInformation {
271 name: symbol.name.clone(),
273 deprecated: symbol.deprecated,
274 location: Location::new(url.clone(), symbol.range),
275 container_name: container_name,
278 for child in symbol.children.iter().flatten() {
279 flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
283 pub fn handle_workspace_symbol(
284 world: WorldSnapshot,
285 params: req::WorkspaceSymbolParams,
286 ) -> Result<Option<Vec<SymbolInformation>>> {
287 let _p = profile("handle_workspace_symbol");
288 let all_symbols = params.query.contains('#');
289 let libs = params.query.contains('*');
291 let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
292 let mut q = Query::new(query);
302 let mut res = exec_query(&world, query)?;
303 if res.is_empty() && !all_symbols {
304 let mut query = Query::new(params.query);
306 res = exec_query(&world, query)?;
309 return Ok(Some(res));
311 fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
312 let mut res = Vec::new();
313 for nav in world.analysis().symbol_search(query)? {
314 let info = SymbolInformation {
315 name: nav.name().to_string(),
316 kind: nav.kind().conv(),
317 location: nav.try_conv_with(world)?,
318 container_name: nav.container_name().map(|v| v.to_string()),
327 pub fn handle_goto_definition(
328 world: WorldSnapshot,
329 params: req::GotoDefinitionParams,
330 ) -> Result<Option<req::GotoDefinitionResponse>> {
331 let _p = profile("handle_goto_definition");
332 let position = params.text_document_position_params.try_conv_with(&world)?;
333 let nav_info = match world.analysis().goto_definition(position)? {
334 None => return Ok(None),
337 let res = (position.file_id, nav_info).try_conv_with(&world)?;
341 pub fn handle_goto_implementation(
342 world: WorldSnapshot,
343 params: req::GotoImplementationParams,
344 ) -> Result<Option<req::GotoImplementationResponse>> {
345 let _p = profile("handle_goto_implementation");
346 let position = params.text_document_position_params.try_conv_with(&world)?;
347 let nav_info = match world.analysis().goto_implementation(position)? {
348 None => return Ok(None),
351 let res = (position.file_id, nav_info).try_conv_with(&world)?;
355 pub fn handle_goto_type_definition(
356 world: WorldSnapshot,
357 params: req::GotoTypeDefinitionParams,
358 ) -> Result<Option<req::GotoTypeDefinitionResponse>> {
359 let _p = profile("handle_goto_type_definition");
360 let position = params.text_document_position_params.try_conv_with(&world)?;
361 let nav_info = match world.analysis().goto_type_definition(position)? {
362 None => return Ok(None),
365 let res = (position.file_id, nav_info).try_conv_with(&world)?;
369 pub fn handle_parent_module(
370 world: WorldSnapshot,
371 params: req::TextDocumentPositionParams,
372 ) -> Result<Vec<Location>> {
373 let _p = profile("handle_parent_module");
374 let position = params.try_conv_with(&world)?;
375 world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
378 pub fn handle_runnables(
379 world: WorldSnapshot,
380 params: req::RunnablesParams,
381 ) -> Result<Vec<req::Runnable>> {
382 let _p = profile("handle_runnables");
383 let file_id = params.text_document.try_conv_with(&world)?;
384 let line_index = world.analysis().file_line_index(file_id)?;
385 let offset = params.position.map(|it| it.conv_with(&line_index));
386 let mut res = Vec::new();
387 let workspace_root = world.workspace_root_for(file_id);
388 for runnable in world.analysis().runnables(file_id)? {
389 if let Some(offset) = offset {
390 if !runnable.range.contains_inclusive(offset) {
394 res.push(to_lsp_runnable(&world, file_id, runnable)?);
396 // Add `cargo check` and `cargo test` for the whole package
397 match CargoTargetSpec::for_file(&world, file_id)? {
399 for &cmd in ["check", "test"].iter() {
400 res.push(req::Runnable {
401 range: Default::default(),
402 label: format!("cargo {} -p {}", cmd, spec.package),
403 bin: "cargo".to_string(),
405 let mut args = vec![cmd.to_string()];
406 spec.clone().push_to(&mut args);
409 extra_args: Vec::new(),
410 env: FxHashMap::default(),
411 cwd: workspace_root.map(|root| root.to_owned()),
416 res.push(req::Runnable {
417 range: Default::default(),
418 label: "cargo check --workspace".to_string(),
419 bin: "cargo".to_string(),
420 args: vec!["check".to_string(), "--workspace".to_string()],
421 extra_args: Vec::new(),
422 env: FxHashMap::default(),
423 cwd: workspace_root.map(|root| root.to_owned()),
430 pub fn handle_completion(
431 world: WorldSnapshot,
432 params: req::CompletionParams,
433 ) -> Result<Option<req::CompletionResponse>> {
434 let _p = profile("handle_completion");
435 let position = params.text_document_position.try_conv_with(&world)?;
436 let completion_triggered_after_single_colon = {
438 if let Some(ctx) = params.context {
439 if ctx.trigger_character.unwrap_or_default() == ":" {
440 let source_file = world.analysis().parse(position.file_id)?;
441 let syntax = source_file.syntax();
442 let text = syntax.text();
443 if let Some(next_char) = text.char_at(position.offset) {
444 let diff = TextSize::of(next_char) + TextSize::of(':');
445 let prev_char = position.offset - diff;
446 if text.char_at(prev_char) != Some(':') {
454 if completion_triggered_after_single_colon {
458 let items = match world.analysis().completions(position, &world.config.completion)? {
459 None => return Ok(None),
460 Some(items) => items,
462 let line_index = world.analysis().file_line_index(position.file_id)?;
463 let line_endings = world.file_line_endings(position.file_id);
464 let items: Vec<CompletionItem> =
465 items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
467 Ok(Some(items.into()))
470 pub fn handle_folding_range(
471 world: WorldSnapshot,
472 params: FoldingRangeParams,
473 ) -> Result<Option<Vec<FoldingRange>>> {
474 let _p = profile("handle_folding_range");
475 let file_id = params.text_document.try_conv_with(&world)?;
476 let folds = world.analysis().folding_ranges(file_id)?;
477 let text = world.analysis().file_text(file_id)?;
478 let line_index = world.analysis().file_line_index(file_id)?;
479 let ctx = FoldConvCtx {
481 line_index: &line_index,
482 line_folding_only: world.config.client_caps.line_folding_only,
484 let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
488 pub fn handle_signature_help(
489 world: WorldSnapshot,
490 params: req::SignatureHelpParams,
491 ) -> Result<Option<req::SignatureHelp>> {
492 let _p = profile("handle_signature_help");
493 let position = params.text_document_position_params.try_conv_with(&world)?;
494 if let Some(call_info) = world.analysis().call_info(position)? {
495 let concise = !world.config.call_info_full;
496 let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
497 if concise && call_info.signature.has_self_param {
498 active_parameter = active_parameter.map(|it| it.saturating_sub(1));
500 let sig_info = call_info.signature.conv_with(concise);
502 Ok(Some(req::SignatureHelp {
503 signatures: vec![sig_info],
504 active_signature: Some(0),
512 pub fn handle_hover(world: WorldSnapshot, params: req::HoverParams) -> Result<Option<Hover>> {
513 let _p = profile("handle_hover");
514 let position = params.text_document_position_params.try_conv_with(&world)?;
515 let info = match world.analysis().hover(position)? {
516 None => return Ok(None),
519 let line_index = world.analysis.file_line_index(position.file_id)?;
520 let range = info.range.conv_with(&line_index);
522 contents: HoverContents::Markup(MarkupContent {
523 kind: MarkupKind::Markdown,
524 value: crate::markdown::format_docs(&info.info.to_markup()),
531 pub fn handle_prepare_rename(
532 world: WorldSnapshot,
533 params: req::TextDocumentPositionParams,
534 ) -> Result<Option<PrepareRenameResponse>> {
535 let _p = profile("handle_prepare_rename");
536 let position = params.try_conv_with(&world)?;
538 let optional_change = world.analysis().rename(position, "dummy")?;
539 let range = match optional_change {
540 None => return Ok(None),
541 Some(it) => it.range,
544 let file_id = params.text_document.try_conv_with(&world)?;
545 let line_index = world.analysis().file_line_index(file_id)?;
546 let range = range.conv_with(&line_index);
547 Ok(Some(PrepareRenameResponse::Range(range)))
550 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
551 let _p = profile("handle_rename");
552 let position = params.text_document_position.try_conv_with(&world)?;
554 if params.new_name.is_empty() {
555 return Err(LspError::new(
556 ErrorCode::InvalidParams as i32,
557 "New Name cannot be empty".into(),
562 let optional_change = world.analysis().rename(position, &*params.new_name)?;
563 let change = match optional_change {
564 None => return Ok(None),
568 let source_change_req = change.try_conv_with(&world)?;
570 Ok(Some(source_change_req.workspace_edit))
573 pub fn handle_references(
574 world: WorldSnapshot,
575 params: req::ReferenceParams,
576 ) -> Result<Option<Vec<Location>>> {
577 let _p = profile("handle_references");
578 let position = params.text_document_position.try_conv_with(&world)?;
580 let refs = match world.analysis().find_all_refs(position, None)? {
581 None => return Ok(None),
585 let locations = if params.context.include_declaration {
587 .filter_map(|reference| {
589 world.analysis().file_line_index(reference.file_range.file_id).ok()?;
591 reference.file_range.file_id,
592 reference.file_range.range,
600 // Only iterate over the references if include_declaration was false
603 .filter_map(|reference| {
605 world.analysis().file_line_index(reference.file_range.file_id).ok()?;
607 reference.file_range.file_id,
608 reference.file_range.range,
620 pub fn handle_formatting(
621 world: WorldSnapshot,
622 params: DocumentFormattingParams,
623 ) -> Result<Option<Vec<TextEdit>>> {
624 let _p = profile("handle_formatting");
625 let file_id = params.text_document.try_conv_with(&world)?;
626 let file = world.analysis().file_text(file_id)?;
627 let crate_ids = world.analysis().crate_for(file_id)?;
629 let file_line_index = world.analysis().file_line_index(file_id)?;
630 let end_position = TextSize::of(file.as_str()).conv_with(&file_line_index);
632 let mut rustfmt = match &world.config.rustfmt {
633 RustfmtConfig::Rustfmt { extra_args } => {
634 let mut cmd = process::Command::new("rustfmt");
635 cmd.args(extra_args);
636 if let Some(&crate_id) = crate_ids.first() {
637 // Assume all crates are in the same edition
638 let edition = world.analysis().crate_edition(crate_id)?;
639 cmd.arg("--edition");
640 cmd.arg(edition.to_string());
644 RustfmtConfig::CustomCommand { command, args } => {
645 let mut cmd = process::Command::new(command);
651 if let Ok(path) = params.text_document.uri.to_file_path() {
652 if let Some(parent) = path.parent() {
653 rustfmt.current_dir(parent);
656 let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
658 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
660 let output = rustfmt.wait_with_output()?;
661 let captured_stdout = String::from_utf8(output.stdout)?;
663 if !output.status.success() {
664 match output.status.code() {
666 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
667 // likely cause exiting with 1. Most Language Servers swallow parse errors on
668 // formatting because otherwise an error is surfaced to the user on top of the
669 // syntax error diagnostics they're already receiving. This is especially jarring
670 // if they have format on save enabled.
671 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
675 // Something else happened - e.g. `rustfmt` is missing or caught a signal
676 return Err(LspError::new(
679 r#"rustfmt exited with:
682 output.status, captured_stdout,
690 Ok(Some(vec![TextEdit {
691 range: Range::new(Position::new(0, 0), end_position),
692 new_text: captured_stdout,
696 fn create_single_code_action(assist: Assist, world: &WorldSnapshot) -> Result<CodeAction> {
697 let arg = to_value(assist.source_change.try_conv_with(world)?)?;
698 let title = assist.label;
699 let command = Command {
700 title: title.clone(),
701 command: "rust-analyzer.applySourceChange".to_string(),
702 arguments: Some(vec![arg]),
705 let kind = match assist.id {
706 AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
707 AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()),
716 command: Some(command),
721 pub fn handle_code_action(
722 world: WorldSnapshot,
723 params: req::CodeActionParams,
724 ) -> Result<Option<CodeActionResponse>> {
725 let _p = profile("handle_code_action");
726 let file_id = params.text_document.try_conv_with(&world)?;
727 let line_index = world.analysis().file_line_index(file_id)?;
728 let range = params.range.conv_with(&line_index);
730 let diagnostics = world.analysis().diagnostics(file_id)?;
731 let mut res = CodeActionResponse::default();
733 let fixes_from_diagnostics = diagnostics
735 .filter_map(|d| Some((d.range, d.fix?)))
736 .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
737 .map(|(_range, fix)| fix);
739 for source_edit in fixes_from_diagnostics {
740 let title = source_edit.label.clone();
741 let edit = source_edit.try_conv_with(&world)?;
743 let command = Command {
745 command: "rust-analyzer.applySourceChange".to_string(),
746 arguments: Some(vec![to_value(edit).unwrap()]),
748 let action = CodeAction {
749 title: command.title.clone(),
753 command: Some(command),
756 res.push(action.into());
759 for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
760 let fix_range = fix.range.conv_with(&line_index);
761 if fix_range.intersect(range).is_none() {
764 res.push(fix.action.clone());
767 let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default();
768 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
769 match &assist.group_label {
770 Some(label) => grouped_assists
771 .entry(label.to_owned())
774 let dummy = Command::new(String::new(), String::new(), None);
775 res.push(dummy.into());
781 res.push(create_single_code_action(assist, &world)?.into());
786 for (group_label, (idx, assists)) in grouped_assists {
787 if assists.len() == 1 {
789 create_single_code_action(assists.into_iter().next().unwrap(), &world)?.into();
791 let title = group_label;
793 let mut arguments = Vec::with_capacity(assists.len());
794 for assist in assists {
795 arguments.push(to_value(assist.source_change.try_conv_with(&world)?)?);
798 let command = Some(Command {
799 title: title.clone(),
800 command: "rust-analyzer.selectAndApplySourceChange".to_string(),
801 arguments: Some(vec![serde_json::Value::Array(arguments)]),
803 res[idx] = CodeAction {
818 pub fn handle_code_lens(
819 world: WorldSnapshot,
820 params: req::CodeLensParams,
821 ) -> Result<Option<Vec<CodeLens>>> {
822 let _p = profile("handle_code_lens");
823 let file_id = params.text_document.try_conv_with(&world)?;
824 let line_index = world.analysis().file_line_index(file_id)?;
826 let mut lenses: Vec<CodeLens> = Default::default();
829 for runnable in world.analysis().runnables(file_id)? {
830 let title = match &runnable.kind {
831 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
832 RunnableKind::Bench { .. } => "Run Bench",
833 RunnableKind::Bin => "Run",
836 let mut r = to_lsp_runnable(&world, file_id, runnable)?;
837 let lens = CodeLens {
839 command: Some(Command {
841 command: "rust-analyzer.runSingle".into(),
842 arguments: Some(vec![to_value(&r).unwrap()]),
848 if r.args[0] == "run" {
849 r.args[0] = "build".into();
851 r.args.push("--no-run".into());
853 let debug_lens = CodeLens {
855 command: Some(Command {
856 title: "Debug".into(),
857 command: "rust-analyzer.debugSingle".into(),
858 arguments: Some(vec![to_value(r).unwrap()]),
862 lenses.push(debug_lens);
869 .file_structure(file_id)?
871 .filter(|it| match it.kind {
872 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
876 let range = it.node_range.conv_with(&line_index);
877 let pos = range.start;
878 let lens_params = req::GotoImplementationParams {
879 text_document_position_params: req::TextDocumentPositionParams::new(
880 params.text_document.clone(),
883 work_done_progress_params: Default::default(),
884 partial_result_params: Default::default(),
889 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
897 #[derive(Debug, Serialize, Deserialize)]
898 #[serde(rename_all = "camelCase")]
899 enum CodeLensResolveData {
900 Impls(req::GotoImplementationParams),
903 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
904 let _p = profile("handle_code_lens_resolve");
905 let data = code_lens.data.unwrap();
906 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
908 Some(CodeLensResolveData::Impls(lens_params)) => {
909 let locations: Vec<Location> =
910 match handle_goto_implementation(world, lens_params.clone())? {
911 Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
912 Some(req::GotoDefinitionResponse::Array(locs)) => locs,
913 Some(req::GotoDefinitionResponse::Link(links)) => links
915 .map(|link| Location::new(link.target_uri, link.target_selection_range))
920 let title = if locations.len() == 1 {
921 "1 implementation".into()
923 format!("{} implementations", locations.len())
926 // We cannot use the 'editor.action.showReferences' command directly
927 // because that command requires vscode types which we convert in the handler
928 // on the client side.
931 command: "rust-analyzer.showReferences".into(),
932 arguments: Some(vec![
933 to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
934 to_value(code_lens.range.start).unwrap(),
935 to_value(locations).unwrap(),
938 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
940 None => Ok(CodeLens {
941 range: code_lens.range,
942 command: Some(Command { title: "Error".into(), ..Default::default() }),
948 pub fn handle_document_highlight(
949 world: WorldSnapshot,
950 params: req::DocumentHighlightParams,
951 ) -> Result<Option<Vec<DocumentHighlight>>> {
952 let _p = profile("handle_document_highlight");
953 let file_id = params.text_document_position_params.text_document.try_conv_with(&world)?;
954 let line_index = world.analysis().file_line_index(file_id)?;
956 let refs = match world.analysis().find_all_refs(
957 params.text_document_position_params.try_conv_with(&world)?,
958 Some(SearchScope::single_file(file_id)),
960 None => return Ok(None),
966 .filter(|reference| reference.file_range.file_id == file_id)
967 .map(|reference| DocumentHighlight {
968 range: reference.file_range.range.conv_with(&line_index),
969 kind: reference.access.map(|it| it.conv()),
975 pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> {
976 let _p = profile("handle_ssr");
979 .structural_search_replace(¶ms.query, params.parse_only)??
980 .try_conv_with(&world)
983 pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
984 let _p = profile("publish_diagnostics");
985 let line_index = world.analysis().file_line_index(file_id)?;
986 let diagnostics: Vec<Diagnostic> = world
988 .diagnostics(file_id)?
990 .map(|d| Diagnostic {
991 range: d.range.conv_with(&line_index),
992 severity: Some(d.severity.conv()),
994 source: Some("rust-analyzer".to_string()),
996 related_information: None,
1000 Ok(DiagnosticTask::SetNative(file_id, diagnostics))
1004 world: &WorldSnapshot,
1007 ) -> Result<req::Runnable> {
1008 let spec = CargoTargetSpec::for_file(world, file_id)?;
1009 let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
1010 let line_index = world.analysis().file_line_index(file_id)?;
1011 let label = match &runnable.kind {
1012 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
1013 RunnableKind::TestMod { path } => format!("test-mod {}", path),
1014 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
1015 RunnableKind::Bin => "run binary".to_string(),
1018 range: runnable.range.conv_with(&line_index),
1020 bin: "cargo".to_string(),
1024 let mut m = FxHashMap::default();
1025 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
1028 cwd: world.workspace_root_for(file_id).map(|root| root.to_owned()),
1032 pub fn handle_inlay_hints(
1033 world: WorldSnapshot,
1034 params: InlayHintsParams,
1035 ) -> Result<Vec<InlayHint>> {
1036 let _p = profile("handle_inlay_hints");
1037 let file_id = params.text_document.try_conv_with(&world)?;
1038 let analysis = world.analysis();
1039 let line_index = analysis.file_line_index(file_id)?;
1041 .inlay_hints(file_id, &world.config.inlay_hints)?
1043 .map_conv_with(&line_index)
1047 pub fn handle_call_hierarchy_prepare(
1048 world: WorldSnapshot,
1049 params: CallHierarchyPrepareParams,
1050 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1051 let _p = profile("handle_call_hierarchy_prepare");
1052 let position = params.text_document_position_params.try_conv_with(&world)?;
1053 let file_id = position.file_id;
1055 let nav_info = match world.analysis().call_hierarchy(position)? {
1056 None => return Ok(None),
1060 let line_index = world.analysis().file_line_index(file_id)?;
1061 let RangeInfo { range, info: navs } = nav_info;
1064 .filter(|it| it.kind() == SyntaxKind::FN_DEF)
1065 .filter_map(|it| to_call_hierarchy_item(file_id, range, &world, &line_index, it).ok())
1071 pub fn handle_call_hierarchy_incoming(
1072 world: WorldSnapshot,
1073 params: CallHierarchyIncomingCallsParams,
1074 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1075 let _p = profile("handle_call_hierarchy_incoming");
1076 let item = params.item;
1078 let doc = TextDocumentIdentifier::new(item.uri);
1079 let frange: FileRange = (&doc, item.range).try_conv_with(&world)?;
1080 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1082 let call_items = match world.analysis().incoming_calls(fpos)? {
1083 None => return Ok(None),
1087 let mut res = vec![];
1089 for call_item in call_items.into_iter() {
1090 let file_id = call_item.target.file_id();
1091 let line_index = world.analysis().file_line_index(file_id)?;
1092 let range = call_item.target.range();
1093 let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?;
1094 res.push(CallHierarchyIncomingCall {
1096 from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(),
1103 pub fn handle_call_hierarchy_outgoing(
1104 world: WorldSnapshot,
1105 params: CallHierarchyOutgoingCallsParams,
1106 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1107 let _p = profile("handle_call_hierarchy_outgoing");
1108 let item = params.item;
1110 let doc = TextDocumentIdentifier::new(item.uri);
1111 let frange: FileRange = (&doc, item.range).try_conv_with(&world)?;
1112 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1114 let call_items = match world.analysis().outgoing_calls(fpos)? {
1115 None => return Ok(None),
1119 let mut res = vec![];
1121 for call_item in call_items.into_iter() {
1122 let file_id = call_item.target.file_id();
1123 let line_index = world.analysis().file_line_index(file_id)?;
1124 let range = call_item.target.range();
1125 let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?;
1126 res.push(CallHierarchyOutgoingCall {
1128 from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(),
1135 pub fn handle_semantic_tokens(
1136 world: WorldSnapshot,
1137 params: SemanticTokensParams,
1138 ) -> Result<Option<SemanticTokensResult>> {
1139 let _p = profile("handle_semantic_tokens");
1141 let file_id = params.text_document.try_conv_with(&world)?;
1142 let text = world.analysis().file_text(file_id)?;
1143 let line_index = world.analysis().file_line_index(file_id)?;
1145 let mut builder = SemanticTokensBuilder::default();
1147 for highlight_range in world.analysis().highlight(file_id)?.into_iter() {
1148 let (token_index, modifier_bitset) = highlight_range.highlight.conv();
1149 for mut range in line_index.lines(highlight_range.range) {
1150 if text[range].ends_with('\n') {
1151 range = TextRange::new(range.start(), range.end() - TextSize::of('\n'));
1153 let range = range.conv_with(&line_index);
1154 builder.push(range, token_index, modifier_bitset);
1158 let tokens = builder.build();
1160 Ok(Some(tokens.into()))
1163 pub fn handle_semantic_tokens_range(
1164 world: WorldSnapshot,
1165 params: SemanticTokensRangeParams,
1166 ) -> Result<Option<SemanticTokensRangeResult>> {
1167 let _p = profile("handle_semantic_tokens_range");
1169 let frange = (¶ms.text_document, params.range).try_conv_with(&world)?;
1170 let line_index = world.analysis().file_line_index(frange.file_id)?;
1172 let mut builder = SemanticTokensBuilder::default();
1174 for highlight_range in world.analysis().highlight_range(frange)?.into_iter() {
1175 let (token_type, token_modifiers) = highlight_range.highlight.conv();
1176 builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
1179 let tokens = builder.build();
1181 Ok(Some(tokens.into()))