diff --git a/Cargo.lock b/Cargo.lock index 0c1d2e8b9fe..c0a9494e514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,17 +51,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.9", -] - [[package]] name = "autocfg" version = "1.0.0" @@ -94,27 +83,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - [[package]] name = "bstr" version = "0.2.13" @@ -124,12 +92,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.3.4" @@ -222,21 +184,6 @@ dependencies = [ "time", ] -[[package]] -name = "clap" -version = "2.33.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "cloudabi" version = "0.1.0" @@ -246,23 +193,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "comrak" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c" -dependencies = [ - "clap", - "entities", - "lazy_static", - "pest", - "pest_derive", - "regex", - "twoway", - "typed-arena", - "unicode_categories", -] - [[package]] name = "console" version = "0.11.3" @@ -355,15 +285,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - [[package]] name = "drop_bomb" version = "0.1.4" @@ -397,12 +318,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "entities" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" - [[package]] name = "env_logger" version = "0.7.1" @@ -412,12 +327,6 @@ dependencies = [ "log", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "filetime" version = "0.2.10" @@ -502,12 +411,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] -name = "generic-array" -version = "0.12.3" +name = "getopts" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "typenum", + "unicode-width", ] [[package]] @@ -963,12 +872,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "parking_lot" version = "0.11.0" @@ -1024,49 +927,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - [[package]] name = "petgraph" version = "0.5.1" @@ -1110,6 +970,27 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "pulldown-cmark" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e142c3b8f49d2200605ee6ba0b1d757310e9e7a72afe78c36ee2ef67300ee00" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb594e453d29e238ac190362a4a291daec00396717a8d1670863121ac56958" +dependencies = [ + "pulldown-cmark", +] + [[package]] name = "quote" version = "1.0.7" @@ -1264,20 +1145,21 @@ dependencies = [ name = "ra_ide" version = "0.1.0" dependencies = [ - "comrak", "either", "indexmap", "insta", "itertools", + "lazy_static", "log", "maplit", + "pulldown-cmark", + "pulldown-cmark-to-cmark", "ra_assists", "ra_cfg", "ra_db", "ra_fmt", "ra_hir", "ra_hir_def", - "ra_hir_expand", "ra_ide_db", "ra_parser", "ra_prof", @@ -1793,18 +1675,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - [[package]] name = "sharded-slab" version = "0.0.9" @@ -1839,12 +1709,6 @@ dependencies = [ name = "stdx" version = "0.1.0" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "superslice" version = "1.0.0" @@ -1924,15 +1788,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f03e7efdedc3bc78cb2337f1e2785c39e45f5ef762d9e4ebb137fff7380a6d8a" -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thin-dst" version = "1.1.0" @@ -2046,39 +1901,14 @@ dependencies = [ ] [[package]] -name = "twoway" -version = "0.2.1" +name = "unicase" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "memchr", - "unchecked-index", + "version_check", ] -[[package]] -name = "typed-arena" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" - -[[package]] -name = "typenum" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - -[[package]] -name = "unchecked-index" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - [[package]] name = "unicode-bidi" version = "0.3.4" @@ -2115,12 +1945,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "url" version = "2.1.1" @@ -2134,10 +1958,10 @@ dependencies = [ ] [[package]] -name = "vec_map" -version = "0.8.2" +name = "version_check" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "vfs" diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 3364a822f43..fe34b30bc80 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -66,12 +66,14 @@ fn from(it: $sv) -> $e { body::scope::ExprScopes, builtin_type::BuiltinType, docs::Documentation, + item_scope::ItemInNs, nameres::ModuleSource, path::{ModPath, Path, PathKind}, type_ref::Mutability, }; pub use hir_expand::{ - hygiene::Hygiene, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, - MacroFile, Origin, + hygiene::Hygiene, + name::{AsName, Name}, + HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, Origin, }; pub use hir_ty::{display::HirDisplay, CallableDef}; diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 1bc095c5b89..642b71937af 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -17,9 +17,11 @@ itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" rand = { version = "0.7.3", features = ["small_rng"] } -comrak = "0.7.0" url = "*" maplit = "*" +lazy_static = "*" +pulldown-cmark-to-cmark = "4.0.2" +pulldown-cmark = "0.7.0" stdx = { path = "../stdx" } @@ -36,7 +38,6 @@ ra_ssr = { path = "../ra_ssr" } 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 diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 34fc36a1fcd..82aa24f4f4f 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,23 +1,25 @@ +use std::collections::{HashMap, HashSet}; use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Documentation, AttrDef, Crate, ModPath, Hygiene + db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, + FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, ModuleDef, ModuleSource, + Semantics, Module }; use itertools::Itertools; +use lazy_static::lazy_static; +use maplit::{hashmap, hashset}; +use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; +use pulldown_cmark_to_cmark::cmark; use ra_db::SourceDatabase; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; -use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, resolver::HasResolver}; -use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; -use ra_hir_expand::name::AsName; -use maplit::{hashset, hashmap}; - -use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; -use comrak::nodes::NodeValue; +use ra_syntax::{ + ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, +}; +use ra_tt::{Ident, Leaf, Literal, Punct, TokenTree}; use url::Url; use crate::{ @@ -389,80 +391,143 @@ fn from_def_source(db: &RootDatabase, def: D, mod_path: Option) -> } } +// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. +fn map_links<'e>( + events: impl Iterator>, + callback: impl Fn(&str, &str) -> String, +) -> impl Iterator> { + let mut in_link = false; + let mut link_text = CowStr::Borrowed(""); + events.map(move |evt| match evt { + Event::Start(Tag::Link(..)) => { + in_link = true; + evt + } + Event::End(Tag::Link(link_type, target, _)) => { + in_link = false; + let target = callback(&target, &link_text); + Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed(""))) + } + Event::Text(s) if in_link => { + link_text = s.clone(); + Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + } + Event::Code(s) if in_link => { + link_text = s.clone(); + Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + } + _ => evt, + }) +} + /// Rewrite documentation links in markdown to point to local documentation/docs.rs fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option { - let arena = Arena::new(); - let doc = parse_document(&arena, markdown, &ComrakOptions::default()); + let doc = Parser::new_with_broken_link_callback( + markdown, + Options::empty(), + Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), + ); - iter_nodes(doc, &|node| { - match &mut node.data.borrow_mut().value { - &mut NodeValue::Link(ref mut link) => { - match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { - // If this is a valid absolute URL don't touch it - Ok(_) => (), - // Otherwise there are two main possibilities - // path-based links: `../../module/struct.MyStruct.html` - // 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_intra(db, definition, &link_text, &link_str) - .or_else(|| try_resolve_path(db, definition, &link_str)); + let doc = map_links(doc, |target, title: &str| { + match Url::parse(target) { + // If this is a valid absolute URL don't touch it + Ok(_) => target.to_string(), + // Otherwise there are two main possibilities + // path-based links: `../../module/struct.MyStruct.html` + // module-based links (AKA intra-doc links): `super::super::module::MyStruct` + Err(_) => { + let resolved = try_resolve_intra(db, definition, title, &target) + .or_else(|| try_resolve_path(db, definition, &target)); - if let Some(resolved) = resolved { - link.url = resolved.as_bytes().to_vec(); - } - - } + if let Some(resolved) = resolved { + resolved + } else { + target.to_string() } - }, - _ => () + } } }); - let mut out = Vec::new(); - format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; - Some(String::from_utf8(out).unwrap().trim().to_string()) + let mut out = String::new(); + cmark(doc, &mut out, None).ok(); + Some(out) } #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] enum Namespace { Types, Values, - Macros + Macros, } +lazy_static!( + /// Map of namespaces to identifying prefixes and suffixes as defined by RFC1946. + static ref NS_MAP: HashMap, HashSet<&'static str>)> = hashmap!{ + Namespace::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), + Namespace::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), + Namespace::Macros => (hashset!{"macro"}, hashset!{"!"}) + }; +); + 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 + 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) + 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() } } +// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| { + prefixes.iter().for_each(|prefix| s = s.trim_start_matches(prefix)); + suffixes.iter().for_each(|suffix| s = s.trim_end_matches(suffix)); + }); + s.trim_start_matches("@").trim() +} + /// 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, definition: &Definition, link_text: &str, link_target: &str) -> Option { - eprintln!("try_resolve_intra"); - +fn try_resolve_intra( + db: &RootDatabase, + definition: &Definition, + link_text: &str, + link_target: &str, +) -> Option { // Set link_target for implied shortlinks - let link_target = if link_target.is_empty() { - link_text.trim_matches('`') - } else { - link_target - }; + let link_target = + if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + // Strip prefixes/suffixes + let link_target = strip_prefixes_suffixes(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. @@ -473,17 +538,17 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str // Resolve it relative to symbol's location (according to the RFC this should consider small scopes let resolver = definition.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)))?, + 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? + Some(Namespace::Macros) => None?, }; // Get the filepath of the final symbol @@ -494,23 +559,28 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str Namespace::Types => ItemInNs::Types(defid), Namespace::Values => ItemInNs::Values(defid), // TODO: - Namespace::Macros => None? + 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::ModuleDef(def))?).ok()? - .into_string() + .join(&format!("{}/", krate.display_name(db)?)) + .ok()? + .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")) + .ok()? + .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) + .ok()? + .into_string(), ) } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { - eprintln!("try_resolve_path"); + if !link.contains("#") && !link.contains(".html") { + return None; + } let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { @@ -522,11 +592,15 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, // then hope rustdoc was run locally with `--document-private-items` let base = import_map.path_of(ns)?; - let base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))).join("/"); + let base = once(format!("{}", krate.display_name(db)?)) + .chain(base.segments.iter().map(|name| format!("{}", name))) + .join("/"); get_doc_url(db, &krate) .and_then(|url| url.join(&base).ok()) - .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) + .and_then(|url| { + get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() + }) .and_then(|url| url.join(link).ok()) .map(|url| url.into_string()) } @@ -566,31 +640,25 @@ fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option match adt { Adt::Struct(s) => format!("struct.{}.html", s.name(db)), Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)) + Adt::Union(u) => format!("union.{}.html", u.name(db)), }, ModuleDef::Module(_) => "index.html".to_string(), ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::EnumVariant(ev) => format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)), + ModuleDef::EnumVariant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), // TODO: Check this is the right prefix - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?) + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), }, Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), - _ => None? + _ => None?, }) } -fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) - where F : Fn(&'a comrak::nodes::AstNode<'a>) { - f(node); - for c in node.children() { - iter_nodes(c, f); - } -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { @@ -614,12 +682,11 @@ mod tests { use crate::mock_analysis::analysis_and_position; fn trim_markup(s: &str) -> String { - s - .replace("``` rust", "```rust") - .replace("-----", "___") - .replace("\n\n___\n\n", "\n___\n\n") + s.trim() + .replace("````", "```") + .replace("---", "___") .replace("\\<-", "<-") - .trim_start_matches("test\n```\n\n") + .replace("```\n\n___", "```\n___") .trim_start_matches("```rust\n") .trim_start_matches("test\n```\n\n```rust\n") .trim_end_matches("\n```") @@ -873,7 +940,10 @@ fn main() { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("test::Option\n```\n\n```rust\nSome")); + assert_eq!( + trim_markup_opt(hover.info.first()).as_deref(), + Some("test::Option\n```\n\n```rust\nSome") + ); let (analysis, position) = analysis_and_position( " @@ -1408,7 +1478,7 @@ fn test_hover_path_link() { /// [Foo](struct.Foo.html) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1421,7 +1491,7 @@ fn test_hover_intra_link() { /// [Foo](Foo) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1434,7 +1504,20 @@ fn test_hover_intra_link_shortlink() { /// [Foo] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_code() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1448,7 +1531,75 @@ fn Foo() {} /// [Foo()] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_namspaced_code() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`struct Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_namspaced_code_with_at() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`struct@Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_reference() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [my Foo][foo] + /// + /// [foo]: Foo + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_external_url() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [external](https://www.google.com) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"], + ); + } + + // Check that we don't rewrite links which we can't identify + #[test] + fn test_hover_unknown_target() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [baz](Baz) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[baz](Baz)"], ); } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6c0d3e19cfe..c8c8b886d7d 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -547,7 +547,7 @@ pub(crate) fn handle_hover( ) -> Result> { let _p = profile("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis().hover(position)? { + let info = match snap.analysis.hover(position)? { None => return Ok(None), Some(info) => info, };