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.rs146
1 files changed, 115 insertions, 31 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index ac477339ec..2925916741 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -5,7 +5,8 @@ use std::{
use either::Either;
use hir::{
- known, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef, ModuleDefId, Semantics,
+ known, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
+ ModuleDefId, Semantics,
};
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
use itertools::Itertools;
@@ -13,21 +14,23 @@ use smallvec::{smallvec, SmallVec};
use stdx::never;
use syntax::{
ast::{self, AstNode},
- match_ast, NodeOrToken, SyntaxNode, TextRange,
+ match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
};
+use text_edit::TextEdit;
use crate::{navigation_target::TryToNav, FileId};
-mod closing_brace;
-mod implicit_static;
-mod fn_lifetime_fn;
-mod closure_ret;
mod adjustment;
-mod chaining;
-mod param_name;
-mod binding_mode;
mod bind_pat;
+mod binding_mode;
+mod chaining;
+mod closing_brace;
+mod closure_ret;
+mod closure_captures;
mod discriminant;
+mod fn_lifetime_fn;
+mod implicit_static;
+mod param_name;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayHintsConfig {
@@ -40,11 +43,13 @@ pub struct InlayHintsConfig {
pub adjustment_hints_mode: AdjustmentHintsMode,
pub adjustment_hints_hide_outside_unsafe: bool,
pub closure_return_type_hints: ClosureReturnTypeHints,
+ pub closure_capture_hints: bool,
pub binding_mode_hints: bool,
pub lifetime_elision_hints: LifetimeElisionHints,
pub param_names_for_lifetime_elision_hints: bool,
pub hide_named_constructor_hints: bool,
pub hide_closure_initialization_hints: bool,
+ pub closure_style: ClosureStyle,
pub max_length: Option<usize>,
pub closing_brace_hints_min_lines: Option<usize>,
}
@@ -87,38 +92,61 @@ pub enum AdjustmentHintsMode {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
+ Adjustment,
BindingMode,
Chaining,
ClosingBrace,
- ClosureReturnType,
+ ClosureCapture,
+ Discriminant,
GenericParamList,
- Adjustment,
- AdjustmentPostfix,
Lifetime,
Parameter,
Type,
- Discriminant,
- OpeningParenthesis,
- ClosingParenthesis,
+}
+
+#[derive(Debug)]
+pub enum InlayHintPosition {
+ Before,
+ After,
}
#[derive(Debug)]
pub struct InlayHint {
/// The text range this inlay hint applies to.
pub range: TextRange,
- /// The kind of this inlay hint. This is used to determine side and padding of the hint for
- /// rendering purposes.
+ pub position: InlayHintPosition,
+ pub pad_left: bool,
+ pub pad_right: bool,
+ /// The kind of this inlay hint.
pub kind: InlayKind,
/// The actual label to show in the inlay hint.
pub label: InlayHintLabel,
+ /// Text edit to apply when "accepting" this inlay hint.
+ pub text_edit: Option<TextEdit>,
}
impl InlayHint {
- fn closing_paren(range: TextRange) -> InlayHint {
- InlayHint { range, kind: InlayKind::ClosingParenthesis, label: InlayHintLabel::from(")") }
+ fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
+ InlayHint {
+ range,
+ kind,
+ label: InlayHintLabel::from(")"),
+ text_edit: None,
+ position: InlayHintPosition::After,
+ pad_left: false,
+ pad_right: false,
+ }
}
- fn opening_paren(range: TextRange) -> InlayHint {
- InlayHint { range, kind: InlayKind::OpeningParenthesis, label: InlayHintLabel::from("(") }
+ fn opening_paren_before(kind: InlayKind, range: TextRange) -> InlayHint {
+ InlayHint {
+ range,
+ kind,
+ label: InlayHintLabel::from("("),
+ text_edit: None,
+ position: InlayHintPosition::Before,
+ pad_left: false,
+ pad_right: false,
+ }
}
}
@@ -283,14 +311,15 @@ impl InlayHintLabelBuilder<'_> {
fn label_of_ty(
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- ty: hir::Type,
+ ty: &hir::Type,
) -> Option<InlayHintLabel> {
fn rec(
sema: &Semantics<'_, RootDatabase>,
famous_defs: &FamousDefs<'_, '_>,
mut max_length: Option<usize>,
- ty: hir::Type,
+ ty: &hir::Type,
label_builder: &mut InlayHintLabelBuilder<'_>,
+ config: &InlayHintsConfig,
) -> Result<(), HirDisplayError> {
let iter_item_type = hint_iterator(sema, famous_defs, &ty);
match iter_item_type {
@@ -321,11 +350,14 @@ fn label_of_ty(
label_builder.write_str(LABEL_ITEM)?;
label_builder.end_location_link();
label_builder.write_str(LABEL_MIDDLE2)?;
- rec(sema, famous_defs, max_length, ty, label_builder)?;
+ rec(sema, famous_defs, max_length, &ty, label_builder, config)?;
label_builder.write_str(LABEL_END)?;
Ok(())
}
- None => ty.display_truncated(sema.db, max_length).write_to(label_builder),
+ None => ty
+ .display_truncated(sema.db, max_length)
+ .with_closure_style(config.closure_style)
+ .write_to(label_builder),
}
}
@@ -335,11 +367,28 @@ fn label_of_ty(
location: None,
result: InlayHintLabel::default(),
};
- let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder);
+ let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config);
let r = label_builder.finish();
Some(r)
}
+fn ty_to_text_edit(
+ sema: &Semantics<'_, RootDatabase>,
+ node_for_hint: &SyntaxNode,
+ ty: &hir::Type,
+ offset_to_insert: TextSize,
+ prefix: String,
+) -> Option<TextEdit> {
+ let scope = sema.scope(node_for_hint)?;
+ // FIXME: Limit the length and bail out on excess somehow?
+ let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
+
+ let mut builder = TextEdit::builder();
+ builder.insert(offset_to_insert, prefix);
+ builder.insert(offset_to_insert, rendered);
+ Some(builder.finish())
+}
+
// Feature: Inlay Hints
//
// rust-analyzer shows additional information inline with the source code.
@@ -408,10 +457,10 @@ fn hints(
ast::Expr::MethodCallExpr(it) => {
param_name::hints(hints, sema, config, ast::Expr::from(it))
}
- ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, famous_defs, 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),
+ ast::Expr::ClosureExpr(it) => {
+ closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
+ closure_ret::hints(hints, famous_defs, config, file_id, it)
+ },
_ => None,
}
},
@@ -481,6 +530,7 @@ fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
#[cfg(test)]
mod tests {
use expect_test::Expect;
+ use hir::ClosureStyle;
use itertools::Itertools;
use test_utils::extract_annotations;
@@ -498,12 +548,14 @@ mod tests {
chaining_hints: false,
lifetime_elision_hints: LifetimeElisionHints::Never,
closure_return_type_hints: ClosureReturnTypeHints::Never,
+ closure_capture_hints: false,
adjustment_hints: AdjustmentHints::Never,
adjustment_hints_mode: AdjustmentHintsMode::Prefix,
adjustment_hints_hide_outside_unsafe: false,
binding_mode_hints: false,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
+ closure_style: ClosureStyle::ImplFn,
param_names_for_lifetime_elision_hints: false,
max_length: None,
closing_brace_hints_min_lines: None,
@@ -530,7 +582,8 @@ mod tests {
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
let actual = inlay_hints
.into_iter()
- .map(|it| (it.range, it.label.to_string()))
+ // FIXME: We trim the start because some inlay produces leading whitespace which is not properly supported by our annotation extraction
+ .map(|it| (it.range, it.label.to_string().trim_start().to_owned()))
.sorted_by_key(|(range, _)| range.start())
.collect::<Vec<_>>();
expected.sort_by_key(|(range, _)| range.start());
@@ -545,6 +598,37 @@ mod tests {
expect.assert_debug_eq(&inlay_hints)
}
+ /// Computes inlay hints for the fixture, applies all the provided text edits and then runs
+ /// expect test.
+ #[track_caller]
+ pub(super) fn check_edit(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+
+ let edits = inlay_hints
+ .into_iter()
+ .filter_map(|hint| hint.text_edit)
+ .reduce(|mut acc, next| {
+ acc.union(next).expect("merging text edits failed");
+ acc
+ })
+ .expect("no edit returned");
+
+ let mut actual = analysis.file_text(file_id).unwrap().to_string();
+ edits.apply(&mut actual);
+ expect.assert_eq(&actual);
+ }
+
+ #[track_caller]
+ pub(super) fn check_no_edit(config: InlayHintsConfig, ra_fixture: &str) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+
+ let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect();
+
+ assert!(edits.is_empty(), "unexpected edits: {edits:?}");
+ }
+
#[test]
fn hints_disabled() {
check_with_config(