Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints.rs')
-rw-r--r--crates/ide/src/inlay_hints.rs142
1 files changed, 117 insertions, 25 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 152f31b3a5..9aef78143d 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1,15 +1,19 @@
-use std::fmt;
+use std::{
+ fmt::{self, Write},
+ mem::take,
+};
use either::Either;
-use hir::{known, HasVisibility, HirDisplay, Semantics};
+use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics};
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
use itertools::Itertools;
+use stdx::never;
use syntax::{
ast::{self, AstNode},
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
};
-use crate::FileId;
+use crate::{navigation_target::TryToNav, FileId};
mod closing_brace;
mod implicit_static;
@@ -23,6 +27,7 @@ mod bind_pat;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayHintsConfig {
+ pub location_links: bool,
pub render_colons: bool,
pub type_hints: bool,
pub parameter_hints: bool,
@@ -89,6 +94,7 @@ pub enum InlayTooltip {
HoverOffset(FileId, TextSize),
}
+#[derive(Default)]
pub struct InlayHintLabel {
pub parts: Vec<InlayHintLabelPart>,
}
@@ -172,6 +178,104 @@ impl fmt::Debug for InlayHintLabelPart {
}
}
+#[derive(Debug)]
+struct InlayHintLabelBuilder<'a> {
+ db: &'a RootDatabase,
+ result: InlayHintLabel,
+ last_part: String,
+ location_link_enabled: bool,
+ location: Option<FileRange>,
+}
+
+impl fmt::Write for InlayHintLabelBuilder<'_> {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ self.last_part.write_str(s)
+ }
+}
+
+impl HirWrite for InlayHintLabelBuilder<'_> {
+ fn start_location_link(&mut self, def: ModuleDefId) {
+ if !self.location_link_enabled {
+ return;
+ }
+ if self.location.is_some() {
+ never!("location link is already started");
+ }
+ self.make_new_part();
+ let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
+ let location =
+ FileRange { file_id: location.file_id, range: location.focus_or_full_range() };
+ self.location = Some(location);
+ }
+
+ fn end_location_link(&mut self) {
+ if !self.location_link_enabled {
+ return;
+ }
+ self.make_new_part();
+ }
+}
+
+impl InlayHintLabelBuilder<'_> {
+ fn make_new_part(&mut self) {
+ self.result.parts.push(InlayHintLabelPart {
+ text: take(&mut self.last_part),
+ linked_location: self.location.take(),
+ });
+ }
+
+ fn finish(mut self) -> InlayHintLabel {
+ self.make_new_part();
+ self.result
+ }
+}
+
+fn label_of_ty(
+ sema: &Semantics<'_, RootDatabase>,
+ desc_pat: &impl AstNode,
+ config: &InlayHintsConfig,
+ ty: hir::Type,
+) -> Option<InlayHintLabel> {
+ fn rec(
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ mut max_length: Option<usize>,
+ ty: hir::Type,
+ label_builder: &mut InlayHintLabelBuilder<'_>,
+ ) {
+ let iter_item_type = hint_iterator(sema, &famous_defs, &ty);
+ match iter_item_type {
+ Some(ty) => {
+ const LABEL_START: &str = "impl Iterator<Item = ";
+ const LABEL_END: &str = ">";
+
+ max_length =
+ max_length.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len()));
+
+ label_builder.write_str(LABEL_START).unwrap();
+ rec(sema, famous_defs, max_length, ty, label_builder);
+ label_builder.write_str(LABEL_END).unwrap();
+ }
+ None => {
+ let _ = ty.display_truncated(sema.db, max_length).write_to(label_builder);
+ }
+ };
+ }
+
+ let krate = sema.scope(desc_pat.syntax())?.krate();
+ let famous_defs = FamousDefs(sema, krate);
+ let mut label_builder = InlayHintLabelBuilder {
+ db: sema.db,
+ last_part: String::new(),
+ location: None,
+ location_link_enabled: config.location_links,
+ result: InlayHintLabel::default(),
+ };
+ rec(sema, &famous_defs, config.max_length, ty, &mut label_builder);
+ let r = label_builder.finish();
+ Some(r)
+}
+
// Feature: Inlay Hints
//
// rust-analyzer shows additional information inline with the source code.
@@ -224,7 +328,7 @@ pub(crate) fn inlay_hints(
fn hints(
hints: &mut Vec<InlayHint>,
- famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: FileId,
node: SyntaxNode,
@@ -233,14 +337,14 @@ fn hints(
match_ast! {
match node {
ast::Expr(expr) => {
- chaining::hints(hints, sema, &famous_defs, config, file_id, &expr);
+ chaining::hints(hints, sema, config, file_id, &expr);
adjustment::hints(hints, sema, config, &expr);
match expr {
ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => {
param_name::hints(hints, sema, config, ast::Expr::from(it))
}
- ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, sema, &famous_defs, config, file_id, it),
+ ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, sema, config, file_id, it),
// We could show reborrows for all expressions, but usually that is just noise to the user
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
// ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
@@ -270,13 +374,12 @@ fn hints(
};
}
-/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
+/// Checks if the type is an Iterator from std::iter and returns its item type.
fn hint_iterator(
sema: &Semantics<'_, RootDatabase>,
famous_defs: &FamousDefs<'_, '_>,
- config: &InlayHintsConfig,
ty: &hir::Type,
-) -> Option<String> {
+) -> Option<hir::Type> {
let db = sema.db;
let strukt = ty.strip_references().as_adt()?;
let krate = strukt.module(db).krate();
@@ -299,21 +402,7 @@ fn hint_iterator(
_ => None,
})?;
if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
- const LABEL_START: &str = "impl Iterator<Item = ";
- const LABEL_END: &str = ">";
-
- let ty_display = hint_iterator(sema, famous_defs, config, &ty)
- .map(|assoc_type_impl| assoc_type_impl.to_string())
- .unwrap_or_else(|| {
- ty.display_truncated(
- db,
- config
- .max_length
- .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
- )
- .to_string()
- });
- return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
+ return Some(ty);
}
}
@@ -336,6 +425,7 @@ mod tests {
use super::ClosureReturnTypeHints;
pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ location_links: false,
render_colons: false,
type_hints: false,
parameter_hints: false,
@@ -350,6 +440,8 @@ mod tests {
max_length: None,
closing_brace_hints_min_lines: None,
};
+ pub(super) const DISABLED_CONFIG_WITH_LINKS: InlayHintsConfig =
+ InlayHintsConfig { location_links: true, ..DISABLED_CONFIG };
pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
type_hints: true,
parameter_hints: true,
@@ -357,7 +449,7 @@ mod tests {
closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
- ..DISABLED_CONFIG
+ ..DISABLED_CONFIG_WITH_LINKS
};
#[track_caller]