@@ -29,8 +29,16 @@ use crate::{
29
29
FilePosition , Semantics ,
30
30
} ;
31
31
32
- /// Weblink to an item's documentation.
33
- pub ( crate ) type DocumentationLink = String ;
32
+ /// Web and local links to an item's documentation.
33
+ #[ derive( Default , Debug , Clone , PartialEq , Eq ) ]
34
+ pub struct DocumentationLinks {
35
+ /// The URL to the documentation on docs.rs.
36
+ /// Could be invalid.
37
+ pub web_url : Option < String > ,
38
+ /// The URL to the documentation in the local file system.
39
+ /// Could be invalid.
40
+ pub local_url : Option < String > ,
41
+ }
34
42
35
43
const MARKDOWN_OPTIONS : Options =
36
44
Options :: ENABLE_FOOTNOTES . union ( Options :: ENABLE_TABLES ) . union ( Options :: ENABLE_TASKLISTS ) ;
@@ -119,38 +127,38 @@ pub(crate) fn remove_links(markdown: &str) -> String {
119
127
//
120
128
// | VS Code | **rust-analyzer: Open Docs**
121
129
// |===
122
- pub ( crate ) fn external_docs (
123
- db : & RootDatabase ,
124
- position : & FilePosition ,
125
- ) -> Option < DocumentationLink > {
130
+ pub ( crate ) fn external_docs ( db : & RootDatabase , position : & FilePosition ) -> DocumentationLinks {
126
131
let sema = & Semantics :: new ( db) ;
127
132
let file = sema. parse ( position. file_id ) . syntax ( ) . clone ( ) ;
128
133
let token = pick_best_token ( file. token_at_offset ( position. offset ) , |kind| match kind {
129
134
IDENT | INT_NUMBER | T ! [ self ] => 3 ,
130
135
T ! [ '(' ] | T ! [ ')' ] => 2 ,
131
136
kind if kind. is_trivia ( ) => 0 ,
132
137
_ => 1 ,
133
- } ) ?;
138
+ } ) ;
139
+ let Some ( token) = token else { return Default :: default ( ) } ;
134
140
let token = sema. descend_into_macros_single ( token) ;
135
141
136
- let node = token. parent ( ) ? ;
142
+ let Some ( node) = token. parent ( ) else { return Default :: default ( ) } ;
137
143
let definition = match_ast ! {
138
144
match node {
139
- ast:: NameRef ( name_ref) => match NameRefClass :: classify( sema, & name_ref) ? {
140
- NameRefClass :: Definition ( def) => def,
141
- NameRefClass :: FieldShorthand { local_ref: _, field_ref } => {
145
+ ast:: NameRef ( name_ref) => match NameRefClass :: classify( sema, & name_ref) {
146
+ Some ( NameRefClass :: Definition ( def) ) => def,
147
+ Some ( NameRefClass :: FieldShorthand { local_ref: _, field_ref } ) => {
142
148
Definition :: Field ( field_ref)
143
149
}
150
+ None => return Default :: default ( ) ,
144
151
} ,
145
- ast:: Name ( name) => match NameClass :: classify( sema, & name) ? {
146
- NameClass :: Definition ( it) | NameClass :: ConstReference ( it) => it,
147
- NameClass :: PatFieldShorthand { local_def: _, field_ref } => Definition :: Field ( field_ref) ,
152
+ ast:: Name ( name) => match NameClass :: classify( sema, & name) {
153
+ Some ( NameClass :: Definition ( it) | NameClass :: ConstReference ( it) ) => it,
154
+ Some ( NameClass :: PatFieldShorthand { local_def: _, field_ref } ) => Definition :: Field ( field_ref) ,
155
+ None => return Default :: default ( ) ,
148
156
} ,
149
- _ => return None ,
157
+ _ => return Default :: default ( ) ,
150
158
}
151
159
} ;
152
160
153
- get_doc_link ( db, definition)
161
+ return get_doc_links ( db, definition) ;
154
162
}
155
163
156
164
/// Extracts all links from a given markdown text returning the definition text range, link-text
@@ -308,19 +316,34 @@ fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)
308
316
//
309
317
// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
310
318
// https://github.com/rust-lang/rfcs/pull/2988
311
- fn get_doc_link ( db : & RootDatabase , def : Definition ) -> Option < String > {
312
- let ( target, file, frag) = filename_and_frag_for_def ( db, def) ? ;
319
+ fn get_doc_links ( db : & RootDatabase , def : Definition ) -> DocumentationLinks {
320
+ let Some ( ( target, file, frag) ) = filename_and_frag_for_def ( db, def) else { return Default :: default ( ) ; } ;
313
321
314
- let mut url = get_doc_base_url ( db, target) ? ;
322
+ let ( mut web_url , mut local_url ) = get_doc_base_urls ( db, target) ;
315
323
316
324
if let Some ( path) = mod_path_of_def ( db, target) {
317
- url = url. join ( & path) . ok ( ) ?;
325
+ web_url = join_url ( web_url, & path) ;
326
+ local_url = join_url ( local_url, & path) ;
318
327
}
319
328
320
- url = url. join ( & file) . ok ( ) ?;
321
- url. set_fragment ( frag. as_deref ( ) ) ;
329
+ web_url = join_url ( web_url, & file) ;
330
+ local_url = join_url ( local_url, & file) ;
331
+
332
+ set_fragment_for_url ( web_url. as_mut ( ) , frag. as_deref ( ) ) ;
333
+ set_fragment_for_url ( local_url. as_mut ( ) , frag. as_deref ( ) ) ;
322
334
323
- Some ( url. into ( ) )
335
+ return DocumentationLinks {
336
+ web_url : web_url. map ( |it| it. into ( ) ) ,
337
+ local_url : local_url. map ( |it| it. into ( ) ) ,
338
+ } ;
339
+
340
+ fn join_url ( base_url : Option < Url > , path : & str ) -> Option < Url > {
341
+ base_url. and_then ( |url| url. join ( path) . ok ( ) )
342
+ }
343
+
344
+ fn set_fragment_for_url ( url : Option < & mut Url > , frag : Option < & str > ) {
345
+ url. map ( |url| url. set_fragment ( frag) ) ;
346
+ }
324
347
}
325
348
326
349
fn rewrite_intra_doc_link (
@@ -332,7 +355,7 @@ fn rewrite_intra_doc_link(
332
355
let ( link, ns) = parse_intra_doc_link ( target) ;
333
356
334
357
let resolved = resolve_doc_path_for_def ( db, def, link, ns) ?;
335
- let mut url = get_doc_base_url ( db, resolved) ?;
358
+ let mut url = get_doc_base_urls ( db, resolved) . 0 ?;
336
359
337
360
let ( _, file, frag) = filename_and_frag_for_def ( db, resolved) ?;
338
361
if let Some ( path) = mod_path_of_def ( db, resolved) {
@@ -351,7 +374,7 @@ fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option<
351
374
return None ;
352
375
}
353
376
354
- let mut url = get_doc_base_url ( db, def) ?;
377
+ let mut url = get_doc_base_urls ( db, def) . 0 ?;
355
378
let ( def, file, frag) = filename_and_frag_for_def ( db, def) ?;
356
379
357
380
if let Some ( path) = mod_path_of_def ( db, def) {
@@ -427,18 +450,26 @@ fn map_links<'e>(
427
450
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
428
451
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
429
452
/// ```
430
- fn get_doc_base_url ( db : & RootDatabase , def : Definition ) -> Option < Url > {
453
+ fn get_doc_base_urls ( db : & RootDatabase , def : Definition ) -> ( Option < Url > , Option < Url > ) {
454
+ // TODO: get this is from `CargoWorkspace`
455
+ // TODO: get `CargoWorkspace` from `db`
456
+ let target_path = "file:///project/root/target" ;
457
+ let target_path = Url :: parse ( target_path) . ok ( ) ;
458
+ let local_doc_path = target_path. and_then ( |url| url. join ( "doc" ) . ok ( ) ) ;
459
+ debug_assert ! ( local_doc_path. is_some( ) , "failed to parse local doc path" ) ;
460
+
431
461
// special case base url of `BuiltinType` to core
432
462
// https://github.com/rust-lang/rust-analyzer/issues/12250
433
463
if let Definition :: BuiltinType ( ..) = def {
434
- return Url :: parse ( "https://doc.rust-lang.org/nightly/core/" ) . ok ( ) ;
464
+ let weblink = Url :: parse ( "https://doc.rust-lang.org/nightly/core/" ) . ok ( ) ;
465
+ return ( weblink, local_doc_path) ;
435
466
} ;
436
467
437
- let krate = def. krate ( db) ? ;
438
- let display_name = krate. display_name ( db) ? ;
468
+ let Some ( krate) = def. krate ( db) else { return Default :: default ( ) } ;
469
+ let Some ( display_name) = krate. display_name ( db) else { return Default :: default ( ) } ;
439
470
let crate_data = & db. crate_graph ( ) [ krate. into ( ) ] ;
440
471
let channel = crate_data. channel . map_or ( "nightly" , ReleaseChannel :: as_str) ;
441
- let base = match & crate_data. origin {
472
+ let ( web_base , local_base ) = match & crate_data. origin {
442
473
// std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
443
474
// FIXME: Use the toolchains channel instead of nightly
444
475
CrateOrigin :: Lang (
@@ -447,16 +478,14 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
447
478
| LangCrateOrigin :: ProcMacro
448
479
| LangCrateOrigin :: Std
449
480
| LangCrateOrigin :: Test ) ,
450
- ) => {
451
- format ! ( "https://doc.rust-lang.org/{channel}/{origin}" )
452
- }
453
- CrateOrigin :: Lang ( _) => return None ,
481
+ ) => ( Some ( format ! ( "https://doc.rust-lang.org/{channel}/{origin}" ) ) , None ) ,
482
+ CrateOrigin :: Lang ( _) => return ( None , None ) ,
454
483
CrateOrigin :: Rustc { name : _ } => {
455
- format ! ( "https://doc.rust-lang.org/{channel}/nightly-rustc/" )
484
+ ( Some ( format ! ( "https://doc.rust-lang.org/{channel}/nightly-rustc/" ) ) , None )
456
485
}
457
486
CrateOrigin :: Local { repo : _, name : _ } => {
458
487
// FIXME: These should not attempt to link to docs.rs!
459
- krate. get_html_root_url ( db) . or_else ( || {
488
+ let weblink = krate. get_html_root_url ( db) . or_else ( || {
460
489
let version = krate. version ( db) ;
461
490
// Fallback to docs.rs. This uses `display_name` and can never be
462
491
// correct, but that's what fallbacks are about.
@@ -468,10 +497,11 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
468
497
krate = display_name,
469
498
version = version. as_deref( ) . unwrap_or( "*" )
470
499
) )
471
- } ) ?
500
+ } ) ;
501
+ ( weblink, local_doc_path)
472
502
}
473
503
CrateOrigin :: Library { repo : _, name } => {
474
- krate. get_html_root_url ( db) . or_else ( || {
504
+ let weblink = krate. get_html_root_url ( db) . or_else ( || {
475
505
let version = krate. version ( db) ;
476
506
// Fallback to docs.rs. This uses `display_name` and can never be
477
507
// correct, but that's what fallbacks are about.
@@ -483,10 +513,14 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
483
513
krate = name,
484
514
version = version. as_deref( ) . unwrap_or( "*" )
485
515
) )
486
- } ) ?
516
+ } ) ;
517
+ ( weblink, local_doc_path)
487
518
}
488
519
} ;
489
- Url :: parse ( & base) . ok ( ) ?. join ( & format ! ( "{display_name}/" ) ) . ok ( )
520
+ let web_base = web_base
521
+ . and_then ( |it| Url :: parse ( & it) . ok ( ) )
522
+ . and_then ( |it| it. join ( & format ! ( "{display_name}/" ) ) . ok ( ) ) ;
523
+ ( web_base, local_base)
490
524
}
491
525
492
526
/// Get the filename and extension generated for a symbol by rustdoc.
0 commit comments