[ty] Add goto definition to playground (#19425)

This commit is contained in:
Micha Reiser 2025-07-19 15:44:44 +02:00 committed by GitHub
parent 06f9f52e59
commit 98d1811dd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 175 additions and 45 deletions

View File

@ -4,7 +4,7 @@ use js_sys::{Error, JsString};
use ruff_db::Db as _;
use ruff_db::diagnostic::{self, DisplayDiagnosticConfig};
use ruff_db::files::{File, FileRange, system_path_to_file};
use ruff_db::source::{line_index, source_text};
use ruff_db::source::{SourceText, line_index, source_text};
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
use ruff_db::system::{
CaseSensitivity, DirectoryEntry, GlobError, MemoryFileSystem, Metadata, PatternError, System,
@ -14,8 +14,11 @@ use ruff_notebook::Notebook;
use ruff_python_formatter::formatted_file;
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
use ruff_text_size::{Ranged, TextSize};
use ty_ide::signature_help;
use ty_ide::{MarkupKind, goto_type_definition, hover, inlay_hints};
use ty_ide::{
MarkupKind, RangedValue, goto_declaration, goto_definition, goto_type_definition, hover,
inlay_hints,
};
use ty_ide::{NavigationTargets, signature_help};
use ty_project::metadata::options::Options;
use ty_project::metadata::value::ValueSource;
use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind};
@ -267,32 +270,61 @@ impl Workspace {
return Ok(Vec::new());
};
let source_range = Range::from_text_range(
targets.file_range().range(),
&index,
Ok(map_targets_to_links(
&self.db,
targets,
&source,
&index,
self.position_encoding,
);
))
}
let links: Vec<_> = targets
.into_iter()
.map(|target| LocationLink {
path: target.file().path(&self.db).to_string(),
full_range: Range::from_file_range(
&self.db,
FileRange::new(target.file(), target.full_range()),
self.position_encoding,
),
selection_range: Some(Range::from_file_range(
&self.db,
FileRange::new(target.file(), target.focus_range()),
self.position_encoding,
)),
origin_selection_range: Some(source_range),
})
.collect();
#[wasm_bindgen(js_name = "gotoDeclaration")]
pub fn goto_declaration(
&self,
file_id: &FileHandle,
position: Position,
) -> Result<Vec<LocationLink>, Error> {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
Ok(links)
let offset = position.to_text_size(&source, &index, self.position_encoding)?;
let Some(targets) = goto_declaration(&self.db, file_id.file, offset) else {
return Ok(Vec::new());
};
Ok(map_targets_to_links(
&self.db,
targets,
&source,
&index,
self.position_encoding,
))
}
#[wasm_bindgen(js_name = "gotoDefinition")]
pub fn goto_definition(
&self,
file_id: &FileHandle,
position: Position,
) -> Result<Vec<LocationLink>, Error> {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
let offset = position.to_text_size(&source, &index, self.position_encoding)?;
let Some(targets) = goto_definition(&self.db, file_id.file, offset) else {
return Ok(Vec::new());
};
Ok(map_targets_to_links(
&self.db,
targets,
&source,
&index,
self.position_encoding,
))
}
#[wasm_bindgen]
@ -464,6 +496,39 @@ pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
Error::new(&err.to_string())
}
fn map_targets_to_links(
db: &dyn Db,
targets: RangedValue<NavigationTargets>,
source: &SourceText,
index: &LineIndex,
position_encoding: PositionEncoding,
) -> Vec<LocationLink> {
let source_range = Range::from_text_range(
targets.file_range().range(),
index,
source,
position_encoding,
);
targets
.into_iter()
.map(|target| LocationLink {
path: target.file().path(db).to_string(),
full_range: Range::from_file_range(
db,
FileRange::new(target.file(), target.full_range()),
position_encoding,
),
selection_range: Some(Range::from_file_range(
db,
FileRange::new(target.file(), target.focus_range()),
position_encoding,
)),
origin_selection_range: Some(source_range),
})
.collect()
}
#[derive(Debug, Eq, PartialEq)]
#[wasm_bindgen(inspectable)]
pub struct FileHandle {

View File

@ -146,6 +146,8 @@ interface PlaygroundServerProps {
class PlaygroundServer
implements
languages.TypeDefinitionProvider,
languages.DeclarationProvider,
languages.DefinitionProvider,
editor.ICodeEditorOpener,
languages.HoverProvider,
languages.InlayHintsProvider,
@ -156,6 +158,8 @@ class PlaygroundServer
languages.SignatureHelpProvider
{
private typeDefinitionProviderDisposable: IDisposable;
private declarationProviderDisposable: IDisposable;
private definitionProviderDisposable: IDisposable;
private editorOpenerDisposable: IDisposable;
private hoverDisposable: IDisposable;
private inlayHintsDisposable: IDisposable;
@ -171,6 +175,10 @@ class PlaygroundServer
) {
this.typeDefinitionProviderDisposable =
monaco.languages.registerTypeDefinitionProvider("python", this);
this.declarationProviderDisposable =
monaco.languages.registerDeclarationProvider("python", this);
this.definitionProviderDisposable =
monaco.languages.registerDefinitionProvider("python", this);
this.hoverDisposable = monaco.languages.registerHoverProvider(
"python",
this,
@ -517,29 +525,61 @@ class PlaygroundServer
new TyPosition(position.lineNumber, position.column),
);
return (
links
.map((link) => {
const targetSelection =
link.selection_range == null
? undefined
: tyRangeToMonacoRange(link.selection_range);
return mapNavigationTargets(links);
}
const originSelection =
link.origin_selection_range == null
? undefined
: tyRangeToMonacoRange(link.origin_selection_range);
provideDeclaration(
model: editor.ITextModel,
position: Position,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_: CancellationToken,
): languages.ProviderResult<languages.Definition | languages.LocationLink[]> {
const workspace = this.props.workspace;
return {
uri: Uri.parse(link.path),
range: tyRangeToMonacoRange(link.full_range),
targetSelectionRange: targetSelection,
originSelectionRange: originSelection,
} as languages.LocationLink;
})
// Filter out vendored files because they aren't open in the editor.
.filter((link) => link.uri.scheme !== "vendored")
const selectedFile = this.props.files.selected;
if (selectedFile == null) {
return;
}
const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) {
return;
}
const links = workspace.gotoDeclaration(
selectedHandle,
new TyPosition(position.lineNumber, position.column),
);
return mapNavigationTargets(links);
}
provideDefinition(
model: editor.ITextModel,
position: Position,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_: CancellationToken,
): languages.ProviderResult<languages.Definition | languages.LocationLink[]> {
const workspace = this.props.workspace;
const selectedFile = this.props.files.selected;
if (selectedFile == null) {
return;
}
const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) {
return;
}
const links = workspace.gotoDefinition(
selectedHandle,
new TyPosition(position.lineNumber, position.column),
);
return mapNavigationTargets(links);
}
openCodeEditor(
@ -625,6 +665,8 @@ class PlaygroundServer
this.hoverDisposable.dispose();
this.editorOpenerDisposable.dispose();
this.typeDefinitionProviderDisposable.dispose();
this.declarationProviderDisposable.dispose();
this.definitionProviderDisposable.dispose();
this.inlayHintsDisposable.dispose();
this.formatDisposable.dispose();
this.rangeSemanticTokensDisposable.dispose();
@ -683,6 +725,29 @@ function generateMonacoTokens(
return { data: Uint32Array.from(result) };
}
function mapNavigationTargets(links: any[]): languages.LocationLink[] {
return links
.map((link) => {
const targetSelection =
link.selection_range == null
? undefined
: tyRangeToMonacoRange(link.selection_range);
const originSelection =
link.origin_selection_range == null
? undefined
: tyRangeToMonacoRange(link.origin_selection_range);
return {
uri: Uri.parse(link.path),
range: tyRangeToMonacoRange(link.full_range),
targetSelectionRange: targetSelection,
originSelectionRange: originSelection,
} as languages.LocationLink;
})
.filter((link) => link.uri.scheme !== "vendored");
}
function mapCompletionKind(kind: CompletionKind): CompletionItemKind {
switch (kind) {
case CompletionKind.Text: