From 5f52a516dedeab16ede8c26807c4ff79b3d308d3 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Fri, 12 Jun 2020 15:02:48 +1200 Subject: Working intra-doc-links --- crates/ra_ide/Cargo.toml | 2 + crates/ra_ide/src/hover.rs | 119 ++++++++++++++++++++++++++++++++++++++-- crates/ra_syntax/src/lib.rs | 2 +- crates/ra_syntax/src/parsing.rs | 2 +- 4 files changed, 117 insertions(+), 8 deletions(-) (limited to 'crates') 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" rand = { version = "0.7.3", features = ["small_rng"] } comrak = "0.7.0" url = "*" +maplit = "*" stdx = { path = "../stdx" } @@ -36,6 +37,7 @@ ra_project_model = { path = "../ra_project_model" } ra_hir_def = { path = "../ra_hir_def" } ra_tt = { path = "../ra_tt" } ra_hir_expand = { path = "../ra_hir_expand" } +ra_parser = { path = "../ra_parser" } # ra_ide should depend only on the top-level `hir` package. if you need # 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; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Documentation, AttrDef, Crate + ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -12,11 +12,13 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; use ra_project_model::ProjectWorkspace; -use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; +use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver}; use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; use ra_hir_expand::name::AsName; +use ra_parser::FragmentKind; +use maplit::{hashset, hashmap}; use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; use comrak::nodes::NodeValue; @@ -412,8 +414,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor // module-based links (AKA intra-doc links): `super::super::module::MyStruct` Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); + let link_text = String::from_utf8(link.title.clone()).unwrap(); let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) - .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); + .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_text, &link_str)); if let Some(resolved) = resolved { link.url = resolved.as_bytes().to_vec(); @@ -430,11 +433,113 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor Some(String::from_utf8(out).unwrap()) } +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +enum Namespace { + Types, + Values, + Macros +} + +impl Namespace { + /// Extract the specified namespace from an intra-doc-link if one exists. + fn from_intra_spec(s: &str) -> Option { + let ns_map = hashmap!{ + Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), + Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), + Self::Macros => (hashset!{"macro"}, hashset!{"!"}) + }; + + ns_map + .iter() + .filter(|(ns, (prefixes, suffixes))| { + prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || + suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) + }) + .map(|(ns, (_, _))| *ns) + .next() + } +} + /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). /// /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { - None +fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link_text: &str, link_target: &str) -> Option { + eprintln!("try_resolve_intra"); + + // Set link_target for implied shortlinks + let link_target = if link_target.is_empty() { + link_text.trim_matches('`') + } else { + link_target + }; + + // Parse link as a module path + // This expects a full document, which a single path isn't, but we can just ignore the errors. + let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0); + let path = parsed.descendants().filter_map(Path::cast).next()?; + let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); + + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes + let resolver = { + use ra_hir_def::*; + use hir::*; + + // TODO: This should be replaced by implementing HasResolver/TryHasResolver on ModuleDef and Definition. + match definition { + Definition::ModuleDef(def) => match def { + ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), + ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), + ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), + ModuleDef::EnumVariant(ev) => Into::::into(Into::::into(ev.clone())).resolver(db), + ModuleDef::Const(c) => Into::::into(Into::::into(c.clone())).resolver(db), + ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), + ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), + ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), + // TODO: This should be a resolver relative to `std` + ModuleDef::BuiltinType(t) => Into::::into(definition.module(db)?).resolver(db) + }, + Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), + Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), + Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), + // it's possible, read probable, that other arms of this are also unreachable + Definition::Local(local) => unreachable!(), + Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) + } + }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + let resolved = resolver.resolve_module_path_in_items(db, &modpath); + let (defid, namespace) = match namespace { + // TODO: .or(resolved.macros) + None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + Some(ns @ Namespace::Types) => (resolved.types?.0, ns), + Some(ns @ Namespace::Values) => (resolved.values?.0, ns), + // TODO: + Some(Namespace::Macros) => None? + }; + + // Get the filepath of the final symbol + let def: ModuleDef = defid.into(); + let module = def.module(db)?; + let krate = module.krate(); + let ns = match namespace { + Namespace::Types => ItemInNs::Types(defid), + Namespace::Values => ItemInNs::Values(defid), + // TODO: + Namespace::Macros => None? + }; + let import_map = db.import_map(krate.into()); + let path = import_map.path_of(ns)?; + + Some( + get_doc_url(db, &krate)? + .join(&format!("{}/", krate.display_name(db)?)).ok()? + .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()? + .join(&get_symbol_filename(db, definition)?).ok()? + .into_string() + ) } enum UrlMode { @@ -444,6 +549,7 @@ enum UrlMode { /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str, mode: UrlMode) -> Option { + eprintln!("try_resolve_path"); let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { @@ -481,6 +587,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index 9b7664576..7a2c44cd2 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -47,7 +47,7 @@ use crate::syntax_node::GreenNode; pub use crate::{ algo::InsertPosition, ast::{AstNode, AstToken}, - parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token}, + parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token, parse_text}, ptr::{AstPtr, SyntaxNodePtr}, syntax_error::SyntaxError, syntax_node::{ diff --git a/crates/ra_syntax/src/parsing.rs b/crates/ra_syntax/src/parsing.rs index 0ed3c20ef..4ec0b8d59 100644 --- a/crates/ra_syntax/src/parsing.rs +++ b/crates/ra_syntax/src/parsing.rs @@ -15,7 +15,7 @@ pub use lexer::*; pub(crate) use self::reparsing::incremental_reparse; use ra_parser::SyntaxKind; -pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec) { +pub fn parse_text(text: &str) -> (GreenNode, Vec) { let (tokens, lexer_errors) = tokenize(&text); let mut token_source = TextTokenSource::new(text, &tokens); -- cgit v1.2.3