aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-06-12 04:02:48 +0100
committerZac Pullar-Strecker <[email protected]>2020-06-30 09:02:47 +0100
commit5f52a516dedeab16ede8c26807c4ff79b3d308d3 (patch)
treea3a061ee2b2c5ceadae741d2c695fcebb25f16e9 /crates/ra_ide
parent8f56e7c3b1220ed0b065e97a9061e59284ac1df1 (diff)
Working intra-doc-links
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml2
-rw-r--r--crates/ra_ide/src/hover.rs119
2 files changed, 115 insertions, 6 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 219ad33e6..1bc095c5b 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -19,6 +19,7 @@ rustc-hash = "1.1.0"
19rand = { version = "0.7.3", features = ["small_rng"] } 19rand = { version = "0.7.3", features = ["small_rng"] }
20comrak = "0.7.0" 20comrak = "0.7.0"
21url = "*" 21url = "*"
22maplit = "*"
22 23
23stdx = { path = "../stdx" } 24stdx = { path = "../stdx" }
24 25
@@ -36,6 +37,7 @@ ra_project_model = { path = "../ra_project_model" }
36ra_hir_def = { path = "../ra_hir_def" } 37ra_hir_def = { path = "../ra_hir_def" }
37ra_tt = { path = "../ra_tt" } 38ra_tt = { path = "../ra_tt" }
38ra_hir_expand = { path = "../ra_hir_expand" } 39ra_hir_expand = { path = "../ra_hir_expand" }
40ra_parser = { path = "../ra_parser" }
39 41
40# ra_ide should depend only on the top-level `hir` package. if you need 42# ra_ide should depend only on the top-level `hir` package. if you need
41# something from some `hir_xxx` subpackage, reexport the API via `hir`. 43# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 760d7fe14..f4b10deac 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -4,7 +4,7 @@ use std::sync::Arc;
4 4
5use hir::{ 5use hir::{
6 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 6 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
7 ModuleSource, Semantics, Documentation, AttrDef, Crate 7 ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene
8}; 8};
9use itertools::Itertools; 9use itertools::Itertools;
10use ra_db::SourceDatabase; 10use ra_db::SourceDatabase;
@@ -12,11 +12,13 @@ use ra_ide_db::{
12 defs::{classify_name, classify_name_ref, Definition}, 12 defs::{classify_name, classify_name_ref, Definition},
13 RootDatabase, 13 RootDatabase,
14}; 14};
15use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 15use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path};
16use ra_project_model::ProjectWorkspace; 16use ra_project_model::ProjectWorkspace;
17use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; 17use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver};
18use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; 18use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf};
19use ra_hir_expand::name::AsName; 19use ra_hir_expand::name::AsName;
20use ra_parser::FragmentKind;
21use maplit::{hashset, hashmap};
20 22
21use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; 23use comrak::{parse_document,format_commonmark, ComrakOptions, Arena};
22use comrak::nodes::NodeValue; 24use comrak::nodes::NodeValue;
@@ -412,8 +414,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor
412 // module-based links (AKA intra-doc links): `super::super::module::MyStruct` 414 // module-based links (AKA intra-doc links): `super::super::module::MyStruct`
413 Err(_) => { 415 Err(_) => {
414 let link_str = String::from_utf8(link.url.clone()).unwrap(); 416 let link_str = String::from_utf8(link.url.clone()).unwrap();
417 let link_text = String::from_utf8(link.title.clone()).unwrap();
415 let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) 418 let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url)
416 .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); 419 .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_text, &link_str));
417 420
418 if let Some(resolved) = resolved { 421 if let Some(resolved) = resolved {
419 link.url = resolved.as_bytes().to_vec(); 422 link.url = resolved.as_bytes().to_vec();
@@ -430,11 +433,113 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor
430 Some(String::from_utf8(out).unwrap()) 433 Some(String::from_utf8(out).unwrap())
431} 434}
432 435
436#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
437enum Namespace {
438 Types,
439 Values,
440 Macros
441}
442
443impl Namespace {
444 /// Extract the specified namespace from an intra-doc-link if one exists.
445 fn from_intra_spec(s: &str) -> Option<Self> {
446 let ns_map = hashmap!{
447 Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}),
448 Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}),
449 Self::Macros => (hashset!{"macro"}, hashset!{"!"})
450 };
451
452 ns_map
453 .iter()
454 .filter(|(ns, (prefixes, suffixes))| {
455 prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) ||
456 suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond)
457 })
458 .map(|(ns, (_, _))| *ns)
459 .next()
460 }
461}
462
433/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). 463/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
434/// 464///
435/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). 465/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
436fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str) -> Option<String> { 466fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link_text: &str, link_target: &str) -> Option<String> {
437 None 467 eprintln!("try_resolve_intra");
468
469 // Set link_target for implied shortlinks
470 let link_target = if link_target.is_empty() {
471 link_text.trim_matches('`')
472 } else {
473 link_target
474 };
475
476 // Parse link as a module path
477 // This expects a full document, which a single path isn't, but we can just ignore the errors.
478 let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0);
479 let path = parsed.descendants().filter_map(Path::cast).next()?;
480 let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
481
482 // Resolve it relative to symbol's location (according to the RFC this should consider small scopes
483 let resolver = {
484 use ra_hir_def::*;
485 use hir::*;
486
487 // TODO: This should be replaced by implementing HasResolver/TryHasResolver on ModuleDef and Definition.
488 match definition {
489 Definition::ModuleDef(def) => match def {
490 ModuleDef::Module(m) => Into::<ModuleId>::into(m.clone()).resolver(db),
491 ModuleDef::Function(f) => Into::<FunctionId>::into(f.clone()).resolver(db),
492 ModuleDef::Adt(adt) => Into::<AdtId>::into(adt.clone()).resolver(db),
493 ModuleDef::EnumVariant(ev) => Into::<GenericDefId>::into(Into::<GenericDef>::into(ev.clone())).resolver(db),
494 ModuleDef::Const(c) => Into::<GenericDefId>::into(Into::<GenericDef>::into(c.clone())).resolver(db),
495 ModuleDef::Static(s) => Into::<StaticId>::into(s.clone()).resolver(db),
496 ModuleDef::Trait(t) => Into::<TraitId>::into(t.clone()).resolver(db),
497 ModuleDef::TypeAlias(t) => Into::<ModuleId>::into(t.module(db)).resolver(db),
498 // TODO: This should be a resolver relative to `std`
499 ModuleDef::BuiltinType(t) => Into::<ModuleId>::into(definition.module(db)?).resolver(db)
500 },
501 Definition::Field(field) => Into::<VariantId>::into(Into::<VariantDef>::into(field.parent_def(db))).resolver(db),
502 Definition::Macro(m) => Into::<ModuleId>::into(m.module(db)?).resolver(db),
503 Definition::SelfType(imp) => Into::<ImplId>::into(imp.clone()).resolver(db),
504 // it's possible, read probable, that other arms of this are also unreachable
505 Definition::Local(local) => unreachable!(),
506 Definition::TypeParam(tp) => Into::<ModuleId>::into(tp.module(db)).resolver(db)
507 }
508 };
509
510 // Namespace disambiguation
511 let namespace = Namespace::from_intra_spec(link_target);
512
513 let resolved = resolver.resolve_module_path_in_items(db, &modpath);
514 let (defid, namespace) = match namespace {
515 // TODO: .or(resolved.macros)
516 None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
517 Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
518 Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
519 // TODO:
520 Some(Namespace::Macros) => None?
521 };
522
523 // Get the filepath of the final symbol
524 let def: ModuleDef = defid.into();
525 let module = def.module(db)?;
526 let krate = module.krate();
527 let ns = match namespace {
528 Namespace::Types => ItemInNs::Types(defid),
529 Namespace::Values => ItemInNs::Values(defid),
530 // TODO:
531 Namespace::Macros => None?
532 };
533 let import_map = db.import_map(krate.into());
534 let path = import_map.path_of(ns)?;
535
536 Some(
537 get_doc_url(db, &krate)?
538 .join(&format!("{}/", krate.display_name(db)?)).ok()?
539 .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()?
540 .join(&get_symbol_filename(db, definition)?).ok()?
541 .into_string()
542 )
438} 543}
439 544
440enum UrlMode { 545enum UrlMode {
@@ -444,6 +549,7 @@ enum UrlMode {
444 549
445/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). 550/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
446fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> { 551fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> {
552 eprintln!("try_resolve_path");
447 let ns = if let Definition::ModuleDef(moddef) = definition { 553 let ns = if let Definition::ModuleDef(moddef) = definition {
448 ItemInNs::Types(moddef.clone().into()) 554 ItemInNs::Types(moddef.clone().into())
449 } else { 555 } else {
@@ -481,6 +587,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pat
481} 587}
482 588
483/// Try to get the root URL of the documentation of a crate. 589/// Try to get the root URL of the documentation of a crate.
590// TODO: Special case standard, core, alloc libraries
484fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { 591fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
485 // Look for #![doc(html_root_url = "...")] 592 // Look for #![doc(html_root_url = "...")]
486 let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); 593 let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());