aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs88
1 files changed, 53 insertions, 35 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 8c972bb41..f7a0af037 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -17,7 +17,7 @@ use ra_ide_db::{
17 RootDatabase, 17 RootDatabase,
18}; 18};
19use ra_syntax::{ 19use ra_syntax::{
20 ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, 20 ast, ast::{Path, MacroCall}, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset,
21}; 21};
22use ra_tt::{Ident, Leaf, Literal, TokenTree}; 22use ra_tt::{Ident, Leaf, Literal, TokenTree};
23use url::Url; 23use url::Url;
@@ -394,35 +394,36 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
394// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 394// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
395fn map_links<'e>( 395fn map_links<'e>(
396 events: impl Iterator<Item = Event<'e>>, 396 events: impl Iterator<Item = Event<'e>>,
397 callback: impl Fn(&str, &str) -> String, 397 callback: impl Fn(&str, &str) -> (String, String),
398) -> impl Iterator<Item = Event<'e>> { 398) -> impl Iterator<Item = Event<'e>> {
399 let mut in_link = false; 399 let mut in_link = false;
400 let mut link_text = CowStr::Borrowed(""); 400 let mut link_target: Option<CowStr> = None;
401
401 events.map(move |evt| match evt { 402 events.map(move |evt| match evt {
402 Event::Start(Tag::Link(..)) => { 403 Event::Start(Tag::Link(_link_type, ref target, _)) => {
403 in_link = true; 404 in_link = true;
405 link_target = Some(target.clone());
404 evt 406 evt
405 } 407 }
406 Event::End(Tag::Link(link_type, target, _)) => { 408 Event::End(Tag::Link(link_type, _target, _)) => {
407 in_link = false; 409 in_link = false;
408 let target = callback(&target, &link_text); 410 Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
409 Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed("")))
410 } 411 }
411 Event::Text(s) if in_link => { 412 Event::Text(s) if in_link => {
412 link_text = s.clone(); 413 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
413 // TODO: This can unintentionally strip words from path-based links. 414 link_target = Some(CowStr::Boxed(link_target_s.into()));
414 // See std::box::Box -> std::box link as an example. 415 Event::Text(CowStr::Boxed(link_name.into()))
415 Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into()))
416 } 416 }
417 Event::Code(s) if in_link => { 417 Event::Code(s) if in_link => {
418 link_text = s.clone(); 418 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
419 Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) 419 link_target = Some(CowStr::Boxed(link_target_s.into()));
420 Event::Code(CowStr::Boxed(link_name.into()))
420 } 421 }
421 _ => evt, 422 _ => evt,
422 }) 423 })
423} 424}
424 425
425/// Rewrite documentation links in markdown to point to local documentation/docs.rs 426/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
426fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> { 427fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> {
427 let doc = Parser::new_with_broken_link_callback( 428 let doc = Parser::new_with_broken_link_callback(
428 markdown, 429 markdown,
@@ -431,21 +432,22 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) ->
431 ); 432 );
432 433
433 let doc = map_links(doc, |target, title: &str| { 434 let doc = map_links(doc, |target, title: &str| {
434 match Url::parse(target) { 435 // This check is imperfect, there's some overlap between valid intra-doc links
435 // If this is a valid absolute URL don't touch it 436 // and valid URLs so we choose to be too eager to try to resolve what might be
436 Ok(_) => target.to_string(), 437 // a URL.
437 // Otherwise there are two main possibilities 438 if target.contains("://") {
438 // path-based links: `../../module/struct.MyStruct.html` 439 (target.to_string(), title.to_string())
439 // module-based links (AKA intra-doc links): `super::super::module::MyStruct` 440 } else {
440 Err(_) => { 441 // Two posibilities:
441 let resolved = try_resolve_intra(db, definition, title, &target) 442 // * path-based links: `../../module/struct.MyStruct.html`
442 .or_else(|| try_resolve_path(db, definition, &target)); 443 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
443 444 let resolved = try_resolve_intra(db, definition, title, &target)
444 if let Some(resolved) = resolved { 445 .or_else(|| try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())));
445 resolved 446
446 } else { 447 if let Some((target, title)) = resolved {
447 target.to_string() 448 (target, title)
448 } 449 } else {
450 (target.to_string(), title.to_string())
449 } 451 }
450 } 452 }
451 }); 453 });
@@ -520,7 +522,7 @@ fn try_resolve_intra(
520 definition: &Definition, 522 definition: &Definition,
521 link_text: &str, 523 link_text: &str,
522 link_target: &str, 524 link_target: &str,
523) -> Option<String> { 525) -> Option<(String, String)> {
524 // Set link_target for implied shortlinks 526 // Set link_target for implied shortlinks
525 let link_target = 527 let link_target =
526 if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; 528 if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };
@@ -534,6 +536,7 @@ fn try_resolve_intra(
534 // Parse link as a module path 536 // Parse link as a module path
535 // This expects a full document, which a single path isn't, but we can just ignore the errors. 537 // This expects a full document, which a single path isn't, but we can just ignore the errors.
536 let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0); 538 let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0);
539 // TODO: Proper parsing
537 let path = parsed.descendants().filter_map(Path::cast).next()?; 540 let path = parsed.descendants().filter_map(Path::cast).next()?;
538 let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); 541 let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
539 542
@@ -566,7 +569,7 @@ fn try_resolve_intra(
566 let import_map = db.import_map(krate.into()); 569 let import_map = db.import_map(krate.into());
567 let path = import_map.path_of(ns)?; 570 let path = import_map.path_of(ns)?;
568 571
569 Some( 572 Some((
570 get_doc_url(db, &krate)? 573 get_doc_url(db, &krate)?
571 .join(&format!("{}/", krate.display_name(db)?)) 574 .join(&format!("{}/", krate.display_name(db)?))
572 .ok()? 575 .ok()?
@@ -575,7 +578,7 @@ fn try_resolve_intra(
575 .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) 578 .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
576 .ok()? 579 .ok()?
577 .into_string(), 580 .into_string(),
578 ) 581 strip_prefixes_suffixes(link_text).to_string()))
579} 582}
580 583
581/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). 584/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
@@ -1485,15 +1488,30 @@ fn func(foo: i32) { if true { <|>foo; }; }
1485 } 1488 }
1486 1489
1487 #[test] 1490 #[test]
1488 fn test_hover_intra_link() { 1491 fn test_hover_path_link_no_strip() {
1489 check_hover_result( 1492 check_hover_result(
1490 r" 1493 r"
1491 //- /lib.rs 1494 //- /lib.rs
1492 pub struct Foo; 1495 pub struct Foo;
1493 /// [Foo](Foo) 1496 /// [struct Foo](struct.Foo.html)
1494 pub struct B<|>ar 1497 pub struct B<|>ar
1495 ", 1498 ",
1496 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], 1499 &["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1500 );
1501 }
1502
1503 #[test]
1504 fn test_hover_intra_link() {
1505 check_hover_result(
1506 r"
1507 //- /lib.rs
1508 pub mod foo {
1509 pub struct Foo;
1510 }
1511 /// [Foo](foo::Foo)
1512 pub struct B<|>ar
1513 ",
1514 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"],
1497 ); 1515 );
1498 } 1516 }
1499 1517