diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index 1004f5b4d5..2ab77f2f1f 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -1,62 +1,128 @@ +use std::{fmt, vec}; + use crate::Db; use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal}; use ruff_python_ast::{AnyNodeRef, Expr, Stmt}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use std::fmt; -use std::fmt::Formatter; use ty_python_semantic::types::{Type, inlay_hint_function_argument_details}; use ty_python_semantic::{HasType, SemanticModel}; -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct InlayHint<'db> { +#[derive(Debug, Clone)] +pub struct InlayHint { pub position: TextSize, - pub content: InlayHintContent<'db>, + pub kind: InlayHintKind, + pub label: InlayHintLabel, } -impl<'db> InlayHint<'db> { - pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> { - self.content.display(db) +impl InlayHint { + fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self { + let label_parts = vec![ + ": ".into(), + InlayHintLabelPart::new(ty.display(db).to_string()), + ]; + + Self { + position, + kind: InlayHintKind::Type, + label: InlayHintLabel { parts: label_parts }, + } + } + + fn call_argument_name(position: TextSize, name: &str) -> Self { + let label_parts = vec![InlayHintLabelPart::new(name), "=".into()]; + + Self { + position, + kind: InlayHintKind::CallArgumentName, + label: InlayHintLabel { parts: label_parts }, + } + } + + pub fn display(&self) -> InlayHintDisplay<'_> { + InlayHintDisplay { inlay_hint: self } } } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum InlayHintContent<'db> { - Type(Type<'db>), - CallArgumentName(String), +#[derive(Debug, Clone)] +pub enum InlayHintKind { + Type, + CallArgumentName, } -impl<'db> InlayHintContent<'db> { - pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> { - DisplayInlayHint { db, hint: self } +#[derive(Debug, Clone)] +pub struct InlayHintLabel { + parts: Vec, +} + +impl InlayHintLabel { + pub fn parts(&self) -> &[InlayHintLabelPart] { + &self.parts } } -pub struct DisplayInlayHint<'a, 'db> { - db: &'db dyn Db, - hint: &'a InlayHintContent<'db>, +pub struct InlayHintDisplay<'a> { + inlay_hint: &'a InlayHint, } -impl fmt::Display for DisplayInlayHint<'_, '_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.hint { - InlayHintContent::Type(ty) => { - write!(f, ": {}", ty.display(self.db)) - } - InlayHintContent::CallArgumentName(name) => { - write!(f, "{name}=") - } +impl fmt::Display for InlayHintDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + for part in &self.inlay_hint.label.parts { + write!(f, "{}", part.text)?; + } + Ok(()) + } +} + +#[derive(Default, Debug, Clone)] +pub struct InlayHintLabelPart { + text: String, + + target: Option, +} + +impl InlayHintLabelPart { + pub fn new(text: impl Into) -> Self { + Self { + text: text.into(), + target: None, + } + } + + pub fn text(&self) -> &str { + &self.text + } + + pub fn target(&self) -> Option<&crate::NavigationTarget> { + self.target.as_ref() + } +} + +impl From for InlayHintLabelPart { + fn from(s: String) -> Self { + Self { + text: s, + target: None, } } } -pub fn inlay_hints<'db>( - db: &'db dyn Db, +impl From<&str> for InlayHintLabelPart { + fn from(s: &str) -> Self { + Self { + text: s.to_string(), + target: None, + } + } +} + +pub fn inlay_hints( + db: &dyn Db, file: File, range: TextRange, settings: &InlayHintSettings, -) -> Vec> { +) -> Vec { let mut visitor = InlayHintVisitor::new(db, file, range, settings); let ast = parsed_module(db, file).load(db); @@ -106,7 +172,7 @@ impl Default for InlayHintSettings { struct InlayHintVisitor<'a, 'db> { db: &'db dyn Db, model: SemanticModel<'db>, - hints: Vec>, + hints: Vec, in_assignment: bool, range: TextRange, settings: &'a InlayHintSettings, @@ -128,13 +194,11 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> { if !self.settings.variable_types { return; } - self.hints.push(InlayHint { - position, - content: InlayHintContent::Type(ty), - }); + self.hints + .push(InlayHint::variable_type(position, ty, self.db)); } - fn add_call_argument_name(&mut self, position: TextSize, name: String) { + fn add_call_argument_name(&mut self, position: TextSize, name: &str) { if !self.settings.call_argument_names { return; } @@ -143,10 +207,8 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> { return; } - self.hints.push(InlayHint { - position, - content: InlayHintContent::CallArgumentName(name), - }); + self.hints + .push(InlayHint::call_argument_name(position, name)); } } @@ -212,10 +274,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> { for (index, arg_or_keyword) in call.arguments.arguments_source_order().enumerate() { if let Some(name) = argument_names.get(&index) { - self.add_call_argument_name( - arg_or_keyword.range().start(), - name.to_string(), - ); + self.add_call_argument_name(arg_or_keyword.range().start(), name); } self.visit_expr(arg_or_keyword.value()); } @@ -322,7 +381,7 @@ mod tests { for hint in hints { let end_position = (hint.position.to_u32() as usize) + offset; - let hint_str = format!("[{}]", hint.display(&self.db)); + let hint_str = format!("[{}]", hint.display()); buf.insert_str(end_position, &hint_str); offset += hint_str.len(); } diff --git a/crates/ty_ide/src/lib.rs b/crates/ty_ide/src/lib.rs index c2567ece0d..6baea75a8f 100644 --- a/crates/ty_ide/src/lib.rs +++ b/crates/ty_ide/src/lib.rs @@ -30,7 +30,7 @@ pub use document_symbols::document_symbols; pub use goto::{goto_declaration, goto_definition, goto_type_definition}; pub use goto_references::goto_references; pub use hover::hover; -pub use inlay_hints::{InlayHintContent, InlayHintSettings, inlay_hints}; +pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints}; pub use markup::MarkupKind; pub use references::ReferencesMode; pub use rename::{can_rename, rename}; diff --git a/crates/ty_server/src/server/api/requests/inlay_hints.rs b/crates/ty_server/src/server/api/requests/inlay_hints.rs index 52a82cd3a2..ec8464fc6b 100644 --- a/crates/ty_server/src/server/api/requests/inlay_hints.rs +++ b/crates/ty_server/src/server/api/requests/inlay_hints.rs @@ -9,7 +9,7 @@ use crate::session::client::Client; use lsp_types::request::InlayHintRequest; use lsp_types::{InlayHintParams, Url}; use ruff_db::source::{line_index, source_text}; -use ty_ide::{InlayHintContent, inlay_hints}; +use ty_ide::{InlayHintKind, InlayHintLabel, inlay_hints}; use ty_project::ProjectDatabase; pub(crate) struct InlayHintRequestHandler; @@ -55,8 +55,8 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { position: hint .position .to_position(&source, &index, snapshot.encoding()), - label: lsp_types::InlayHintLabel::String(hint.display(db).to_string()), - kind: Some(inlay_hint_kind(&hint.content)), + label: inlay_hint_label(&hint.label), + kind: Some(inlay_hint_kind(&hint.kind)), tooltip: None, padding_left: None, padding_right: None, @@ -71,9 +71,22 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { impl RetriableRequestHandler for InlayHintRequestHandler {} -fn inlay_hint_kind(inlay_hint_content: &InlayHintContent) -> lsp_types::InlayHintKind { - match inlay_hint_content { - InlayHintContent::Type(_) => lsp_types::InlayHintKind::TYPE, - InlayHintContent::CallArgumentName(_) => lsp_types::InlayHintKind::PARAMETER, +fn inlay_hint_kind(inlay_hint_kind: &InlayHintKind) -> lsp_types::InlayHintKind { + match inlay_hint_kind { + InlayHintKind::Type => lsp_types::InlayHintKind::TYPE, + InlayHintKind::CallArgumentName => lsp_types::InlayHintKind::PARAMETER, } } + +fn inlay_hint_label(inlay_hint_label: &InlayHintLabel) -> lsp_types::InlayHintLabel { + let mut label_parts = Vec::new(); + for part in inlay_hint_label.parts() { + label_parts.push(lsp_types::InlayHintLabelPart { + value: part.text().into(), + location: None, + tooltip: None, + command: None, + }); + } + lsp_types::InlayHintLabel::LabelParts(label_parts) +} diff --git a/crates/ty_server/tests/e2e/inlay_hints.rs b/crates/ty_server/tests/e2e/inlay_hints.rs index 324c4f1fc6..3dbbbee994 100644 --- a/crates/ty_server/tests/e2e/inlay_hints.rs +++ b/crates/ty_server/tests/e2e/inlay_hints.rs @@ -42,7 +42,14 @@ foo(1) "line": 0, "character": 1 }, - "label": ": Literal[1]", + "label": [ + { + "value": ": " + }, + { + "value": "Literal[1]" + } + ], "kind": 1 }, { @@ -50,7 +57,14 @@ foo(1) "line": 5, "character": 4 }, - "label": "a=", + "label": [ + { + "value": "a" + }, + { + "value": "=" + } + ], "kind": 2 } ] diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index 7b2c970ea3..8722afcbe7 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -449,14 +449,14 @@ impl Workspace { Ok(result .into_iter() .map(|hint| InlayHint { - markdown: hint.display(&self.db).to_string(), + markdown: hint.display().to_string(), position: Position::from_text_size( hint.position, &index, &source, self.position_encoding, ), - kind: hint.content.into(), + kind: hint.kind.into(), }) .collect()) } @@ -988,11 +988,11 @@ pub enum InlayHintKind { Parameter, } -impl From> for InlayHintKind { - fn from(kind: ty_ide::InlayHintContent) -> Self { +impl From for InlayHintKind { + fn from(kind: ty_ide::InlayHintKind) -> Self { match kind { - ty_ide::InlayHintContent::Type(_) => Self::Type, - ty_ide::InlayHintContent::CallArgumentName(_) => Self::Parameter, + ty_ide::InlayHintKind::Type => Self::Type, + ty_ide::InlayHintKind::CallArgumentName => Self::Parameter, } } }