11use std:: any:: Any ;
22
33use js_sys:: { Error , JsString } ;
4+ use red_knot_ide:: go_to_type_definition;
45use red_knot_project:: metadata:: options:: Options ;
56use red_knot_project:: metadata:: value:: ValueSource ;
67use red_knot_project:: watch:: { ChangeEvent , ChangedKind , CreatedKind , DeletedKind } ;
78use red_knot_project:: ProjectMetadata ;
89use red_knot_project:: { Db , ProjectDatabase } ;
910use red_knot_python_semantic:: Program ;
1011use ruff_db:: diagnostic:: { DisplayDiagnosticConfig , OldDiagnosticTrait } ;
11- use ruff_db:: files:: { system_path_to_file, File } ;
12+ use ruff_db:: files:: { system_path_to_file, File , FileRange } ;
1213use ruff_db:: source:: { line_index, source_text} ;
1314use ruff_db:: system:: walk_directory:: WalkDirectoryBuilder ;
1415use ruff_db:: system:: {
@@ -17,7 +18,8 @@ use ruff_db::system::{
1718} ;
1819use ruff_db:: Upcast ;
1920use ruff_notebook:: Notebook ;
20- use ruff_source_file:: SourceLocation ;
21+ use ruff_source_file:: { LineIndex , OneIndexed , SourceLocation } ;
22+ use ruff_text_size:: Ranged ;
2123use wasm_bindgen:: prelude:: * ;
2224
2325#[ wasm_bindgen( start) ]
@@ -195,6 +197,52 @@ impl Workspace {
195197
196198 Ok ( source_text. to_string ( ) )
197199 }
200+
201+ #[ wasm_bindgen( js_name = "gotoTypeDefinition" ) ]
202+ pub fn goto_type_definition (
203+ & self ,
204+ file_id : & FileHandle ,
205+ position : Position ,
206+ ) -> Result < Vec < LocationLink > , Error > {
207+ let source = source_text ( & self . db , file_id. file ) ;
208+ let index = line_index ( & self . db , file_id. file ) ;
209+
210+ let offset = index. offset (
211+ OneIndexed :: new ( position. line ) . ok_or_else ( || {
212+ Error :: new ( "Invalid value `0` for `position.line`. The line index is 1-indexed." )
213+ } ) ?,
214+ OneIndexed :: new ( position. column ) . ok_or_else ( || {
215+ Error :: new (
216+ "Invalid value `0` for `position.column`. The column index is 1-indexed." ,
217+ )
218+ } ) ?,
219+ & source,
220+ ) ;
221+
222+ let Some ( targets) = go_to_type_definition ( & self . db , file_id. file , offset) else {
223+ return Ok ( Vec :: new ( ) ) ;
224+ } ;
225+
226+ let source_range = Range :: from_text_range ( targets. file_range ( ) . range ( ) , & index, & source) ;
227+
228+ let links: Vec < _ > = targets
229+ . into_iter ( )
230+ . map ( |target| LocationLink {
231+ path : target. file ( ) . path ( & self . db ) . to_string ( ) ,
232+ full_range : Range :: from_file_range (
233+ & self . db ,
234+ FileRange :: new ( target. file ( ) , target. full_range ( ) ) ,
235+ ) ,
236+ selection_range : Some ( Range :: from_file_range (
237+ & self . db ,
238+ FileRange :: new ( target. file ( ) , target. focus_range ( ) ) ,
239+ ) ) ,
240+ origin_selection_range : Some ( source_range) ,
241+ } )
242+ . collect ( ) ;
243+
244+ Ok ( links)
245+ }
198246}
199247
200248pub ( crate ) fn into_error < E : std:: fmt:: Display > ( err : E ) -> Error {
@@ -257,16 +305,10 @@ impl Diagnostic {
257305 #[ wasm_bindgen( js_name = "toRange" ) ]
258306 pub fn to_range ( & self , workspace : & Workspace ) -> Option < Range > {
259307 self . inner . span ( ) . and_then ( |span| {
260- let line_index = line_index ( workspace. db . upcast ( ) , span. file ( ) ) ;
261- let source = source_text ( workspace. db . upcast ( ) , span. file ( ) ) ;
262- let text_range = span. range ( ) ?;
263-
264- Some ( Range {
265- start : line_index
266- . source_location ( text_range. start ( ) , & source)
267- . into ( ) ,
268- end : line_index. source_location ( text_range. end ( ) , & source) . into ( ) ,
269- } )
308+ Some ( Range :: from_file_range (
309+ & workspace. db ,
310+ FileRange :: new ( span. file ( ) , span. range ( ) ?) ,
311+ ) )
270312 } )
271313 }
272314
@@ -287,6 +329,38 @@ pub struct Range {
287329 pub end : Position ,
288330}
289331
332+ impl Range {
333+ fn from_file_range ( db : & dyn Db , range : FileRange ) -> Self {
334+ let index = line_index ( db. upcast ( ) , range. file ( ) ) ;
335+ let source = source_text ( db. upcast ( ) , range. file ( ) ) ;
336+
337+ let text_range = range. range ( ) ;
338+
339+ let start = index. source_location ( text_range. start ( ) , & source) ;
340+ let end = index. source_location ( text_range. end ( ) , & source) ;
341+ Self :: from ( ( start, end) )
342+ }
343+
344+ fn from_text_range (
345+ text_range : ruff_text_size:: TextRange ,
346+ line_index : & LineIndex ,
347+ source : & str ,
348+ ) -> Self {
349+ let start = line_index. source_location ( text_range. start ( ) , source) ;
350+ let end = line_index. source_location ( text_range. end ( ) , source) ;
351+ Self :: from ( ( start, end) )
352+ }
353+ }
354+
355+ impl From < ( SourceLocation , SourceLocation ) > for Range {
356+ fn from ( ( start, end) : ( SourceLocation , SourceLocation ) ) -> Self {
357+ Self {
358+ start : start. into ( ) ,
359+ end : end. into ( ) ,
360+ }
361+ }
362+ }
363+
290364#[ wasm_bindgen]
291365#[ derive( Copy , Clone , Eq , PartialEq , Debug ) ]
292366pub struct Position {
@@ -297,6 +371,14 @@ pub struct Position {
297371 pub column : usize ,
298372}
299373
374+ #[ wasm_bindgen]
375+ impl Position {
376+ #[ wasm_bindgen( constructor) ]
377+ pub fn new ( line : usize , column : usize ) -> Self {
378+ Self { line, column }
379+ }
380+ }
381+
300382impl From < SourceLocation > for Position {
301383 fn from ( location : SourceLocation ) -> Self {
302384 Self {
@@ -341,6 +423,20 @@ impl From<ruff_text_size::TextRange> for TextRange {
341423 }
342424}
343425
426+ #[ wasm_bindgen]
427+ pub struct LocationLink {
428+ /// The target file path
429+ #[ wasm_bindgen( getter_with_clone) ]
430+ pub path : String ,
431+
432+ /// The full range of the target
433+ pub full_range : Range ,
434+ /// The target's range that should be selected/highlighted
435+ pub selection_range : Option < Range > ,
436+ /// The range of the origin.
437+ pub origin_selection_range : Option < Range > ,
438+ }
439+
344440#[ derive( Debug , Clone ) ]
345441struct WasmSystem {
346442 fs : MemoryFileSystem ,
0 commit comments