diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 2ca756cbe044..3dc72731cb9e 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use stdx::to_lower_snake_case; use syntax::{ ast::{self, AstNode, HasArgList, HasName, UnaryOp}, - match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T, + match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, TextRange, T, }; use crate::FileId; @@ -58,6 +58,7 @@ pub struct InlayHint { pub(crate) fn inlay_hints( db: &RootDatabase, file_id: FileId, + range_limit: Option, config: &InlayHintsConfig, ) -> Vec { let _p = profile::span("inlay_hints"); @@ -65,25 +66,50 @@ pub(crate) fn inlay_hints( let file = sema.parse(file_id); let file = file.syntax(); - let mut res = Vec::new(); - - for node in file.descendants() { - if let Some(expr) = ast::Expr::cast(node.clone()) { - get_chaining_hints(&mut res, &sema, config, &expr); - match expr { - ast::Expr::CallExpr(it) => { - get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); - } - ast::Expr::MethodCallExpr(it) => { - get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); + let mut hints = Vec::new(); + + if let Some(range_limit) = range_limit { + let range_limit = range_limit.range; + match file.covering_element(range_limit) { + NodeOrToken::Token(_) => return hints, + NodeOrToken::Node(n) => { + for node in n + .descendants() + .filter(|descendant| range_limit.contains_range(descendant.text_range())) + { + get_hints(&mut hints, &sema, config, node); } - _ => (), } - } else if let Some(it) = ast::IdentPat::cast(node.clone()) { - get_bind_pat_hints(&mut res, &sema, config, &it); } + } else { + for node in file.descendants() { + get_hints(&mut hints, &sema, config, node); + } + } + + hints +} + +fn get_hints( + hints: &mut Vec, + sema: &Semantics, + config: &InlayHintsConfig, + node: SyntaxNode, +) { + if let Some(expr) = ast::Expr::cast(node.clone()) { + get_chaining_hints(hints, sema, config, &expr); + match expr { + ast::Expr::CallExpr(it) => { + get_param_name_hints(hints, sema, config, ast::Expr::from(it)); + } + ast::Expr::MethodCallExpr(it) => { + get_param_name_hints(hints, sema, config, ast::Expr::from(it)); + } + _ => (), + } + } else if let Some(it) = ast::IdentPat::cast(node) { + get_bind_pat_hints(hints, sema, config, &it); } - res } fn get_chaining_hints( @@ -541,6 +567,8 @@ fn get_callable( #[cfg(test)] mod tests { use expect_test::{expect, Expect}; + use ide_db::base_db::FileRange; + use syntax::{TextRange, TextSize}; use test_utils::extract_annotations; use crate::{fixture, inlay_hints::InlayHintsConfig}; @@ -604,7 +632,7 @@ mod tests { fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { let (analysis, file_id) = fixture::file(ra_fixture); let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); - let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap(); + 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())).collect::>(); assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); @@ -613,7 +641,7 @@ mod tests { #[track_caller] fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { let (analysis, file_id) = fixture::file(ra_fixture); - let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap(); + let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); expect.assert_debug_eq(&inlay_hints) } @@ -1045,6 +1073,55 @@ fn main() { ) } + #[test] + fn check_hint_range_limit() { + let fixture = r#" + //- minicore: fn, sized + fn foo() -> impl Fn() { loop {} } + fn foo1() -> impl Fn(f64) { loop {} } + fn foo2() -> impl Fn(f64, f64) { loop {} } + fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} } + fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} } + fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} } + fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} } + fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} } + + fn main() { + let foo = foo(); + let foo = foo1(); + let foo = foo2(); + let foo = foo3(); + // ^^^ impl Fn(f64, f64) -> u32 + let foo = foo4(); + // ^^^ &dyn Fn(f64, f64) -> u32 + let foo = foo5(); + let foo = foo6(); + let foo = foo7(); + } + "#; + let (analysis, file_id) = fixture::file(fixture); + let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); + let inlay_hints = analysis + .inlay_hints( + &InlayHintsConfig { + parameter_hints: false, + type_hints: true, + chaining_hints: false, + hide_named_constructor_hints: false, + max_length: None, + }, + file_id, + Some(FileRange { + file_id, + range: TextRange::new(TextSize::from(500), TextSize::from(600)), + }), + ) + .unwrap(); + let actual = + inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::>(); + assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); + } + #[test] fn fn_hints_ptr_rpit_fn_parentheses() { check_types( diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 4028b0bc7257..1acaaaccf0da 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -358,8 +358,9 @@ impl Analysis { &self, config: &InlayHintsConfig, file_id: FileId, + range: Option, ) -> Cancellable> { - self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config)) + self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config)) } /// Returns the set of folding ranges. diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index d5bfbd18941c..64f2bc442332 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -112,6 +112,7 @@ impl StaticIndex<'_> { max_length: Some(25), }, file_id, + None, ) .unwrap(); // hovers diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 457399a61898..dc6cf61f79a8 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -115,6 +115,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { experimental: Some(json!({ "externalDocs": true, "hoverRange": true, + "inlayHints": true, "joinLines": true, "matchingBrace": true, "moveItem": true, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index b45fbe698c99..249e861f9b72 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1318,11 +1318,22 @@ pub(crate) fn handle_inlay_hints( params: InlayHintsParams, ) -> Result> { let _p = profile::span("handle_inlay_hints"); - let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let document_uri = ¶ms.text_document.uri; + let file_id = from_proto::file_id(&snap, document_uri)?; let line_index = snap.file_line_index(file_id)?; + let range = params + .range + .map(|range| { + from_proto::file_range( + &snap, + TextDocumentIdentifier::new(document_uri.to_owned()), + range, + ) + }) + .transpose()?; Ok(snap .analysis - .inlay_hints(&snap.config.inlay_hints(), file_id)? + .inlay_hints(&snap.config.inlay_hints(), file_id, range)? .into_iter() .map(|it| to_proto::inlay_hint(&line_index, it)) .collect()) diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index ea98d0993529..846f15130442 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -233,27 +233,34 @@ pub enum InlayHints {} impl Request for InlayHints { type Params = InlayHintsParams; type Result = Vec; - const METHOD: &'static str = "rust-analyzer/inlayHints"; + const METHOD: &'static str = "experimental/inlayHints"; } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct InlayHintsParams { pub text_document: TextDocumentIdentifier, + pub range: Option, } -#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] -pub enum InlayKind { - TypeHint, - ParameterHint, - ChainingHint, +#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct InlayHintKind(u8); + +impl InlayHintKind { + pub const TYPE: InlayHintKind = InlayHintKind(1); + pub const PARAMETER: InlayHintKind = InlayHintKind(2); } #[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct InlayHint { - pub range: Range, - pub kind: InlayKind, pub label: String, + pub position: Position, + pub kind: Option, + pub tooltip: Option, + pub padding_left: Option, + pub padding_right: Option, } pub enum Ssr {} diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index f0de166d8b59..f60f4e10319a 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -413,12 +413,18 @@ pub(crate) fn signature_help( pub(crate) fn inlay_hint(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint { lsp_ext::InlayHint { label: inlay_hint.label.to_string(), - range: range(line_index, inlay_hint.range), + position: match inlay_hint.kind { + InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()), + _ => position(line_index, inlay_hint.range.end()), + }, kind: match inlay_hint.kind { - InlayKind::ParameterHint => lsp_ext::InlayKind::ParameterHint, - InlayKind::TypeHint => lsp_ext::InlayKind::TypeHint, - InlayKind::ChainingHint => lsp_ext::InlayKind::ChainingHint, + InlayKind::ParameterHint => Some(lsp_ext::InlayHintKind::PARAMETER), + InlayKind::TypeHint => Some(lsp_ext::InlayHintKind::TYPE), + InlayKind::ChainingHint => None, }, + tooltip: None, + padding_left: Some(true), + padding_right: Some(true), } } diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 85df1188a8a7..9f1c7fe0a3a6 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@