@@ -5,6 +5,8 @@ mod tests;
5
5
6
6
mod intra_doc_links;
7
7
8
+ use std:: ffi:: OsStr ;
9
+
8
10
use pulldown_cmark:: { BrokenLink , CowStr , Event , InlineStr , LinkType , Options , Parser , Tag } ;
9
11
use pulldown_cmark_to_cmark:: { cmark_resume_with_options, Options as CMarkOptions } ;
10
12
use stdx:: format_to;
@@ -29,8 +31,16 @@ use crate::{
29
31
FilePosition , Semantics ,
30
32
} ;
31
33
32
- /// Weblink to an item's documentation.
33
- pub ( crate ) type DocumentationLink = String ;
34
+ /// Web and local links to an item's documentation.
35
+ #[ derive( Default , Debug , Clone , PartialEq , Eq ) ]
36
+ pub struct DocumentationLinks {
37
+ /// The URL to the documentation on docs.rs.
38
+ /// May not lead anywhere.
39
+ pub web_url : Option < String > ,
40
+ /// The URL to the documentation in the local file system.
41
+ /// May not lead anywhere.
42
+ pub local_url : Option < String > ,
43
+ }
34
44
35
45
const MARKDOWN_OPTIONS : Options =
36
46
Options :: ENABLE_FOOTNOTES . union ( Options :: ENABLE_TABLES ) . union ( Options :: ENABLE_TASKLISTS ) ;
@@ -109,7 +119,7 @@ pub(crate) fn remove_links(markdown: &str) -> String {
109
119
110
120
// Feature: Open Docs
111
121
//
112
- // Retrieve a link to documentation for the given symbol.
122
+ // Retrieve a links to documentation for the given symbol.
113
123
//
114
124
// The simplest way to use this feature is via the context menu. Right-click on
115
125
// the selected item. The context menu opens. Select **Open Docs**.
@@ -122,7 +132,9 @@ pub(crate) fn remove_links(markdown: &str) -> String {
122
132
pub ( crate ) fn external_docs (
123
133
db : & RootDatabase ,
124
134
position : & FilePosition ,
125
- ) -> Option < DocumentationLink > {
135
+ target_dir : Option < & OsStr > ,
136
+ sysroot : Option < & OsStr > ,
137
+ ) -> Option < DocumentationLinks > {
126
138
let sema = & Semantics :: new ( db) ;
127
139
let file = sema. parse ( position. file_id ) . syntax ( ) . clone ( ) ;
128
140
let token = pick_best_token ( file. token_at_offset ( position. offset ) , |kind| match kind {
@@ -146,11 +158,11 @@ pub(crate) fn external_docs(
146
158
NameClass :: Definition ( it) | NameClass :: ConstReference ( it) => it,
147
159
NameClass :: PatFieldShorthand { local_def: _, field_ref } => Definition :: Field ( field_ref) ,
148
160
} ,
149
- _ => return None ,
161
+ _ => return None
150
162
}
151
163
} ;
152
164
153
- get_doc_link ( db, definition)
165
+ Some ( get_doc_links ( db, definition, target_dir , sysroot ) )
154
166
}
155
167
156
168
/// Extracts all links from a given markdown text returning the definition text range, link-text
@@ -308,19 +320,35 @@ fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)
308
320
//
309
321
// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
310
322
// 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) ?;
323
+ fn get_doc_links (
324
+ db : & RootDatabase ,
325
+ def : Definition ,
326
+ target_dir : Option < & OsStr > ,
327
+ sysroot : Option < & OsStr > ,
328
+ ) -> DocumentationLinks {
329
+ let join_url = |base_url : Option < Url > , path : & str | -> Option < Url > {
330
+ base_url. and_then ( |url| url. join ( path) . ok ( ) )
331
+ } ;
332
+
333
+ let Some ( ( target, file, frag) ) = filename_and_frag_for_def ( db, def) else { return Default :: default ( ) ; } ;
313
334
314
- let mut url = get_doc_base_url ( db, target) ? ;
335
+ let ( mut web_url , mut local_url ) = get_doc_base_urls ( db, target, target_dir , sysroot ) ;
315
336
316
337
if let Some ( path) = mod_path_of_def ( db, target) {
317
- url = url. join ( & path) . ok ( ) ?;
338
+ web_url = join_url ( web_url, & path) ;
339
+ local_url = join_url ( local_url, & path) ;
318
340
}
319
341
320
- url = url. join ( & file) . ok ( ) ?;
321
- url. set_fragment ( frag. as_deref ( ) ) ;
342
+ web_url = join_url ( web_url, & file) ;
343
+ local_url = join_url ( local_url, & file) ;
344
+
345
+ web_url. as_mut ( ) . map ( |url| url. set_fragment ( frag. as_deref ( ) ) ) ;
346
+ local_url. as_mut ( ) . map ( |url| url. set_fragment ( frag. as_deref ( ) ) ) ;
322
347
323
- Some ( url. into ( ) )
348
+ DocumentationLinks {
349
+ web_url : web_url. map ( |it| it. into ( ) ) ,
350
+ local_url : local_url. map ( |it| it. into ( ) ) ,
351
+ }
324
352
}
325
353
326
354
fn rewrite_intra_doc_link (
@@ -332,7 +360,7 @@ fn rewrite_intra_doc_link(
332
360
let ( link, ns) = parse_intra_doc_link ( target) ;
333
361
334
362
let resolved = resolve_doc_path_for_def ( db, def, link, ns) ?;
335
- let mut url = get_doc_base_url ( db, resolved) ?;
363
+ let mut url = get_doc_base_urls ( db, resolved, None , None ) . 0 ?;
336
364
337
365
let ( _, file, frag) = filename_and_frag_for_def ( db, resolved) ?;
338
366
if let Some ( path) = mod_path_of_def ( db, resolved) {
@@ -351,7 +379,7 @@ fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option<
351
379
return None ;
352
380
}
353
381
354
- let mut url = get_doc_base_url ( db, def) ?;
382
+ let mut url = get_doc_base_urls ( db, def, None , None ) . 0 ?;
355
383
let ( def, file, frag) = filename_and_frag_for_def ( db, def) ?;
356
384
357
385
if let Some ( path) = mod_path_of_def ( db, def) {
@@ -426,19 +454,38 @@ fn map_links<'e>(
426
454
/// ```ignore
427
455
/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
428
456
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
457
+ /// file:///project/root/target/doc/std/iter/trait.Iterator.html#tymethod.next
458
+ /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
429
459
/// ```
430
- fn get_doc_base_url ( db : & RootDatabase , def : Definition ) -> Option < Url > {
460
+ fn get_doc_base_urls (
461
+ db : & RootDatabase ,
462
+ def : Definition ,
463
+ target_dir : Option < & OsStr > ,
464
+ sysroot : Option < & OsStr > ,
465
+ ) -> ( Option < Url > , Option < Url > ) {
466
+ let local_doc = target_dir
467
+ . and_then ( |path| path. to_str ( ) )
468
+ . and_then ( |path| Url :: parse ( & format ! ( "file:///{path}/" ) ) . ok ( ) )
469
+ . and_then ( |it| it. join ( "doc/" ) . ok ( ) ) ;
470
+ let system_doc = sysroot
471
+ . and_then ( |it| it. to_str ( ) )
472
+ . map ( |sysroot| format ! ( "file:///{sysroot}/share/doc/rust/html/" ) )
473
+ . and_then ( |it| Url :: parse ( & it) . ok ( ) ) ;
474
+
431
475
// special case base url of `BuiltinType` to core
432
476
// https://github.com/rust-lang/rust-analyzer/issues/12250
433
477
if let Definition :: BuiltinType ( ..) = def {
434
- return Url :: parse ( "https://doc.rust-lang.org/nightly/core/" ) . ok ( ) ;
478
+ let web_link = Url :: parse ( "https://doc.rust-lang.org/nightly/core/" ) . ok ( ) ;
479
+ let system_link = system_doc. and_then ( |it| it. join ( "core/" ) . ok ( ) ) ;
480
+ return ( web_link, system_link) ;
435
481
} ;
436
482
437
- let krate = def. krate ( db) ? ;
438
- let display_name = krate. display_name ( db) ? ;
483
+ let Some ( krate) = def. krate ( db) else { return Default :: default ( ) } ;
484
+ let Some ( display_name) = krate. display_name ( db) else { return Default :: default ( ) } ;
439
485
let crate_data = & db. crate_graph ( ) [ krate. into ( ) ] ;
440
486
let channel = crate_data. channel . map_or ( "nightly" , ReleaseChannel :: as_str) ;
441
- let base = match & crate_data. origin {
487
+
488
+ let ( web_base, local_base) = match & crate_data. origin {
442
489
// std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
443
490
// FIXME: Use the toolchains channel instead of nightly
444
491
CrateOrigin :: Lang (
@@ -448,15 +495,17 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
448
495
| LangCrateOrigin :: Std
449
496
| LangCrateOrigin :: Test ) ,
450
497
) => {
451
- format ! ( "https://doc.rust-lang.org/{channel}/{origin}" )
498
+ let system_url = system_doc. and_then ( |it| it. join ( & format ! ( "{origin}" ) ) . ok ( ) ) ;
499
+ let web_url = format ! ( "https://doc.rust-lang.org/{channel}/{origin}" ) ;
500
+ ( Some ( web_url) , system_url)
452
501
}
453
- CrateOrigin :: Lang ( _) => return None ,
502
+ CrateOrigin :: Lang ( _) => return ( None , None ) ,
454
503
CrateOrigin :: Rustc { name : _ } => {
455
- format ! ( "https://doc.rust-lang.org/{channel}/nightly-rustc/" )
504
+ ( Some ( format ! ( "https://doc.rust-lang.org/{channel}/nightly-rustc/" ) ) , None )
456
505
}
457
506
CrateOrigin :: Local { repo : _, name : _ } => {
458
507
// FIXME: These should not attempt to link to docs.rs!
459
- krate. get_html_root_url ( db) . or_else ( || {
508
+ let weblink = krate. get_html_root_url ( db) . or_else ( || {
460
509
let version = krate. version ( db) ;
461
510
// Fallback to docs.rs. This uses `display_name` and can never be
462
511
// correct, but that's what fallbacks are about.
@@ -468,10 +517,11 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
468
517
krate = display_name,
469
518
version = version. as_deref( ) . unwrap_or( "*" )
470
519
) )
471
- } ) ?
520
+ } ) ;
521
+ ( weblink, local_doc)
472
522
}
473
523
CrateOrigin :: Library { repo : _, name } => {
474
- krate. get_html_root_url ( db) . or_else ( || {
524
+ let weblink = krate. get_html_root_url ( db) . or_else ( || {
475
525
let version = krate. version ( db) ;
476
526
// Fallback to docs.rs. This uses `display_name` and can never be
477
527
// correct, but that's what fallbacks are about.
@@ -483,10 +533,16 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
483
533
krate = name,
484
534
version = version. as_deref( ) . unwrap_or( "*" )
485
535
) )
486
- } ) ?
536
+ } ) ;
537
+ ( weblink, local_doc)
487
538
}
488
539
} ;
489
- Url :: parse ( & base) . ok ( ) ?. join ( & format ! ( "{display_name}/" ) ) . ok ( )
540
+ let web_base = web_base
541
+ . and_then ( |it| Url :: parse ( & it) . ok ( ) )
542
+ . and_then ( |it| it. join ( & format ! ( "{display_name}/" ) ) . ok ( ) ) ;
543
+ let local_base = local_base. and_then ( |it| it. join ( & format ! ( "{display_name}/" ) ) . ok ( ) ) ;
544
+
545
+ ( web_base, local_base)
490
546
}
491
547
492
548
/// Get the filename and extension generated for a symbol by rustdoc.
0 commit comments