diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-06-12 04:02:48 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-06-30 09:02:47 +0100 |
commit | 5f52a516dedeab16ede8c26807c4ff79b3d308d3 (patch) | |
tree | a3a061ee2b2c5ceadae741d2c695fcebb25f16e9 /crates/ra_ide | |
parent | 8f56e7c3b1220ed0b065e97a9061e59284ac1df1 (diff) |
Working intra-doc-links
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/hover.rs | 119 |
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" | |||
19 | rand = { version = "0.7.3", features = ["small_rng"] } | 19 | rand = { version = "0.7.3", features = ["small_rng"] } |
20 | comrak = "0.7.0" | 20 | comrak = "0.7.0" |
21 | url = "*" | 21 | url = "*" |
22 | maplit = "*" | ||
22 | 23 | ||
23 | stdx = { path = "../stdx" } | 24 | stdx = { path = "../stdx" } |
24 | 25 | ||
@@ -36,6 +37,7 @@ ra_project_model = { path = "../ra_project_model" } | |||
36 | ra_hir_def = { path = "../ra_hir_def" } | 37 | ra_hir_def = { path = "../ra_hir_def" } |
37 | ra_tt = { path = "../ra_tt" } | 38 | ra_tt = { path = "../ra_tt" } |
38 | ra_hir_expand = { path = "../ra_hir_expand" } | 39 | ra_hir_expand = { path = "../ra_hir_expand" } |
40 | ra_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 | ||
5 | use hir::{ | 5 | use 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 | }; |
9 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use ra_db::SourceDatabase; | 10 | use 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 | }; |
15 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 15 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; |
16 | use ra_project_model::ProjectWorkspace; | 16 | use ra_project_model::ProjectWorkspace; |
17 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; | 17 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver}; |
18 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; | 18 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; |
19 | use ra_hir_expand::name::AsName; | 19 | use ra_hir_expand::name::AsName; |
20 | use ra_parser::FragmentKind; | ||
21 | use maplit::{hashset, hashmap}; | ||
20 | 22 | ||
21 | use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; | 23 | use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; |
22 | use comrak::nodes::NodeValue; | 24 | use 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)] | ||
437 | enum Namespace { | ||
438 | Types, | ||
439 | Values, | ||
440 | Macros | ||
441 | } | ||
442 | |||
443 | impl 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). |
436 | fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str) -> Option<String> { | 466 | fn 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 | ||
440 | enum UrlMode { | 545 | enum 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`). |
446 | fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> { | 551 | fn 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 | ||
484 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | 591 | fn 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()); |