1 //! FIXME: write short doc here
3 use std::{fmt::Write as _, io::Write as _};
5 use lsp_server::ErrorCode;
7 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
8 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
9 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
10 Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
13 AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope,
16 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
17 use rustc_hash::FxHashMap;
18 use serde::{Deserialize, Serialize};
19 use serde_json::to_value;
22 cargo_target_spec::{runnable_args, CargoTargetSpec},
23 conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
24 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
29 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
30 let _p = profile("handle_analyzer_status");
31 let mut buf = world.status();
32 writeln!(buf, "\n\nrequests:").unwrap();
33 let requests = world.latest_requests.read();
34 for (is_last, r) in requests.iter() {
35 let mark = if is_last { "*" } else { " " };
36 writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap();
41 pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
42 let _p = profile("handle_syntax_tree");
43 let id = params.text_document.try_conv_with(&world)?;
44 let line_index = world.analysis().file_line_index(id)?;
45 let text_range = params.range.map(|p| p.conv_with(&line_index));
46 let res = world.analysis().syntax_tree(id, text_range)?;
50 pub fn handle_expand_macro(
52 params: req::ExpandMacroParams,
53 ) -> Result<Option<req::ExpandedMacro>> {
54 let _p = profile("handle_expand_macro");
55 let file_id = params.text_document.try_conv_with(&world)?;
56 let line_index = world.analysis().file_line_index(file_id)?;
57 let offset = params.position.map(|p| p.conv_with(&line_index));
62 let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
63 Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion }))
68 pub fn handle_selection_range(
70 params: req::SelectionRangeParams,
71 ) -> Result<Vec<req::SelectionRange>> {
72 let _p = profile("handle_selection_range");
73 let file_id = params.text_document.try_conv_with(&world)?;
74 let line_index = world.analysis().file_line_index(file_id)?;
78 .map_conv_with(&line_index)
80 let mut ranges = Vec::new();
82 let mut range = TextRange::from_to(position, position);
85 let frange = FileRange { file_id, range };
86 let next = world.analysis().extend_selection(frange)?;
94 let mut range = req::SelectionRange {
95 range: ranges.last().unwrap().conv_with(&line_index),
98 for r in ranges.iter().rev().skip(1) {
99 range = req::SelectionRange {
100 range: r.conv_with(&line_index),
101 parent: Some(Box::new(range)),
109 pub fn handle_find_matching_brace(
110 world: WorldSnapshot,
111 params: req::FindMatchingBraceParams,
112 ) -> Result<Vec<Position>> {
113 let _p = profile("handle_find_matching_brace");
114 let file_id = params.text_document.try_conv_with(&world)?;
115 let line_index = world.analysis().file_line_index(file_id)?;
119 .map_conv_with(&line_index)
121 if let Ok(Some(matching_brace_offset)) =
122 world.analysis().matching_brace(FilePosition { file_id, offset })
124 matching_brace_offset
129 .map_conv_with(&line_index)
134 pub fn handle_join_lines(
135 world: WorldSnapshot,
136 params: req::JoinLinesParams,
137 ) -> Result<req::SourceChange> {
138 let _p = profile("handle_join_lines");
139 let frange = (¶ms.text_document, params.range).try_conv_with(&world)?;
140 world.analysis().join_lines(frange)?.try_conv_with(&world)
143 pub fn handle_on_enter(
144 world: WorldSnapshot,
145 params: req::TextDocumentPositionParams,
146 ) -> Result<Option<req::SourceChange>> {
147 let _p = profile("handle_on_enter");
148 let position = params.try_conv_with(&world)?;
149 match world.analysis().on_enter(position)? {
151 Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
155 // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
156 pub fn handle_on_type_formatting(
157 world: WorldSnapshot,
158 params: req::DocumentOnTypeFormattingParams,
159 ) -> Result<Option<Vec<TextEdit>>> {
160 let _p = profile("handle_on_type_formatting");
161 let mut position = params.text_document_position.try_conv_with(&world)?;
162 let line_index = world.analysis().file_line_index(position.file_id)?;
163 let line_endings = world.file_line_endings(position.file_id);
165 // in `ra_ide_api`, the `on_type` invariant is that
166 // `text.char_at(position) == typed_char`.
167 position.offset = position.offset - TextUnit::of_char('.');
168 let char_typed = params.ch.chars().next().unwrap_or('\0');
170 // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
171 // but it requires precise cursor positioning to work, and one can't
172 // position the cursor with on_type formatting. So, let's just toggle this
173 // feature off here, hoping that we'll enable it one day, 😿.
174 if char_typed == '>' {
178 let edit = world.analysis().on_char_typed(position, char_typed)?;
179 let mut edit = match edit {
181 None => return Ok(None),
184 // This should be a single-file edit
185 let edit = edit.source_file_edits.pop().unwrap();
187 let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
191 pub fn handle_document_symbol(
192 world: WorldSnapshot,
193 params: req::DocumentSymbolParams,
194 ) -> Result<Option<req::DocumentSymbolResponse>> {
195 let _p = profile("handle_document_symbol");
196 let file_id = params.text_document.try_conv_with(&world)?;
197 let line_index = world.analysis().file_line_index(file_id)?;
199 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
201 for symbol in world.analysis().file_structure(file_id)? {
202 let doc_symbol = DocumentSymbol {
204 detail: symbol.detail,
205 kind: symbol.kind.conv(),
206 deprecated: Some(symbol.deprecated),
207 range: symbol.node_range.conv_with(&line_index),
208 selection_range: symbol.navigation_range.conv_with(&line_index),
211 parents.push((doc_symbol, symbol.parent));
213 let mut res = Vec::new();
214 while let Some((node, parent)) = parents.pop() {
216 None => res.push(node),
218 let children = &mut parents[i].0.children;
219 if children.is_none() {
220 *children = Some(Vec::new());
222 children.as_mut().unwrap().push(node);
230 pub fn handle_workspace_symbol(
231 world: WorldSnapshot,
232 params: req::WorkspaceSymbolParams,
233 ) -> Result<Option<Vec<SymbolInformation>>> {
234 let _p = profile("handle_workspace_symbol");
235 let all_symbols = params.query.contains('#');
236 let libs = params.query.contains('*');
238 let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
239 let mut q = Query::new(query);
249 let mut res = exec_query(&world, query)?;
250 if res.is_empty() && !all_symbols {
251 let mut query = Query::new(params.query);
253 res = exec_query(&world, query)?;
256 return Ok(Some(res));
258 fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
259 let mut res = Vec::new();
260 for nav in world.analysis().symbol_search(query)? {
261 let info = SymbolInformation {
262 name: nav.name().to_string(),
263 kind: nav.kind().conv(),
264 location: nav.try_conv_with(world)?,
265 container_name: nav.container_name().map(|v| v.to_string()),
274 pub fn handle_goto_definition(
275 world: WorldSnapshot,
276 params: req::TextDocumentPositionParams,
277 ) -> Result<Option<req::GotoDefinitionResponse>> {
278 let _p = profile("handle_goto_definition");
279 let position = params.try_conv_with(&world)?;
280 let nav_info = match world.analysis().goto_definition(position)? {
281 None => return Ok(None),
284 let res = (position.file_id, nav_info).try_conv_with(&world)?;
288 pub fn handle_goto_implementation(
289 world: WorldSnapshot,
290 params: req::TextDocumentPositionParams,
291 ) -> Result<Option<req::GotoImplementationResponse>> {
292 let _p = profile("handle_goto_implementation");
293 let position = params.try_conv_with(&world)?;
294 let nav_info = match world.analysis().goto_implementation(position)? {
295 None => return Ok(None),
298 let res = (position.file_id, nav_info).try_conv_with(&world)?;
302 pub fn handle_goto_type_definition(
303 world: WorldSnapshot,
304 params: req::TextDocumentPositionParams,
305 ) -> Result<Option<req::GotoTypeDefinitionResponse>> {
306 let _p = profile("handle_goto_type_definition");
307 let position = params.try_conv_with(&world)?;
308 let nav_info = match world.analysis().goto_type_definition(position)? {
309 None => return Ok(None),
312 let res = (position.file_id, nav_info).try_conv_with(&world)?;
316 pub fn handle_parent_module(
317 world: WorldSnapshot,
318 params: req::TextDocumentPositionParams,
319 ) -> Result<Vec<Location>> {
320 let _p = profile("handle_parent_module");
321 let position = params.try_conv_with(&world)?;
322 world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
325 pub fn handle_runnables(
326 world: WorldSnapshot,
327 params: req::RunnablesParams,
328 ) -> Result<Vec<req::Runnable>> {
329 let _p = profile("handle_runnables");
330 let file_id = params.text_document.try_conv_with(&world)?;
331 let line_index = world.analysis().file_line_index(file_id)?;
332 let offset = params.position.map(|it| it.conv_with(&line_index));
333 let mut res = Vec::new();
334 let workspace_root = world.workspace_root_for(file_id);
335 for runnable in world.analysis().runnables(file_id)? {
336 if let Some(offset) = offset {
337 if !runnable.range.contains_inclusive(offset) {
341 res.push(to_lsp_runnable(&world, file_id, runnable)?);
343 let mut check_args = vec!["check".to_string()];
345 match CargoTargetSpec::for_file(&world, file_id)? {
347 label = format!("cargo check -p {}", spec.package);
348 spec.push_to(&mut check_args);
351 label = "cargo check --all".to_string();
352 check_args.push("--all".to_string())
355 // Always add `cargo check`.
356 res.push(req::Runnable {
357 range: Default::default(),
359 bin: "cargo".to_string(),
361 env: FxHashMap::default(),
362 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
367 pub fn handle_decorations(
368 world: WorldSnapshot,
369 params: TextDocumentIdentifier,
370 ) -> Result<Vec<Decoration>> {
371 let _p = profile("handle_decorations");
372 let file_id = params.try_conv_with(&world)?;
373 highlight(&world, file_id)
376 pub fn handle_completion(
377 world: WorldSnapshot,
378 params: req::CompletionParams,
379 ) -> Result<Option<req::CompletionResponse>> {
380 let _p = profile("handle_completion");
381 let position = params.text_document_position.try_conv_with(&world)?;
382 let completion_triggered_after_single_colon = {
384 if let Some(ctx) = params.context {
385 if ctx.trigger_character.unwrap_or_default() == ":" {
386 let source_file = world.analysis().parse(position.file_id)?;
387 let syntax = source_file.syntax();
388 let text = syntax.text();
389 if let Some(next_char) = text.char_at(position.offset) {
390 let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':');
391 let prev_char = position.offset - diff;
392 if text.char_at(prev_char) != Some(':') {
400 if completion_triggered_after_single_colon {
404 let items = match world.analysis().completions(position)? {
405 None => return Ok(None),
406 Some(items) => items,
408 let line_index = world.analysis().file_line_index(position.file_id)?;
409 let line_endings = world.file_line_endings(position.file_id);
410 let items: Vec<CompletionItem> =
411 items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
413 Ok(Some(items.into()))
416 pub fn handle_folding_range(
417 world: WorldSnapshot,
418 params: FoldingRangeParams,
419 ) -> Result<Option<Vec<FoldingRange>>> {
420 let _p = profile("handle_folding_range");
421 let file_id = params.text_document.try_conv_with(&world)?;
422 let folds = world.analysis().folding_ranges(file_id)?;
423 let text = world.analysis().file_text(file_id)?;
424 let line_index = world.analysis().file_line_index(file_id)?;
425 let ctx = FoldConvCtx {
427 line_index: &line_index,
428 line_folding_only: world.options.line_folding_only,
430 let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
434 pub fn handle_signature_help(
435 world: WorldSnapshot,
436 params: req::TextDocumentPositionParams,
437 ) -> Result<Option<req::SignatureHelp>> {
438 let _p = profile("handle_signature_help");
439 let position = params.try_conv_with(&world)?;
440 if let Some(call_info) = world.analysis().call_info(position)? {
441 let active_parameter = call_info.active_parameter.map(|it| it as i64);
442 let sig_info = call_info.signature.conv();
444 Ok(Some(req::SignatureHelp {
445 signatures: vec![sig_info],
446 active_signature: Some(0),
455 world: WorldSnapshot,
456 params: req::TextDocumentPositionParams,
457 ) -> Result<Option<Hover>> {
458 let _p = profile("handle_hover");
459 let position = params.try_conv_with(&world)?;
460 let info = match world.analysis().hover(position)? {
461 None => return Ok(None),
464 let line_index = world.analysis.file_line_index(position.file_id)?;
465 let range = info.range.conv_with(&line_index);
467 contents: HoverContents::Markup(MarkupContent {
468 kind: MarkupKind::Markdown,
469 value: crate::markdown::format_docs(&info.info.to_markup()),
476 pub fn handle_prepare_rename(
477 world: WorldSnapshot,
478 params: req::TextDocumentPositionParams,
479 ) -> Result<Option<PrepareRenameResponse>> {
480 let _p = profile("handle_prepare_rename");
481 let position = params.try_conv_with(&world)?;
483 // We support renaming references like handle_rename does.
484 // In the future we may want to reject the renaming of things like keywords here too.
485 let optional_change = world.analysis().rename(position, "dummy")?;
486 let range = match optional_change {
487 None => return Ok(None),
488 Some(it) => it.range,
491 let file_id = params.text_document.try_conv_with(&world)?;
492 let line_index = world.analysis().file_line_index(file_id)?;
493 let range = range.conv_with(&line_index);
494 Ok(Some(PrepareRenameResponse::Range(range)))
497 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
498 let _p = profile("handle_rename");
499 let position = params.text_document_position.try_conv_with(&world)?;
501 if params.new_name.is_empty() {
502 return Err(LspError::new(
503 ErrorCode::InvalidParams as i32,
504 "New Name cannot be empty".into(),
509 let optional_change = world.analysis().rename(position, &*params.new_name)?;
510 let change = match optional_change {
511 None => return Ok(None),
515 let source_change_req = change.try_conv_with(&world)?;
517 Ok(Some(source_change_req.workspace_edit))
520 pub fn handle_references(
521 world: WorldSnapshot,
522 params: req::ReferenceParams,
523 ) -> Result<Option<Vec<Location>>> {
524 let _p = profile("handle_references");
525 let position = params.text_document_position.try_conv_with(&world)?;
527 let refs = match world.analysis().find_all_refs(position, None)? {
528 None => return Ok(None),
532 let locations = if params.context.include_declaration {
535 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
536 to_location(r.file_id, r.range, &world, &line_index).ok()
540 // Only iterate over the references if include_declaration was false
544 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
545 to_location(r.file_id, r.range, &world, &line_index).ok()
553 pub fn handle_formatting(
554 world: WorldSnapshot,
555 params: DocumentFormattingParams,
556 ) -> Result<Option<Vec<TextEdit>>> {
557 let _p = profile("handle_formatting");
558 let file_id = params.text_document.try_conv_with(&world)?;
559 let file = world.analysis().file_text(file_id)?;
561 let file_line_index = world.analysis().file_line_index(file_id)?;
562 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
565 let mut rustfmt = process::Command::new("rustfmt");
566 rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped());
568 if let Ok(path) = params.text_document.uri.to_file_path() {
569 if let Some(parent) = path.parent() {
570 rustfmt.current_dir(parent);
573 let mut rustfmt = rustfmt.spawn()?;
575 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
577 let output = rustfmt.wait_with_output()?;
578 let captured_stdout = String::from_utf8(output.stdout)?;
580 if !output.status.success() {
581 match output.status.code() {
583 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
584 // likely cause exiting with 1. Most Language Servers swallow parse errors on
585 // formatting because otherwise an error is surfaced to the user on top of the
586 // syntax error diagnostics they're already receiving. This is especially jarring
587 // if they have format on save enabled.
588 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
592 // Something else happened - e.g. `rustfmt` is missing or caught a signal
593 return Err(LspError::new(
596 r#"rustfmt exited with:
599 output.status, captured_stdout,
607 Ok(Some(vec![TextEdit {
608 range: Range::new(Position::new(0, 0), end_position),
609 new_text: captured_stdout,
613 pub fn handle_code_action(
614 world: WorldSnapshot,
615 params: req::CodeActionParams,
616 ) -> Result<Option<CodeActionResponse>> {
617 let _p = profile("handle_code_action");
618 let file_id = params.text_document.try_conv_with(&world)?;
619 let line_index = world.analysis().file_line_index(file_id)?;
620 let range = params.range.conv_with(&line_index);
622 let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
623 let diagnostics = world.analysis().diagnostics(file_id)?;
624 let mut res = CodeActionResponse::default();
626 let fixes_from_diagnostics = diagnostics
628 .filter_map(|d| Some((d.range, d.fix?)))
629 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
630 .map(|(_range, fix)| fix);
632 for source_edit in fixes_from_diagnostics {
633 let title = source_edit.label.clone();
634 let edit = source_edit.try_conv_with(&world)?;
636 let command = Command {
638 command: "rust-analyzer.applySourceChange".to_string(),
639 arguments: Some(vec![to_value(edit).unwrap()]),
641 let action = CodeAction {
642 title: command.title.clone(),
646 command: Some(command),
648 res.push(action.into());
651 for assist in assists {
652 let title = assist.change.label.clone();
653 let edit = assist.change.try_conv_with(&world)?;
655 let command = Command {
657 command: "rust-analyzer.applySourceChange".to_string(),
658 arguments: Some(vec![to_value(edit).unwrap()]),
660 let action = CodeAction {
661 title: command.title.clone(),
662 kind: match assist.id {
663 AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
668 command: Some(command),
670 res.push(action.into());
676 pub fn handle_code_lens(
677 world: WorldSnapshot,
678 params: req::CodeLensParams,
679 ) -> Result<Option<Vec<CodeLens>>> {
680 let _p = profile("handle_code_lens");
681 let file_id = params.text_document.try_conv_with(&world)?;
682 let line_index = world.analysis().file_line_index(file_id)?;
684 let mut lenses: Vec<CodeLens> = Default::default();
687 for runnable in world.analysis().runnables(file_id)? {
688 let title = match &runnable.kind {
689 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️Run Test",
690 RunnableKind::Bench { .. } => "Run Bench",
691 RunnableKind::Bin => "Run",
694 let r = to_lsp_runnable(&world, file_id, runnable)?;
695 let lens = CodeLens {
697 command: Some(Command {
699 command: "rust-analyzer.runSingle".into(),
700 arguments: Some(vec![to_value(r).unwrap()]),
712 .file_structure(file_id)?
714 .filter(|it| match it.kind {
715 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
719 let range = it.node_range.conv_with(&line_index);
720 let pos = range.start;
722 req::TextDocumentPositionParams::new(params.text_document.clone(), pos);
726 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
734 #[derive(Debug, Serialize, Deserialize)]
735 #[serde(rename_all = "camelCase")]
736 enum CodeLensResolveData {
737 Impls(req::TextDocumentPositionParams),
740 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
741 let _p = profile("handle_code_lens_resolve");
742 let data = code_lens.data.unwrap();
743 let resolve = serde_json::from_value(data)?;
745 Some(CodeLensResolveData::Impls(lens_params)) => {
746 let locations: Vec<Location> =
747 match handle_goto_implementation(world, lens_params.clone())? {
748 Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
749 Some(req::GotoDefinitionResponse::Array(locs)) => locs,
750 Some(req::GotoDefinitionResponse::Link(links)) => links
752 .map(|link| Location::new(link.target_uri, link.target_selection_range))
757 let title = if locations.len() == 1 {
758 "1 implementation".into()
760 format!("{} implementations", locations.len())
763 // We cannot use the 'editor.action.showReferences' command directly
764 // because that command requires vscode types which we convert in the handler
765 // on the client side.
768 command: "rust-analyzer.showReferences".into(),
769 arguments: Some(vec![
770 to_value(&lens_params.text_document.uri).unwrap(),
771 to_value(code_lens.range.start).unwrap(),
772 to_value(locations).unwrap(),
775 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
777 None => Ok(CodeLens {
778 range: code_lens.range,
779 command: Some(Command { title: "Error".into(), ..Default::default() }),
785 pub fn handle_document_highlight(
786 world: WorldSnapshot,
787 params: req::TextDocumentPositionParams,
788 ) -> Result<Option<Vec<DocumentHighlight>>> {
789 let _p = profile("handle_document_highlight");
790 let file_id = params.text_document.try_conv_with(&world)?;
791 let line_index = world.analysis().file_line_index(file_id)?;
793 let refs = match world
795 .find_all_refs(params.try_conv_with(&world)?, Some(SearchScope::single_file(file_id)))?
797 None => return Ok(None),
803 .filter(|r| r.file_id == file_id)
804 .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
809 pub fn publish_diagnostics(
810 world: &WorldSnapshot,
812 ) -> Result<req::PublishDiagnosticsParams> {
813 let _p = profile("publish_diagnostics");
814 let uri = world.file_id_to_uri(file_id)?;
815 let line_index = world.analysis().file_line_index(file_id)?;
816 let diagnostics = world
818 .diagnostics(file_id)?
820 .map(|d| Diagnostic {
821 range: d.range.conv_with(&line_index),
822 severity: Some(d.severity.conv()),
824 source: Some("rust-analyzer".to_string()),
826 related_information: None,
829 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
832 pub fn publish_decorations(
833 world: &WorldSnapshot,
835 ) -> Result<req::PublishDecorationsParams> {
836 let _p = profile("publish_decorations");
837 let uri = world.file_id_to_uri(file_id)?;
838 Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
842 world: &WorldSnapshot,
845 ) -> Result<req::Runnable> {
846 let args = runnable_args(world, file_id, &runnable.kind)?;
847 let line_index = world.analysis().file_line_index(file_id)?;
848 let label = match &runnable.kind {
849 RunnableKind::Test { name } => format!("test {}", name),
850 RunnableKind::TestMod { path } => format!("test-mod {}", path),
851 RunnableKind::Bench { name } => format!("bench {}", name),
852 RunnableKind::Bin => "run binary".to_string(),
855 range: runnable.range.conv_with(&line_index),
857 bin: "cargo".to_string(),
860 let mut m = FxHashMap::default();
861 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
864 cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
867 fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
868 let line_index = world.analysis().file_line_index(file_id)?;
873 .map(|h| Decoration {
874 range: h.range.conv_with(&line_index),
876 binding_hash: h.binding_hash.map(|x| x.to_string()),
882 pub fn handle_inlay_hints(
883 world: WorldSnapshot,
884 params: InlayHintsParams,
885 ) -> Result<Vec<InlayHint>> {
886 let _p = profile("handle_inlay_hints");
887 let file_id = params.text_document.try_conv_with(&world)?;
888 let analysis = world.analysis();
889 let line_index = analysis.file_line_index(file_id)?;
891 .inlay_hints(file_id)?
893 .map(|api_type| InlayHint {
894 label: api_type.label.to_string(),
895 range: api_type.range.conv_with(&line_index),
896 kind: match api_type.kind {
897 ra_ide_api::InlayKind::TypeHint => InlayKind::TypeHint,