mirror of https://github.com/astral-sh/ruff
[ty] Add goto definition to playground (#19425)
This commit is contained in:
parent
06f9f52e59
commit
98d1811dd1
|
|
@ -4,7 +4,7 @@ use js_sys::{Error, JsString};
|
||||||
use ruff_db::Db as _;
|
use ruff_db::Db as _;
|
||||||
use ruff_db::diagnostic::{self, DisplayDiagnosticConfig};
|
use ruff_db::diagnostic::{self, DisplayDiagnosticConfig};
|
||||||
use ruff_db::files::{File, FileRange, system_path_to_file};
|
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::walk_directory::WalkDirectoryBuilder;
|
||||||
use ruff_db::system::{
|
use ruff_db::system::{
|
||||||
CaseSensitivity, DirectoryEntry, GlobError, MemoryFileSystem, Metadata, PatternError, System,
|
CaseSensitivity, DirectoryEntry, GlobError, MemoryFileSystem, Metadata, PatternError, System,
|
||||||
|
|
@ -14,8 +14,11 @@ use ruff_notebook::Notebook;
|
||||||
use ruff_python_formatter::formatted_file;
|
use ruff_python_formatter::formatted_file;
|
||||||
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
|
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
use ty_ide::signature_help;
|
use ty_ide::{
|
||||||
use ty_ide::{MarkupKind, goto_type_definition, hover, inlay_hints};
|
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::options::Options;
|
||||||
use ty_project::metadata::value::ValueSource;
|
use ty_project::metadata::value::ValueSource;
|
||||||
use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind};
|
use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind};
|
||||||
|
|
@ -267,32 +270,61 @@ impl Workspace {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
};
|
};
|
||||||
|
|
||||||
let source_range = Range::from_text_range(
|
Ok(map_targets_to_links(
|
||||||
targets.file_range().range(),
|
&self.db,
|
||||||
&index,
|
targets,
|
||||||
&source,
|
&source,
|
||||||
|
&index,
|
||||||
self.position_encoding,
|
self.position_encoding,
|
||||||
);
|
))
|
||||||
|
}
|
||||||
|
|
||||||
let links: Vec<_> = targets
|
#[wasm_bindgen(js_name = "gotoDeclaration")]
|
||||||
.into_iter()
|
pub fn goto_declaration(
|
||||||
.map(|target| LocationLink {
|
&self,
|
||||||
path: target.file().path(&self.db).to_string(),
|
file_id: &FileHandle,
|
||||||
full_range: Range::from_file_range(
|
position: Position,
|
||||||
&self.db,
|
) -> Result<Vec<LocationLink>, Error> {
|
||||||
FileRange::new(target.file(), target.full_range()),
|
let source = source_text(&self.db, file_id.file);
|
||||||
self.position_encoding,
|
let index = line_index(&self.db, file_id.file);
|
||||||
),
|
|
||||||
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();
|
|
||||||
|
|
||||||
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]
|
#[wasm_bindgen]
|
||||||
|
|
@ -464,6 +496,39 @@ pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
|
||||||
Error::new(&err.to_string())
|
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)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
#[wasm_bindgen(inspectable)]
|
#[wasm_bindgen(inspectable)]
|
||||||
pub struct FileHandle {
|
pub struct FileHandle {
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,8 @@ interface PlaygroundServerProps {
|
||||||
class PlaygroundServer
|
class PlaygroundServer
|
||||||
implements
|
implements
|
||||||
languages.TypeDefinitionProvider,
|
languages.TypeDefinitionProvider,
|
||||||
|
languages.DeclarationProvider,
|
||||||
|
languages.DefinitionProvider,
|
||||||
editor.ICodeEditorOpener,
|
editor.ICodeEditorOpener,
|
||||||
languages.HoverProvider,
|
languages.HoverProvider,
|
||||||
languages.InlayHintsProvider,
|
languages.InlayHintsProvider,
|
||||||
|
|
@ -156,6 +158,8 @@ class PlaygroundServer
|
||||||
languages.SignatureHelpProvider
|
languages.SignatureHelpProvider
|
||||||
{
|
{
|
||||||
private typeDefinitionProviderDisposable: IDisposable;
|
private typeDefinitionProviderDisposable: IDisposable;
|
||||||
|
private declarationProviderDisposable: IDisposable;
|
||||||
|
private definitionProviderDisposable: IDisposable;
|
||||||
private editorOpenerDisposable: IDisposable;
|
private editorOpenerDisposable: IDisposable;
|
||||||
private hoverDisposable: IDisposable;
|
private hoverDisposable: IDisposable;
|
||||||
private inlayHintsDisposable: IDisposable;
|
private inlayHintsDisposable: IDisposable;
|
||||||
|
|
@ -171,6 +175,10 @@ class PlaygroundServer
|
||||||
) {
|
) {
|
||||||
this.typeDefinitionProviderDisposable =
|
this.typeDefinitionProviderDisposable =
|
||||||
monaco.languages.registerTypeDefinitionProvider("python", this);
|
monaco.languages.registerTypeDefinitionProvider("python", this);
|
||||||
|
this.declarationProviderDisposable =
|
||||||
|
monaco.languages.registerDeclarationProvider("python", this);
|
||||||
|
this.definitionProviderDisposable =
|
||||||
|
monaco.languages.registerDefinitionProvider("python", this);
|
||||||
this.hoverDisposable = monaco.languages.registerHoverProvider(
|
this.hoverDisposable = monaco.languages.registerHoverProvider(
|
||||||
"python",
|
"python",
|
||||||
this,
|
this,
|
||||||
|
|
@ -517,29 +525,61 @@ class PlaygroundServer
|
||||||
new TyPosition(position.lineNumber, position.column),
|
new TyPosition(position.lineNumber, position.column),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return mapNavigationTargets(links);
|
||||||
links
|
}
|
||||||
.map((link) => {
|
|
||||||
const targetSelection =
|
|
||||||
link.selection_range == null
|
|
||||||
? undefined
|
|
||||||
: tyRangeToMonacoRange(link.selection_range);
|
|
||||||
|
|
||||||
const originSelection =
|
provideDeclaration(
|
||||||
link.origin_selection_range == null
|
model: editor.ITextModel,
|
||||||
? undefined
|
position: Position,
|
||||||
: tyRangeToMonacoRange(link.origin_selection_range);
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_: CancellationToken,
|
||||||
|
): languages.ProviderResult<languages.Definition | languages.LocationLink[]> {
|
||||||
|
const workspace = this.props.workspace;
|
||||||
|
|
||||||
return {
|
const selectedFile = this.props.files.selected;
|
||||||
uri: Uri.parse(link.path),
|
if (selectedFile == null) {
|
||||||
range: tyRangeToMonacoRange(link.full_range),
|
return;
|
||||||
targetSelectionRange: targetSelection,
|
}
|
||||||
originSelectionRange: originSelection,
|
|
||||||
} as languages.LocationLink;
|
const selectedHandle = this.props.files.handles[selectedFile];
|
||||||
})
|
|
||||||
// Filter out vendored files because they aren't open in the editor.
|
if (selectedHandle == null) {
|
||||||
.filter((link) => link.uri.scheme !== "vendored")
|
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(
|
openCodeEditor(
|
||||||
|
|
@ -625,6 +665,8 @@ class PlaygroundServer
|
||||||
this.hoverDisposable.dispose();
|
this.hoverDisposable.dispose();
|
||||||
this.editorOpenerDisposable.dispose();
|
this.editorOpenerDisposable.dispose();
|
||||||
this.typeDefinitionProviderDisposable.dispose();
|
this.typeDefinitionProviderDisposable.dispose();
|
||||||
|
this.declarationProviderDisposable.dispose();
|
||||||
|
this.definitionProviderDisposable.dispose();
|
||||||
this.inlayHintsDisposable.dispose();
|
this.inlayHintsDisposable.dispose();
|
||||||
this.formatDisposable.dispose();
|
this.formatDisposable.dispose();
|
||||||
this.rangeSemanticTokensDisposable.dispose();
|
this.rangeSemanticTokensDisposable.dispose();
|
||||||
|
|
@ -683,6 +725,29 @@ function generateMonacoTokens(
|
||||||
return { data: Uint32Array.from(result) };
|
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 {
|
function mapCompletionKind(kind: CompletionKind): CompletionItemKind {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case CompletionKind.Text:
|
case CompletionKind.Text:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue