diff options
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 83 |
1 files changed, 67 insertions, 16 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 079195184..91167e30a 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -2,7 +2,7 @@ use std::iter::once; | |||
2 | 2 | ||
3 | use hir::{ | 3 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
5 | ModuleSource, Semantics, Module, Documentation | 5 | ModuleSource, Semantics, Module, Documentation, AttrDef, Crate |
6 | }; | 6 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
@@ -13,6 +13,7 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | use ra_project_model::ProjectWorkspace; | 14 | use ra_project_model::ProjectWorkspace; |
15 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId}; | 15 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId}; |
16 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf, Subtree, SmolStr}; | ||
16 | 17 | ||
17 | use crate::{ | 18 | use crate::{ |
18 | display::{ | 19 | display::{ |
@@ -415,7 +416,7 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor | |||
415 | // If contains :: module-based link | 416 | // If contains :: module-based link |
416 | Err(_) => { | 417 | Err(_) => { |
417 | let link_str = String::from_utf8(link.url.clone()).unwrap(); | 418 | let link_str = String::from_utf8(link.url.clone()).unwrap(); |
418 | let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str) | 419 | let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) |
419 | .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); | 420 | .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); |
420 | 421 | ||
421 | if let Some(resolved) = resolved { | 422 | if let Some(resolved) = resolved { |
@@ -442,27 +443,77 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pa | |||
442 | None | 443 | None |
443 | } | 444 | } |
444 | 445 | ||
446 | enum UrlMode { | ||
447 | Url, | ||
448 | File | ||
449 | } | ||
450 | |||
445 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`) | 451 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`) |
446 | fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str) -> Option<String> { | 452 | fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> { |
447 | let ns = if let Definition::ModuleDef(moddef) = definition { | 453 | let ns = if let Definition::ModuleDef(moddef) = definition { |
448 | ItemInNs::Types(moddef.clone().into()) | 454 | ItemInNs::Types(moddef.clone().into()) |
449 | } else { | 455 | } else { |
450 | return None; | 456 | return None; |
451 | }; | 457 | }; |
452 | let krate = definition.module(db)?.krate(); | 458 | let module = definition.module(db)?; |
459 | let krate = module.krate(); | ||
453 | let import_map = db.import_map(krate.into()); | 460 | let import_map = db.import_map(krate.into()); |
454 | let base = import_map.path_of(ns).unwrap(); | 461 | // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, |
455 | let base = base.segments.iter().map(|name| format!("{}", name)).collect::<PathBuf>(); | 462 | // then hope rustdoc was run locally with `--document-private-items` |
456 | 463 | let base = import_map.path_of(ns)?; | |
457 | doc_target_dirs | 464 | let mut base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))); |
458 | .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link))) | 465 | |
459 | .inspect(|path| eprintln!("candidate {}", path.display())) | 466 | match mode { |
460 | .filter(|path| path.exists()) | 467 | UrlMode::Url => { |
461 | // slice out the UNC '\?\' added by canonicalize | 468 | let root = get_doc_url(db, &krate); |
462 | .map(|path| format!("file:///{}", path.display())) | 469 | let mut base = base.join("/"); |
463 | // \. is treated as an escape in vscode's markdown hover rendering | 470 | if let Some(url) = root { |
464 | .map(|path_str| path_str.replace("\\", "/")) | 471 | eprintln!("root: {:?} base: {:?} link: {} root&base: {} root&base&link: {}", url, &base, link, url.join(&base).unwrap(), url.join(&base).unwrap().join(link).unwrap()); |
465 | .next() | 472 | if link.starts_with("#") { |
473 | base = base + "/" | ||
474 | }; | ||
475 | Some(url.join(&base)?.join(link)?.into_string()) | ||
476 | } else {None} | ||
477 | }, | ||
478 | UrlMode::File => { | ||
479 | let base = base.collect::<PathBuf>(); | ||
480 | doc_target_dirs | ||
481 | .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link))) | ||
482 | .inspect(|path| eprintln!("candidate {}", path.display())) | ||
483 | .filter(|path| path.exists()) | ||
484 | .map(|path| format!("file:///{}", path.display())) | ||
485 | // \. is treated as an escape in vscode's markdown hover rendering | ||
486 | .map(|path_str| path_str.replace("\\", "/")) | ||
487 | .next() | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | |||
492 | /// Try to get the root URL of the documentation of a crate. | ||
493 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | ||
494 | // Look for #![doc(html_root_url = "https://docs.rs/...")] | ||
495 | let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); | ||
496 | let doc_attr_q = attrs.by_key("doc"); | ||
497 | let doc_url = if doc_attr_q.exists() { | ||
498 | doc_attr_q.tt_values().filter_map(|tt| match tt.token_trees.as_slice() { | ||
499 | &[ | ||
500 | TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident_text, ..})), | ||
501 | TokenTree::Leaf(Leaf::Punct(Punct{r#char: '=', ..})), | ||
502 | TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..})) | ||
503 | ] if ident_text == "html_root_url" => Some(text), | ||
504 | _ => { | ||
505 | None | ||
506 | } | ||
507 | }).next() | ||
508 | } else { | ||
509 | None | ||
510 | }; | ||
511 | eprintln!("url {:?}", doc_url); | ||
512 | |||
513 | // TODO: It should be possible to fallback to `format!("https://docs.rs/{}/*", crate_name, *)` | ||
514 | let url = doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()); | ||
515 | eprintln!("url {:?}", url); | ||
516 | url | ||
466 | } | 517 | } |
467 | 518 | ||
468 | fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) | 519 | fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) |