mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
[ty] Refactor inlay hints structure to use separate parts (#20052)
## Summary Our internal inlay hints structure (`ty_ide::InlayHint`) now more closely resembles `lsp_types::InlayHint`. This mainly allows us to convert to `lsp_types::InlayHint` with less hassle, but it also allows us to manage the different parts of the inlay hint better, which in the future will allow us to implement features like goto on the type part of the type inlay hint. It also really isn't important to store a specific `Type` instance in the `InlayHintContent`. So we remove this and use `InlayHintLabel` instead which just shows the representation of the type (along with other information). We see a similar structure used in rust-analyzer too.
This commit is contained in:
parent
ef4897f9f3
commit
8d6dc7d3a3
5 changed files with 147 additions and 61 deletions
|
@ -1,62 +1,128 @@
|
||||||
|
use std::{fmt, vec};
|
||||||
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||||
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
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::types::{Type, inlay_hint_function_argument_details};
|
||||||
use ty_python_semantic::{HasType, SemanticModel};
|
use ty_python_semantic::{HasType, SemanticModel};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InlayHint<'db> {
|
pub struct InlayHint {
|
||||||
pub position: TextSize,
|
pub position: TextSize,
|
||||||
pub content: InlayHintContent<'db>,
|
pub kind: InlayHintKind,
|
||||||
|
pub label: InlayHintLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> InlayHint<'db> {
|
impl InlayHint {
|
||||||
pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> {
|
fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self {
|
||||||
self.content.display(db)
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum InlayHintContent<'db> {
|
pub enum InlayHintKind {
|
||||||
Type(Type<'db>),
|
Type,
|
||||||
CallArgumentName(String),
|
CallArgumentName,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> InlayHintContent<'db> {
|
#[derive(Debug, Clone)]
|
||||||
pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> {
|
pub struct InlayHintLabel {
|
||||||
DisplayInlayHint { db, hint: self }
|
parts: Vec<InlayHintLabelPart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlayHintLabel {
|
||||||
|
pub fn parts(&self) -> &[InlayHintLabelPart] {
|
||||||
|
&self.parts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DisplayInlayHint<'a, 'db> {
|
pub struct InlayHintDisplay<'a> {
|
||||||
db: &'db dyn Db,
|
inlay_hint: &'a InlayHint,
|
||||||
hint: &'a InlayHintContent<'db>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DisplayInlayHint<'_, '_> {
|
impl fmt::Display for InlayHintDisplay<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.hint {
|
for part in &self.inlay_hint.label.parts {
|
||||||
InlayHintContent::Type(ty) => {
|
write!(f, "{}", part.text)?;
|
||||||
write!(f, ": {}", ty.display(self.db))
|
}
|
||||||
}
|
Ok(())
|
||||||
InlayHintContent::CallArgumentName(name) => {
|
}
|
||||||
write!(f, "{name}=")
|
}
|
||||||
}
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct InlayHintLabelPart {
|
||||||
|
text: String,
|
||||||
|
|
||||||
|
target: Option<crate::NavigationTarget>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlayHintLabelPart {
|
||||||
|
pub fn new(text: impl Into<String>) -> 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<String> for InlayHintLabelPart {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self {
|
||||||
|
text: s,
|
||||||
|
target: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inlay_hints<'db>(
|
impl From<&str> for InlayHintLabelPart {
|
||||||
db: &'db dyn Db,
|
fn from(s: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
text: s.to_string(),
|
||||||
|
target: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inlay_hints(
|
||||||
|
db: &dyn Db,
|
||||||
file: File,
|
file: File,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
settings: &InlayHintSettings,
|
settings: &InlayHintSettings,
|
||||||
) -> Vec<InlayHint<'db>> {
|
) -> Vec<InlayHint> {
|
||||||
let mut visitor = InlayHintVisitor::new(db, file, range, settings);
|
let mut visitor = InlayHintVisitor::new(db, file, range, settings);
|
||||||
|
|
||||||
let ast = parsed_module(db, file).load(db);
|
let ast = parsed_module(db, file).load(db);
|
||||||
|
@ -106,7 +172,7 @@ impl Default for InlayHintSettings {
|
||||||
struct InlayHintVisitor<'a, 'db> {
|
struct InlayHintVisitor<'a, 'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
model: SemanticModel<'db>,
|
model: SemanticModel<'db>,
|
||||||
hints: Vec<InlayHint<'db>>,
|
hints: Vec<InlayHint>,
|
||||||
in_assignment: bool,
|
in_assignment: bool,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
settings: &'a InlayHintSettings,
|
settings: &'a InlayHintSettings,
|
||||||
|
@ -128,13 +194,11 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||||
if !self.settings.variable_types {
|
if !self.settings.variable_types {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.hints.push(InlayHint {
|
self.hints
|
||||||
position,
|
.push(InlayHint::variable_type(position, ty, self.db));
|
||||||
content: InlayHintContent::Type(ty),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if !self.settings.call_argument_names {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -143,10 +207,8 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hints.push(InlayHint {
|
self.hints
|
||||||
position,
|
.push(InlayHint::call_argument_name(position, name));
|
||||||
content: InlayHintContent::CallArgumentName(name),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +274,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||||
|
|
||||||
for (index, arg_or_keyword) in call.arguments.arguments_source_order().enumerate() {
|
for (index, arg_or_keyword) in call.arguments.arguments_source_order().enumerate() {
|
||||||
if let Some(name) = argument_names.get(&index) {
|
if let Some(name) = argument_names.get(&index) {
|
||||||
self.add_call_argument_name(
|
self.add_call_argument_name(arg_or_keyword.range().start(), name);
|
||||||
arg_or_keyword.range().start(),
|
|
||||||
name.to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
self.visit_expr(arg_or_keyword.value());
|
self.visit_expr(arg_or_keyword.value());
|
||||||
}
|
}
|
||||||
|
@ -322,7 +381,7 @@ mod tests {
|
||||||
|
|
||||||
for hint in hints {
|
for hint in hints {
|
||||||
let end_position = (hint.position.to_u32() as usize) + offset;
|
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);
|
buf.insert_str(end_position, &hint_str);
|
||||||
offset += hint_str.len();
|
offset += hint_str.len();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub use document_symbols::document_symbols;
|
||||||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||||
pub use goto_references::goto_references;
|
pub use goto_references::goto_references;
|
||||||
pub use hover::hover;
|
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 markup::MarkupKind;
|
||||||
pub use references::ReferencesMode;
|
pub use references::ReferencesMode;
|
||||||
pub use rename::{can_rename, rename};
|
pub use rename::{can_rename, rename};
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::session::client::Client;
|
||||||
use lsp_types::request::InlayHintRequest;
|
use lsp_types::request::InlayHintRequest;
|
||||||
use lsp_types::{InlayHintParams, Url};
|
use lsp_types::{InlayHintParams, Url};
|
||||||
use ruff_db::source::{line_index, source_text};
|
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;
|
use ty_project::ProjectDatabase;
|
||||||
|
|
||||||
pub(crate) struct InlayHintRequestHandler;
|
pub(crate) struct InlayHintRequestHandler;
|
||||||
|
@ -55,8 +55,8 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
||||||
position: hint
|
position: hint
|
||||||
.position
|
.position
|
||||||
.to_position(&source, &index, snapshot.encoding()),
|
.to_position(&source, &index, snapshot.encoding()),
|
||||||
label: lsp_types::InlayHintLabel::String(hint.display(db).to_string()),
|
label: inlay_hint_label(&hint.label),
|
||||||
kind: Some(inlay_hint_kind(&hint.content)),
|
kind: Some(inlay_hint_kind(&hint.kind)),
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
padding_left: None,
|
padding_left: None,
|
||||||
padding_right: None,
|
padding_right: None,
|
||||||
|
@ -71,9 +71,22 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
||||||
|
|
||||||
impl RetriableRequestHandler for InlayHintRequestHandler {}
|
impl RetriableRequestHandler for InlayHintRequestHandler {}
|
||||||
|
|
||||||
fn inlay_hint_kind(inlay_hint_content: &InlayHintContent) -> lsp_types::InlayHintKind {
|
fn inlay_hint_kind(inlay_hint_kind: &InlayHintKind) -> lsp_types::InlayHintKind {
|
||||||
match inlay_hint_content {
|
match inlay_hint_kind {
|
||||||
InlayHintContent::Type(_) => lsp_types::InlayHintKind::TYPE,
|
InlayHintKind::Type => lsp_types::InlayHintKind::TYPE,
|
||||||
InlayHintContent::CallArgumentName(_) => lsp_types::InlayHintKind::PARAMETER,
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,14 @@ foo(1)
|
||||||
"line": 0,
|
"line": 0,
|
||||||
"character": 1
|
"character": 1
|
||||||
},
|
},
|
||||||
"label": ": Literal[1]",
|
"label": [
|
||||||
|
{
|
||||||
|
"value": ": "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Literal[1]"
|
||||||
|
}
|
||||||
|
],
|
||||||
"kind": 1
|
"kind": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -50,7 +57,14 @@ foo(1)
|
||||||
"line": 5,
|
"line": 5,
|
||||||
"character": 4
|
"character": 4
|
||||||
},
|
},
|
||||||
"label": "a=",
|
"label": [
|
||||||
|
{
|
||||||
|
"value": "a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "="
|
||||||
|
}
|
||||||
|
],
|
||||||
"kind": 2
|
"kind": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -449,14 +449,14 @@ impl Workspace {
|
||||||
Ok(result
|
Ok(result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|hint| InlayHint {
|
.map(|hint| InlayHint {
|
||||||
markdown: hint.display(&self.db).to_string(),
|
markdown: hint.display().to_string(),
|
||||||
position: Position::from_text_size(
|
position: Position::from_text_size(
|
||||||
hint.position,
|
hint.position,
|
||||||
&index,
|
&index,
|
||||||
&source,
|
&source,
|
||||||
self.position_encoding,
|
self.position_encoding,
|
||||||
),
|
),
|
||||||
kind: hint.content.into(),
|
kind: hint.kind.into(),
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
@ -988,11 +988,11 @@ pub enum InlayHintKind {
|
||||||
Parameter,
|
Parameter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ty_ide::InlayHintContent<'_>> for InlayHintKind {
|
impl From<ty_ide::InlayHintKind> for InlayHintKind {
|
||||||
fn from(kind: ty_ide::InlayHintContent) -> Self {
|
fn from(kind: ty_ide::InlayHintKind) -> Self {
|
||||||
match kind {
|
match kind {
|
||||||
ty_ide::InlayHintContent::Type(_) => Self::Type,
|
ty_ide::InlayHintKind::Type => Self::Type,
|
||||||
ty_ide::InlayHintContent::CallArgumentName(_) => Self::Parameter,
|
ty_ide::InlayHintKind::CallArgumentName => Self::Parameter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue