1 use std::{fmt::Write as _, io::Write as _};
3 use gen_lsp_server::ErrorCode;
5 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
6 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind,
7 FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position,
8 PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier,
9 TextEdit, WorkspaceEdit,
12 AssistId, FileId, FilePosition, FileRange, FoldKind, Query, Runnable, RunnableKind,
15 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
16 use rustc_hash::FxHashMap;
17 use serde::{Deserialize, Serialize};
18 use serde_json::to_value;
21 cargo_target_spec::{runnable_args, CargoTargetSpec},
22 conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith, TryConvWithToVec},
23 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
28 pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
29 let mut buf = world.status();
30 writeln!(buf, "\n\nrequests:").unwrap();
31 let requests = world.latest_requests.read();
32 for (is_last, r) in requests.iter() {
33 let mark = if is_last { "*" } else { " " };
34 writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap();
39 pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
40 let id = params.text_document.try_conv_with(&world)?;
41 let line_index = world.analysis().file_line_index(id)?;
42 let text_range = params.range.map(|p| p.conv_with(&line_index));
43 let res = world.analysis().syntax_tree(id, text_range)?;
47 pub fn handle_selection_range(
49 params: req::SelectionRangeParams,
50 ) -> Result<Vec<req::SelectionRange>> {
51 let _p = profile("handle_selection_range");
52 let file_id = params.text_document.try_conv_with(&world)?;
53 let line_index = world.analysis().file_line_index(file_id)?;
57 .map_conv_with(&line_index)
59 let mut ranges = Vec::new();
61 let mut range = TextRange::from_to(position, position);
64 let frange = FileRange { file_id, range };
65 let next = world.analysis().extend_selection(frange)?;
73 let mut range = req::SelectionRange {
74 range: ranges.last().unwrap().conv_with(&line_index),
77 for r in ranges.iter().rev().skip(1) {
78 range = req::SelectionRange {
79 range: r.conv_with(&line_index),
80 parent: Some(Box::new(range)),
88 pub fn handle_find_matching_brace(
90 params: req::FindMatchingBraceParams,
91 ) -> Result<Vec<Position>> {
92 let _p = profile("handle_find_matching_brace");
93 let file_id = params.text_document.try_conv_with(&world)?;
94 let line_index = world.analysis().file_line_index(file_id)?;
98 .map_conv_with(&line_index)
100 if let Ok(Some(matching_brace_offset)) =
101 world.analysis().matching_brace(FilePosition { file_id, offset })
103 matching_brace_offset
108 .map_conv_with(&line_index)
113 pub fn handle_join_lines(
114 world: WorldSnapshot,
115 params: req::JoinLinesParams,
116 ) -> Result<req::SourceChange> {
117 let _p = profile("handle_join_lines");
118 let frange = (¶ms.text_document, params.range).try_conv_with(&world)?;
119 world.analysis().join_lines(frange)?.try_conv_with(&world)
122 pub fn handle_on_enter(
123 world: WorldSnapshot,
124 params: req::TextDocumentPositionParams,
125 ) -> Result<Option<req::SourceChange>> {
126 let _p = profile("handle_on_enter");
127 let position = params.try_conv_with(&world)?;
128 match world.analysis().on_enter(position)? {
130 Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
134 pub fn handle_on_type_formatting(
135 world: WorldSnapshot,
136 params: req::DocumentOnTypeFormattingParams,
137 ) -> Result<Option<Vec<TextEdit>>> {
138 let _p = profile("handle_on_type_formatting");
139 let mut position = params.text_document_position.try_conv_with(&world)?;
140 let line_index = world.analysis().file_line_index(position.file_id)?;
141 let line_endings = world.file_line_endings(position.file_id);
143 // in `ra_ide_api`, the `on_type` invariant is that
144 // `text.char_at(position) == typed_char`.
145 position.offset = position.offset - TextUnit::of_char('.');
147 let edit = match params.ch.as_str() {
148 "=" => world.analysis().on_eq_typed(position),
149 "." => world.analysis().on_dot_typed(position),
150 _ => return Ok(None),
152 let mut edit = match edit {
154 None => return Ok(None),
157 // This should be a single-file edit
158 let edit = edit.source_file_edits.pop().unwrap();
160 let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
164 pub fn handle_document_symbol(
165 world: WorldSnapshot,
166 params: req::DocumentSymbolParams,
167 ) -> Result<Option<req::DocumentSymbolResponse>> {
168 let file_id = params.text_document.try_conv_with(&world)?;
169 let line_index = world.analysis().file_line_index(file_id)?;
171 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
173 for symbol in world.analysis().file_structure(file_id)? {
174 let doc_symbol = DocumentSymbol {
176 detail: symbol.detail,
177 kind: symbol.kind.conv(),
178 deprecated: Some(symbol.deprecated),
179 range: symbol.node_range.conv_with(&line_index),
180 selection_range: symbol.navigation_range.conv_with(&line_index),
183 parents.push((doc_symbol, symbol.parent));
185 let mut res = Vec::new();
186 while let Some((node, parent)) = parents.pop() {
188 None => res.push(node),
190 let children = &mut parents[i].0.children;
191 if children.is_none() {
192 *children = Some(Vec::new());
194 children.as_mut().unwrap().push(node);
202 pub fn handle_workspace_symbol(
203 world: WorldSnapshot,
204 params: req::WorkspaceSymbolParams,
205 ) -> Result<Option<Vec<SymbolInformation>>> {
206 let all_symbols = params.query.contains('#');
207 let libs = params.query.contains('*');
209 let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
210 let mut q = Query::new(query);
220 let mut res = exec_query(&world, query)?;
221 if res.is_empty() && !all_symbols {
222 let mut query = Query::new(params.query);
224 res = exec_query(&world, query)?;
227 return Ok(Some(res));
229 fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
230 let mut res = Vec::new();
231 for nav in world.analysis().symbol_search(query)? {
232 let info = SymbolInformation {
233 name: nav.name().to_string(),
234 kind: nav.kind().conv(),
235 location: nav.try_conv_with(world)?,
236 container_name: nav.container_name().map(|v| v.to_string()),
245 pub fn handle_goto_definition(
246 world: WorldSnapshot,
247 params: req::TextDocumentPositionParams,
248 ) -> Result<Option<req::GotoDefinitionResponse>> {
249 let position = params.try_conv_with(&world)?;
250 let nav_info = match world.analysis().goto_definition(position)? {
251 None => return Ok(None),
254 let res = (position.file_id, nav_info).try_conv_with(&world)?;
258 pub fn handle_goto_implementation(
259 world: WorldSnapshot,
260 params: req::TextDocumentPositionParams,
261 ) -> Result<Option<req::GotoImplementationResponse>> {
262 let position = params.try_conv_with(&world)?;
263 let nav_info = match world.analysis().goto_implementation(position)? {
264 None => return Ok(None),
267 let res = (position.file_id, nav_info).try_conv_with(&world)?;
271 pub fn handle_goto_type_definition(
272 world: WorldSnapshot,
273 params: req::TextDocumentPositionParams,
274 ) -> Result<Option<req::GotoTypeDefinitionResponse>> {
275 let position = params.try_conv_with(&world)?;
276 let nav_info = match world.analysis().goto_type_definition(position)? {
277 None => return Ok(None),
280 let res = (position.file_id, nav_info).try_conv_with(&world)?;
284 pub fn handle_parent_module(
285 world: WorldSnapshot,
286 params: req::TextDocumentPositionParams,
287 ) -> Result<Vec<Location>> {
288 let position = params.try_conv_with(&world)?;
289 world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
292 pub fn handle_runnables(
293 world: WorldSnapshot,
294 params: req::RunnablesParams,
295 ) -> Result<Vec<req::Runnable>> {
296 let file_id = params.text_document.try_conv_with(&world)?;
297 let line_index = world.analysis().file_line_index(file_id)?;
298 let offset = params.position.map(|it| it.conv_with(&line_index));
299 let mut res = Vec::new();
300 let workspace_root = world.workspace_root_for(file_id);
301 for runnable in world.analysis().runnables(file_id)? {
302 if let Some(offset) = offset {
303 if !runnable.range.contains_inclusive(offset) {
307 res.push(to_lsp_runnable(&world, file_id, runnable)?);
309 let mut check_args = vec!["check".to_string()];
311 match CargoTargetSpec::for_file(&world, file_id)? {
313 label = format!("cargo check -p {}", spec.package);
314 spec.push_to(&mut check_args);
317 label = "cargo check --all".to_string();
318 check_args.push("--all".to_string())
321 // Always add `cargo check`.
322 res.push(req::Runnable {
323 range: Default::default(),
325 bin: "cargo".to_string(),
327 env: FxHashMap::default(),
328 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
333 pub fn handle_decorations(
334 world: WorldSnapshot,
335 params: TextDocumentIdentifier,
336 ) -> Result<Vec<Decoration>> {
337 let file_id = params.try_conv_with(&world)?;
338 highlight(&world, file_id)
341 pub fn handle_completion(
342 world: WorldSnapshot,
343 params: req::CompletionParams,
344 ) -> Result<Option<req::CompletionResponse>> {
345 let _p = profile("handle_completion");
346 let position = params.text_document_position.try_conv_with(&world)?;
347 let completion_triggered_after_single_colon = {
349 if let Some(ctx) = params.context {
350 if ctx.trigger_character.unwrap_or_default() == ":" {
351 let source_file = world.analysis().parse(position.file_id)?;
352 let syntax = source_file.syntax();
353 let text = syntax.text();
354 if let Some(next_char) = text.char_at(position.offset) {
355 let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':');
356 let prev_char = position.offset - diff;
357 if text.char_at(prev_char) != Some(':') {
365 if completion_triggered_after_single_colon {
369 let items = match world.analysis().completions(position)? {
370 None => return Ok(None),
371 Some(items) => items,
373 let line_index = world.analysis().file_line_index(position.file_id)?;
374 let line_endings = world.file_line_endings(position.file_id);
375 let items: Vec<CompletionItem> =
376 items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
378 Ok(Some(items.into()))
381 pub fn handle_folding_range(
382 world: WorldSnapshot,
383 params: FoldingRangeParams,
384 ) -> Result<Option<Vec<FoldingRange>>> {
385 let file_id = params.text_document.try_conv_with(&world)?;
386 let line_index = world.analysis().file_line_index(file_id)?;
391 .folding_ranges(file_id)?
394 let kind = match fold.kind {
395 FoldKind::Comment => Some(FoldingRangeKind::Comment),
396 FoldKind::Imports => Some(FoldingRangeKind::Imports),
397 FoldKind::Mods => None,
398 FoldKind::Block => None,
400 let range = fold.range.conv_with(&line_index);
402 start_line: range.start.line,
403 start_character: Some(range.start.character),
404 end_line: range.end.line,
405 end_character: Some(range.start.character),
415 pub fn handle_signature_help(
416 world: WorldSnapshot,
417 params: req::TextDocumentPositionParams,
418 ) -> Result<Option<req::SignatureHelp>> {
419 let position = params.try_conv_with(&world)?;
420 if let Some(call_info) = world.analysis().call_info(position)? {
421 let active_parameter = call_info.active_parameter.map(|it| it as i64);
422 let sig_info = call_info.signature.conv();
424 Ok(Some(req::SignatureHelp {
425 signatures: vec![sig_info],
426 active_signature: Some(0),
435 world: WorldSnapshot,
436 params: req::TextDocumentPositionParams,
437 ) -> Result<Option<Hover>> {
438 let position = params.try_conv_with(&world)?;
439 let info = match world.analysis().hover(position)? {
440 None => return Ok(None),
443 let line_index = world.analysis.file_line_index(position.file_id)?;
444 let range = info.range.conv_with(&line_index);
446 contents: HoverContents::Markup(MarkupContent {
447 kind: MarkupKind::Markdown,
448 value: crate::markdown::format_docs(&info.info.to_markup()),
455 pub fn handle_prepare_rename(
456 world: WorldSnapshot,
457 params: req::TextDocumentPositionParams,
458 ) -> Result<Option<PrepareRenameResponse>> {
459 let position = params.try_conv_with(&world)?;
461 // We support renaming references like handle_rename does.
462 // In the future we may want to reject the renaming of things like keywords here too.
463 let refs = match world.analysis().find_all_refs(position)? {
464 None => return Ok(None),
468 // Refs should always have a declaration
469 let r = refs.declaration();
470 let file_id = params.text_document.try_conv_with(&world)?;
471 let line_index = world.analysis().file_line_index(file_id)?;
472 let loc = to_location(r.file_id(), r.range(), &world, &line_index)?;
474 Ok(Some(PrepareRenameResponse::Range(loc.range)))
477 pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
478 let position = params.text_document_position.try_conv_with(&world)?;
480 if params.new_name.is_empty() {
481 return Err(LspError::new(
482 ErrorCode::InvalidParams as i32,
483 "New Name cannot be empty".into(),
488 let optional_change = world.analysis().rename(position, &*params.new_name)?;
489 let change = match optional_change {
490 None => return Ok(None),
494 let source_change_req = change.try_conv_with(&world)?;
496 Ok(Some(source_change_req.workspace_edit))
499 pub fn handle_references(
500 world: WorldSnapshot,
501 params: req::ReferenceParams,
502 ) -> Result<Option<Vec<Location>>> {
503 let position = params.text_document_position.try_conv_with(&world)?;
504 let line_index = world.analysis().file_line_index(position.file_id)?;
506 let refs = match world.analysis().find_all_refs(position)? {
507 None => return Ok(None),
511 let locations = if params.context.include_declaration {
513 .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok())
516 // Only iterate over the references if include_declaration was false
519 .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok())
526 pub fn handle_formatting(
527 world: WorldSnapshot,
528 params: DocumentFormattingParams,
529 ) -> Result<Option<Vec<TextEdit>>> {
530 let file_id = params.text_document.try_conv_with(&world)?;
531 let file = world.analysis().file_text(file_id)?;
533 let file_line_index = world.analysis().file_line_index(file_id)?;
534 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
537 let mut rustfmt = process::Command::new("rustfmt");
538 rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped());
540 if let Ok(path) = params.text_document.uri.to_file_path() {
541 if let Some(parent) = path.parent() {
542 rustfmt.current_dir(parent);
545 let mut rustfmt = rustfmt.spawn()?;
547 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
549 let output = rustfmt.wait_with_output()?;
550 let captured_stdout = String::from_utf8(output.stdout)?;
552 if !output.status.success() {
553 match output.status.code() {
555 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
556 // likely cause exiting with 1. Most Language Servers swallow parse errors on
557 // formatting because otherwise an error is surfaced to the user on top of the
558 // syntax error diagnostics they're already receiving. This is especially jarring
559 // if they have format on save enabled.
560 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
564 // Something else happened - e.g. `rustfmt` is missing or caught a signal
565 return Err(LspError::new(
568 r#"rustfmt exited with:
571 output.status, captured_stdout,
579 Ok(Some(vec![TextEdit {
580 range: Range::new(Position::new(0, 0), end_position),
581 new_text: captured_stdout,
585 pub fn handle_code_action(
586 world: WorldSnapshot,
587 params: req::CodeActionParams,
588 ) -> Result<Option<CodeActionResponse>> {
589 let _p = profile("handle_code_action");
590 let file_id = params.text_document.try_conv_with(&world)?;
591 let line_index = world.analysis().file_line_index(file_id)?;
592 let range = params.range.conv_with(&line_index);
594 let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
595 let diagnostics = world.analysis().diagnostics(file_id)?;
596 let mut res = CodeActionResponse::default();
598 let fixes_from_diagnostics = diagnostics
600 .filter_map(|d| Some((d.range, d.fix?)))
601 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
602 .map(|(_range, fix)| fix);
604 for source_edit in fixes_from_diagnostics {
605 let title = source_edit.label.clone();
606 let edit = source_edit.try_conv_with(&world)?;
608 let command = Command {
610 command: "rust-analyzer.applySourceChange".to_string(),
611 arguments: Some(vec![to_value(edit).unwrap()]),
613 let action = CodeAction {
614 title: command.title.clone(),
618 command: Some(command),
620 res.push(action.into());
623 for assist in assists {
624 let title = assist.change.label.clone();
625 let edit = assist.change.try_conv_with(&world)?;
627 let command = Command {
629 command: "rust-analyzer.applySourceChange".to_string(),
630 arguments: Some(vec![to_value(edit).unwrap()]),
632 let action = CodeAction {
633 title: command.title.clone(),
634 kind: match assist.id {
635 AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
640 command: Some(command),
642 res.push(action.into());
648 pub fn handle_code_lens(
649 world: WorldSnapshot,
650 params: req::CodeLensParams,
651 ) -> Result<Option<Vec<CodeLens>>> {
652 let file_id = params.text_document.try_conv_with(&world)?;
653 let line_index = world.analysis().file_line_index(file_id)?;
655 let mut lenses: Vec<CodeLens> = Default::default();
658 for runnable in world.analysis().runnables(file_id)? {
659 let title = match &runnable.kind {
660 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️Run Test",
661 RunnableKind::Bench { .. } => "Run Bench",
662 RunnableKind::Bin => "Run",
665 let r = to_lsp_runnable(&world, file_id, runnable)?;
666 let lens = CodeLens {
668 command: Some(Command {
670 command: "rust-analyzer.runSingle".into(),
671 arguments: Some(vec![to_value(r).unwrap()]),
683 .file_structure(file_id)?
685 .filter(|it| match it.kind {
686 SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
690 let range = it.node_range.conv_with(&line_index);
691 let pos = range.start;
693 req::TextDocumentPositionParams::new(params.text_document.clone(), pos);
697 data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
705 #[derive(Debug, Serialize, Deserialize)]
706 #[serde(rename_all = "camelCase")]
707 enum CodeLensResolveData {
708 Impls(req::TextDocumentPositionParams),
711 pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
712 let data = code_lens.data.unwrap();
713 let resolve = serde_json::from_value(data)?;
715 Some(CodeLensResolveData::Impls(lens_params)) => {
716 let locations: Vec<Location> =
717 match handle_goto_implementation(world, lens_params.clone())? {
718 Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
719 Some(req::GotoDefinitionResponse::Array(locs)) => locs,
720 Some(req::GotoDefinitionResponse::Link(links)) => links
722 .map(|link| Location::new(link.target_uri, link.target_selection_range))
727 let title = if locations.len() == 1 {
728 "1 implementation".into()
730 format!("{} implementations", locations.len())
733 // We cannot use the 'editor.action.showReferences' command directly
734 // because that command requires vscode types which we convert in the handler
735 // on the client side.
738 command: "rust-analyzer.showReferences".into(),
739 arguments: Some(vec![
740 to_value(&lens_params.text_document.uri).unwrap(),
741 to_value(code_lens.range.start).unwrap(),
742 to_value(locations).unwrap(),
745 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
747 None => Ok(CodeLens {
748 range: code_lens.range,
749 command: Some(Command { title: "Error".into(), ..Default::default() }),
755 pub fn handle_document_highlight(
756 world: WorldSnapshot,
757 params: req::TextDocumentPositionParams,
758 ) -> Result<Option<Vec<DocumentHighlight>>> {
759 let file_id = params.text_document.try_conv_with(&world)?;
760 let line_index = world.analysis().file_line_index(file_id)?;
762 let refs = match world.analysis().find_all_refs(params.try_conv_with(&world)?)? {
763 None => return Ok(None),
769 .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
774 pub fn publish_diagnostics(
775 world: &WorldSnapshot,
777 ) -> Result<req::PublishDiagnosticsParams> {
778 let uri = world.file_id_to_uri(file_id)?;
779 let line_index = world.analysis().file_line_index(file_id)?;
780 let diagnostics = world
782 .diagnostics(file_id)?
784 .map(|d| Diagnostic {
785 range: d.range.conv_with(&line_index),
786 severity: Some(d.severity.conv()),
788 source: Some("rust-analyzer".to_string()),
790 related_information: None,
793 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
796 pub fn publish_decorations(
797 world: &WorldSnapshot,
799 ) -> Result<req::PublishDecorationsParams> {
800 let uri = world.file_id_to_uri(file_id)?;
801 Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
805 world: &WorldSnapshot,
808 ) -> Result<req::Runnable> {
809 let args = runnable_args(world, file_id, &runnable.kind)?;
810 let line_index = world.analysis().file_line_index(file_id)?;
811 let label = match &runnable.kind {
812 RunnableKind::Test { name } => format!("test {}", name),
813 RunnableKind::TestMod { path } => format!("test-mod {}", path),
814 RunnableKind::Bench { name } => format!("bench {}", name),
815 RunnableKind::Bin => "run binary".to_string(),
818 range: runnable.range.conv_with(&line_index),
820 bin: "cargo".to_string(),
823 let mut m = FxHashMap::default();
824 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
827 cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
830 fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
831 let line_index = world.analysis().file_line_index(file_id)?;
836 .map(|h| Decoration {
837 range: h.range.conv_with(&line_index),
839 binding_hash: h.binding_hash.map(|x| x.to_string()),
845 pub fn handle_inlay_hints(
846 world: WorldSnapshot,
847 params: InlayHintsParams,
848 ) -> Result<Vec<InlayHint>> {
849 let file_id = params.text_document.try_conv_with(&world)?;
850 let analysis = world.analysis();
851 let line_index = analysis.file_line_index(file_id)?;
853 .inlay_hints(file_id)?
855 .map(|api_type| InlayHint {
856 label: api_type.label.to_string(),
857 range: api_type.range.conv_with(&line_index),
858 kind: match api_type.kind {
859 ra_ide_api::InlayKind::TypeHint => InlayKind::TypeHint,