[red-knot] Add inlay type hints (#17214)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Matthew Mckee 2025-04-10 10:21:40 +01:00 committed by GitHub
parent 9f6913c488
commit 10e44124e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 529 additions and 50 deletions

View file

@ -1,7 +1,7 @@
use std::any::Any;
use js_sys::{Error, JsString};
use red_knot_ide::{goto_type_definition, hover, MarkupKind};
use red_knot_ide::{goto_type_definition, hover, inlay_hints, MarkupKind};
use red_knot_project::metadata::options::Options;
use red_knot_project::metadata::value::ValueSource;
use red_knot_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind};
@ -20,7 +20,7 @@ use ruff_db::Upcast;
use ruff_notebook::Notebook;
use ruff_python_formatter::formatted_file;
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextSize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
@ -216,17 +216,7 @@ impl Workspace {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
let offset = index.offset(
OneIndexed::new(position.line).ok_or_else(|| {
Error::new("Invalid value `0` for `position.line`. The line index is 1-indexed.")
})?,
OneIndexed::new(position.column).ok_or_else(|| {
Error::new(
"Invalid value `0` for `position.column`. The column index is 1-indexed.",
)
})?,
&source,
);
let offset = position.to_text_size(&source, &index)?;
let Some(targets) = goto_type_definition(&self.db, file_id.file, offset) else {
return Ok(Vec::new());
@ -258,17 +248,7 @@ impl Workspace {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
let offset = index.offset(
OneIndexed::new(position.line).ok_or_else(|| {
Error::new("Invalid value `0` for `position.line`. The line index is 1-indexed.")
})?,
OneIndexed::new(position.column).ok_or_else(|| {
Error::new(
"Invalid value `0` for `position.column`. The column index is 1-indexed.",
)
})?,
&source,
);
let offset = position.to_text_size(&source, &index)?;
let Some(range_info) = hover(&self.db, file_id.file, offset) else {
return Ok(None);
@ -283,6 +263,26 @@ impl Workspace {
range: source_range,
}))
}
#[wasm_bindgen(js_name = "inlayHints")]
pub fn inlay_hints(&self, file_id: &FileHandle, range: Range) -> Result<Vec<InlayHint>, Error> {
let index = line_index(&self.db, file_id.file);
let source = source_text(&self.db, file_id.file);
let result = inlay_hints(
&self.db,
file_id.file,
range.to_text_range(&index, &source)?,
);
Ok(result
.into_iter()
.map(|hint| InlayHint {
markdown: hint.display(&self.db).to_string(),
position: Position::from_text_size(hint.position, &index, &source),
})
.collect())
}
}
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
@ -369,6 +369,14 @@ pub struct Range {
pub end: Position,
}
#[wasm_bindgen]
impl Range {
#[wasm_bindgen(constructor)]
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
}
impl Range {
fn from_file_range(db: &dyn Db, file_range: FileRange) -> Self {
let index = line_index(db.upcast(), file_range.file());
@ -382,9 +390,21 @@ impl Range {
line_index: &LineIndex,
source: &str,
) -> Self {
let start = line_index.source_location(text_range.start(), source);
let end = line_index.source_location(text_range.end(), source);
Self::from((start, end))
Self {
start: Position::from_text_size(text_range.start(), line_index, source),
end: Position::from_text_size(text_range.end(), line_index, source),
}
}
fn to_text_range(
self,
line_index: &LineIndex,
source: &str,
) -> Result<ruff_text_size::TextRange, Error> {
let start = self.start.to_text_size(source, line_index)?;
let end = self.end.to_text_size(source, line_index)?;
Ok(ruff_text_size::TextRange::new(start, end))
}
}
@ -415,6 +435,28 @@ impl Position {
}
}
impl Position {
fn to_text_size(self, text: &str, index: &LineIndex) -> Result<TextSize, Error> {
let text_size = index.offset(
OneIndexed::new(self.line).ok_or_else(|| {
Error::new("Invalid value `0` for `position.line`. The line index is 1-indexed.")
})?,
OneIndexed::new(self.column).ok_or_else(|| {
Error::new(
"Invalid value `0` for `position.column`. The column index is 1-indexed.",
)
})?,
text,
);
Ok(text_size)
}
fn from_text_size(offset: TextSize, line_index: &LineIndex, source: &str) -> Self {
line_index.source_location(offset, source).into()
}
}
impl From<SourceLocation> for Position {
fn from(location: SourceLocation) -> Self {
Self {
@ -433,13 +475,13 @@ pub enum Severity {
Fatal,
}
impl From<ruff_db::diagnostic::Severity> for Severity {
fn from(value: ruff_db::diagnostic::Severity) -> Self {
impl From<diagnostic::Severity> for Severity {
fn from(value: diagnostic::Severity) -> Self {
match value {
ruff_db::diagnostic::Severity::Info => Self::Info,
ruff_db::diagnostic::Severity::Warning => Self::Warning,
ruff_db::diagnostic::Severity::Error => Self::Error,
ruff_db::diagnostic::Severity::Fatal => Self::Fatal,
diagnostic::Severity::Info => Self::Info,
diagnostic::Severity::Warning => Self::Warning,
diagnostic::Severity::Error => Self::Error,
diagnostic::Severity::Fatal => Self::Fatal,
}
}
}
@ -481,6 +523,14 @@ pub struct Hover {
pub range: Range,
}
#[wasm_bindgen]
pub struct InlayHint {
#[wasm_bindgen(getter_with_clone)]
pub markdown: String,
pub position: Position,
}
#[derive(Debug, Clone)]
struct WasmSystem {
fs: MemoryFileSystem,