aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock141
-rw-r--r--crates/ra_hir/src/code_model.rs20
-rw-r--r--crates/ra_hir/src/lib.rs6
-rw-r--r--crates/ra_ide/Cargo.toml9
-rw-r--r--crates/ra_ide/src/hover.rs439
-rw-r--r--crates/ra_ide/src/mock_analysis.rs2
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/defs.rs17
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs2
9 files changed, 628 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 234c31406..572fb868e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -400,6 +400,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
400checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 400checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
401 401
402[[package]] 402[[package]]
403name = "getopts"
404version = "0.2.21"
405source = "registry+https://github.com/rust-lang/crates.io-index"
406checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
407dependencies = [
408 "unicode-width",
409]
410
411[[package]]
412name = "getrandom"
413version = "0.1.14"
414source = "registry+https://github.com/rust-lang/crates.io-index"
415checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
416dependencies = [
417 "cfg-if",
418 "libc",
419 "wasi",
420]
421
422[[package]]
403name = "gimli" 423name = "gimli"
404version = "0.22.0" 424version = "0.22.0"
405source = "registry+https://github.com/rust-lang/crates.io-index" 425source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -620,6 +640,12 @@ dependencies = [
620] 640]
621 641
622[[package]] 642[[package]]
643name = "maplit"
644version = "1.0.2"
645source = "registry+https://github.com/rust-lang/crates.io-index"
646checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
647
648[[package]]
623name = "matchers" 649name = "matchers"
624version = "0.0.1" 650version = "0.0.1"
625source = "registry+https://github.com/rust-lang/crates.io-index" 651source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -882,6 +908,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
882checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 908checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
883 909
884[[package]] 910[[package]]
911name = "ppv-lite86"
912version = "0.2.8"
913source = "registry+https://github.com/rust-lang/crates.io-index"
914checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
915
916[[package]]
885name = "proc-macro2" 917name = "proc-macro2"
886version = "1.0.19" 918version = "1.0.19"
887source = "registry+https://github.com/rust-lang/crates.io-index" 919source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -891,6 +923,27 @@ dependencies = [
891] 923]
892 924
893[[package]] 925[[package]]
926name = "pulldown-cmark"
927version = "0.7.2"
928source = "registry+https://github.com/rust-lang/crates.io-index"
929checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55"
930dependencies = [
931 "bitflags",
932 "getopts",
933 "memchr",
934 "unicase",
935]
936
937[[package]]
938name = "pulldown-cmark-to-cmark"
939version = "4.0.2"
940source = "registry+https://github.com/rust-lang/crates.io-index"
941checksum = "cffb594e453d29e238ac190362a4a291daec00396717a8d1670863121ac56958"
942dependencies = [
943 "pulldown-cmark",
944]
945
946[[package]]
894name = "quote" 947name = "quote"
895version = "1.0.7" 948version = "1.0.7"
896source = "registry+https://github.com/rust-lang/crates.io-index" 949source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1052,21 +1105,30 @@ dependencies = [
1052 "expect", 1105 "expect",
1053 "indexmap", 1106 "indexmap",
1054 "itertools", 1107 "itertools",
1108 "lazy_static",
1055 "log", 1109 "log",
1110 "maplit",
1056 "oorandom", 1111 "oorandom",
1112 "pulldown-cmark",
1113 "pulldown-cmark-to-cmark",
1057 "ra_assists", 1114 "ra_assists",
1058 "ra_cfg", 1115 "ra_cfg",
1059 "ra_db", 1116 "ra_db",
1060 "ra_fmt", 1117 "ra_fmt",
1061 "ra_hir", 1118 "ra_hir",
1119 "ra_hir_def",
1062 "ra_ide_db", 1120 "ra_ide_db",
1121 "ra_parser",
1063 "ra_prof", 1122 "ra_prof",
1064 "ra_ssr", 1123 "ra_ssr",
1065 "ra_syntax", 1124 "ra_syntax",
1066 "ra_text_edit", 1125 "ra_text_edit",
1126 "ra_tt",
1127 "rand",
1067 "rustc-hash", 1128 "rustc-hash",
1068 "stdx", 1129 "stdx",
1069 "test_utils", 1130 "test_utils",
1131 "url",
1070] 1132]
1071 1133
1072[[package]] 1134[[package]]
@@ -1079,6 +1141,7 @@ dependencies = [
1079 "once_cell", 1141 "once_cell",
1080 "ra_db", 1142 "ra_db",
1081 "ra_hir", 1143 "ra_hir",
1144 "ra_hir_def",
1082 "ra_prof", 1145 "ra_prof",
1083 "ra_syntax", 1146 "ra_syntax",
1084 "ra_text_edit", 1147 "ra_text_edit",
@@ -1226,6 +1289,57 @@ dependencies = [
1226] 1289]
1227 1290
1228[[package]] 1291[[package]]
1292name = "rand"
1293version = "0.7.3"
1294source = "registry+https://github.com/rust-lang/crates.io-index"
1295checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
1296dependencies = [
1297 "getrandom",
1298 "libc",
1299 "rand_chacha",
1300 "rand_core",
1301 "rand_hc",
1302 "rand_pcg",
1303]
1304
1305[[package]]
1306name = "rand_chacha"
1307version = "0.2.2"
1308source = "registry+https://github.com/rust-lang/crates.io-index"
1309checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
1310dependencies = [
1311 "ppv-lite86",
1312 "rand_core",
1313]
1314
1315[[package]]
1316name = "rand_core"
1317version = "0.5.1"
1318source = "registry+https://github.com/rust-lang/crates.io-index"
1319checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
1320dependencies = [
1321 "getrandom",
1322]
1323
1324[[package]]
1325name = "rand_hc"
1326version = "0.2.0"
1327source = "registry+https://github.com/rust-lang/crates.io-index"
1328checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1329dependencies = [
1330 "rand_core",
1331]
1332
1333[[package]]
1334name = "rand_pcg"
1335version = "0.2.1"
1336source = "registry+https://github.com/rust-lang/crates.io-index"
1337checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
1338dependencies = [
1339 "rand_core",
1340]
1341
1342[[package]]
1229name = "rayon" 1343name = "rayon"
1230version = "1.3.1" 1344version = "1.3.1"
1231source = "registry+https://github.com/rust-lang/crates.io-index" 1345source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1713,6 +1827,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1713checksum = "0ee12e4891ab3acc2d95d5023022ace22020247bb8a8d1ece875a443f7dab37d" 1827checksum = "0ee12e4891ab3acc2d95d5023022ace22020247bb8a8d1ece875a443f7dab37d"
1714 1828
1715[[package]] 1829[[package]]
1830name = "unicase"
1831version = "2.6.0"
1832source = "registry+https://github.com/rust-lang/crates.io-index"
1833checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
1834dependencies = [
1835 "version_check",
1836]
1837
1838[[package]]
1716name = "unicode-bidi" 1839name = "unicode-bidi"
1717version = "0.3.4" 1840version = "0.3.4"
1718source = "registry+https://github.com/rust-lang/crates.io-index" 1841source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1737,6 +1860,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1737checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1860checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1738 1861
1739[[package]] 1862[[package]]
1863name = "unicode-width"
1864version = "0.1.8"
1865source = "registry+https://github.com/rust-lang/crates.io-index"
1866checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1867
1868[[package]]
1740name = "unicode-xid" 1869name = "unicode-xid"
1741version = "0.2.1" 1870version = "0.2.1"
1742source = "registry+https://github.com/rust-lang/crates.io-index" 1871source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1755,6 +1884,12 @@ dependencies = [
1755] 1884]
1756 1885
1757[[package]] 1886[[package]]
1887name = "version_check"
1888version = "0.9.2"
1889source = "registry+https://github.com/rust-lang/crates.io-index"
1890checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1891
1892[[package]]
1758name = "vfs" 1893name = "vfs"
1759version = "0.1.0" 1894version = "0.1.0"
1760dependencies = [ 1895dependencies = [
@@ -1789,6 +1924,12 @@ dependencies = [
1789] 1924]
1790 1925
1791[[package]] 1926[[package]]
1927name = "wasi"
1928version = "0.9.0+wasi-snapshot-preview1"
1929source = "registry+https://github.com/rust-lang/crates.io-index"
1930checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1931
1932[[package]]
1792name = "winapi" 1933name = "winapi"
1793version = "0.2.8" 1934version = "0.2.8"
1794source = "registry+https://github.com/rust-lang/crates.io-index" 1935source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 36c0bdc9e..5d88c5c64 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -196,7 +196,6 @@ impl ModuleDef {
196 ModuleDef::Function(it) => Some(it.name(db)), 196 ModuleDef::Function(it) => Some(it.name(db)),
197 ModuleDef::EnumVariant(it) => Some(it.name(db)), 197 ModuleDef::EnumVariant(it) => Some(it.name(db)),
198 ModuleDef::TypeAlias(it) => Some(it.name(db)), 198 ModuleDef::TypeAlias(it) => Some(it.name(db)),
199
200 ModuleDef::Module(it) => it.name(db), 199 ModuleDef::Module(it) => it.name(db),
201 ModuleDef::Const(it) => it.name(db), 200 ModuleDef::Const(it) => it.name(db),
202 ModuleDef::Static(it) => it.name(db), 201 ModuleDef::Static(it) => it.name(db),
@@ -204,6 +203,25 @@ impl ModuleDef {
204 ModuleDef::BuiltinType(it) => Some(it.as_name()), 203 ModuleDef::BuiltinType(it) => Some(it.as_name()),
205 } 204 }
206 } 205 }
206
207 pub fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
208 Some(match self {
209 ModuleDef::Module(m) => ModuleId::from(m.clone()).resolver(db),
210 ModuleDef::Function(f) => FunctionId::from(f.clone()).resolver(db),
211 ModuleDef::Adt(adt) => AdtId::from(adt.clone()).resolver(db),
212 ModuleDef::EnumVariant(ev) => {
213 GenericDefId::from(GenericDef::from(ev.clone())).resolver(db)
214 }
215 ModuleDef::Const(c) => {
216 GenericDefId::from(GenericDef::from(c.clone())).resolver(db)
217 }
218 ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db),
219 ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db),
220 ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db),
221 // FIXME: This should be a resolver relative to `std/core`
222 ModuleDef::BuiltinType(_t) => None?,
223 })
224 }
207} 225}
208 226
209pub use hir_def::{ 227pub use hir_def::{
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 31f3241c9..e2d13dbfd 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -47,13 +47,15 @@ pub use hir_def::{
47 body::scope::ExprScopes, 47 body::scope::ExprScopes,
48 builtin_type::BuiltinType, 48 builtin_type::BuiltinType,
49 docs::Documentation, 49 docs::Documentation,
50 item_scope::ItemInNs,
50 nameres::ModuleSource, 51 nameres::ModuleSource,
51 path::{ModPath, Path, PathKind}, 52 path::{ModPath, Path, PathKind},
52 type_ref::Mutability, 53 type_ref::Mutability,
53}; 54};
54pub use hir_expand::{ 55pub use hir_expand::{
55 hygiene::Hygiene, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, 56 hygiene::Hygiene,
56 MacroDefId, /* FIXME */ 57 name::{AsName, Name},
58 HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, /* FIXME */
57 MacroFile, Origin, 59 MacroFile, Origin,
58}; 60};
59pub use hir_ty::display::HirDisplay; 61pub use hir_ty::display::HirDisplay;
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index f4181c4eb..4e2ba6d61 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -17,6 +17,12 @@ indexmap = "1.3.2"
17itertools = "0.9.0" 17itertools = "0.9.0"
18log = "0.4.8" 18log = "0.4.8"
19rustc-hash = "1.1.0" 19rustc-hash = "1.1.0"
20rand = { version = "0.7.3", features = ["small_rng"] }
21url = "*"
22maplit = "*"
23lazy_static = "*"
24pulldown-cmark-to-cmark = "4.0.2"
25pulldown-cmark = "0.7.0"
20oorandom = "11.1.2" 26oorandom = "11.1.2"
21 27
22stdx = { path = "../stdx" } 28stdx = { path = "../stdx" }
@@ -31,6 +37,9 @@ ra_prof = { path = "../ra_prof" }
31test_utils = { path = "../test_utils" } 37test_utils = { path = "../test_utils" }
32ra_assists = { path = "../ra_assists" } 38ra_assists = { path = "../ra_assists" }
33ra_ssr = { path = "../ra_ssr" } 39ra_ssr = { path = "../ra_ssr" }
40ra_hir_def = { path = "../ra_hir_def" }
41ra_tt = { path = "../ra_tt" }
42ra_parser = { path = "../ra_parser" }
34 43
35# ra_ide should depend only on the top-level `hir` package. if you need 44# ra_ide should depend only on the top-level `hir` package. if you need
36# something from some `hir_xxx` subpackage, reexport the API via `hir`. 45# 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 aa48cb412..ad68bc43c 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,16 +1,26 @@
1use std::collections::{HashMap, HashSet};
2use std::iter::once;
3
1use hir::{ 4use hir::{
2 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 5 db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation,
3 Module, ModuleDef, ModuleSource, Semantics, 6 FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, Module, ModuleDef,
7 ModuleSource, Semantics,
4}; 8};
5use itertools::Itertools; 9use itertools::Itertools;
10use lazy_static::lazy_static;
11use maplit::{hashmap, hashset};
12use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
13use pulldown_cmark_to_cmark::cmark;
6use ra_db::SourceDatabase; 14use ra_db::SourceDatabase;
7use ra_ide_db::{ 15use ra_ide_db::{
8 defs::{classify_name, classify_name_ref, Definition}, 16 defs::{classify_name, classify_name_ref, Definition},
9 RootDatabase, 17 RootDatabase,
10}; 18};
11use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 19use ra_syntax::{ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
20use ra_tt::{Ident, Leaf, Literal, TokenTree};
12use stdx::format_to; 21use stdx::format_to;
13use test_utils::mark; 22use test_utils::mark;
23use url::Url;
14 24
15use crate::{ 25use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 26 display::{macro_label, ShortLabel, ToNav, TryToNav},
@@ -92,7 +102,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
92 }; 102 };
93 if let Some(definition) = definition { 103 if let Some(definition) = definition {
94 if let Some(markup) = hover_for_definition(db, definition) { 104 if let Some(markup) = hover_for_definition(db, definition) {
95 res.markup = markup; 105 let markup = rewrite_links(db, &markup.as_str(), &definition);
106 res.markup = Markup::from(markup);
96 if let Some(action) = show_implementations_action(db, definition) { 107 if let Some(action) = show_implementations_action(db, definition) {
97 res.actions.push(action); 108 res.actions.push(action);
98 } 109 }
@@ -335,6 +346,277 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
335 } 346 }
336} 347}
337 348
349// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
350fn map_links<'e>(
351 events: impl Iterator<Item = Event<'e>>,
352 callback: impl Fn(&str, &str) -> (String, String),
353) -> impl Iterator<Item = Event<'e>> {
354 let mut in_link = false;
355 let mut link_target: Option<CowStr> = None;
356
357 events.map(move |evt| match evt {
358 Event::Start(Tag::Link(_link_type, ref target, _)) => {
359 in_link = true;
360 link_target = Some(target.clone());
361 evt
362 }
363 Event::End(Tag::Link(link_type, _target, _)) => {
364 in_link = false;
365 Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
366 }
367 Event::Text(s) if in_link => {
368 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
369 link_target = Some(CowStr::Boxed(link_target_s.into()));
370 Event::Text(CowStr::Boxed(link_name.into()))
371 }
372 Event::Code(s) if in_link => {
373 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
374 link_target = Some(CowStr::Boxed(link_target_s.into()));
375 Event::Code(CowStr::Boxed(link_name.into()))
376 }
377 _ => evt,
378 })
379}
380
381/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
382fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
383 let doc = Parser::new_with_broken_link_callback(
384 markdown,
385 Options::empty(),
386 Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
387 );
388
389 let doc = map_links(doc, |target, title: &str| {
390 // This check is imperfect, there's some overlap between valid intra-doc links
391 // and valid URLs so we choose to be too eager to try to resolve what might be
392 // a URL.
393 if target.contains("://") {
394 (target.to_string(), title.to_string())
395 } else {
396 // Two posibilities:
397 // * path-based links: `../../module/struct.MyStruct.html`
398 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
399 let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| {
400 try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))
401 });
402
403 if let Some((target, title)) = resolved {
404 (target, title)
405 } else {
406 (target.to_string(), title.to_string())
407 }
408 }
409 });
410 let mut out = String::new();
411 cmark(doc, &mut out, None).ok();
412 out
413}
414
415#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
416enum Namespace {
417 Types,
418 Values,
419 Macros,
420}
421
422lazy_static!(
423 /// Map of namespaces to identifying prefixes and suffixes as defined by RFC1946.
424 static ref NS_MAP: HashMap<Namespace, (HashSet<&'static str>, HashSet<&'static str>)> = hashmap!{
425 Namespace::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}),
426 Namespace::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}),
427 Namespace::Macros => (hashset!{"macro"}, hashset!{"!"})
428 };
429);
430
431impl Namespace {
432 /// Extract the specified namespace from an intra-doc-link if one exists.
433 fn from_intra_spec(s: &str) -> Option<Self> {
434 NS_MAP
435 .iter()
436 .filter(|(_ns, (prefixes, suffixes))| {
437 prefixes
438 .iter()
439 .map(|prefix| {
440 s.starts_with(prefix)
441 && s.chars()
442 .nth(prefix.len() + 1)
443 .map(|c| c == '@' || c == ' ')
444 .unwrap_or(false)
445 })
446 .any(|cond| cond)
447 || suffixes
448 .iter()
449 .map(|suffix| {
450 s.starts_with(suffix)
451 && s.chars()
452 .nth(suffix.len() + 1)
453 .map(|c| c == '@' || c == ' ')
454 .unwrap_or(false)
455 })
456 .any(|cond| cond)
457 })
458 .map(|(ns, (_, _))| *ns)
459 .next()
460 }
461}
462
463// Strip prefixes, suffixes, and inline code marks from the given string.
464fn strip_prefixes_suffixes(mut s: &str) -> &str {
465 s = s.trim_matches('`');
466 NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| {
467 prefixes.iter().for_each(|prefix| s = s.trim_start_matches(prefix));
468 suffixes.iter().for_each(|suffix| s = s.trim_end_matches(suffix));
469 });
470 s.trim_start_matches("@").trim()
471}
472
473/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
474///
475/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
476fn try_resolve_intra(
477 db: &RootDatabase,
478 definition: &Definition,
479 link_text: &str,
480 link_target: &str,
481) -> Option<(String, String)> {
482 // Set link_target for implied shortlinks
483 let link_target =
484 if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };
485
486 // Namespace disambiguation
487 let namespace = Namespace::from_intra_spec(link_target);
488
489 // Strip prefixes/suffixes
490 let link_target = strip_prefixes_suffixes(link_target);
491
492 // Parse link as a module path
493 let path = Path::parse(link_target).ok()?;
494 let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
495
496 // Resolve it relative to symbol's location (according to the RFC this should consider small scopes
497 let resolver = definition.resolver(db)?;
498
499 let resolved = resolver.resolve_module_path_in_items(db, &modpath);
500 let (defid, namespace) = match namespace {
501 // FIXME: .or(resolved.macros)
502 None => resolved
503 .types
504 .map(|t| (t.0, Namespace::Types))
505 .or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
506 Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
507 Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
508 // FIXME:
509 Some(Namespace::Macros) => None?,
510 };
511
512 // Get the filepath of the final symbol
513 let def: ModuleDef = defid.into();
514 let module = def.module(db)?;
515 let krate = module.krate();
516 let ns = match namespace {
517 Namespace::Types => ItemInNs::Types(defid),
518 Namespace::Values => ItemInNs::Values(defid),
519 // FIXME:
520 Namespace::Macros => None?,
521 };
522 let import_map = db.import_map(krate.into());
523 let path = import_map.path_of(ns)?;
524
525 Some((
526 get_doc_url(db, &krate)?
527 .join(&format!("{}/", krate.display_name(db)?))
528 .ok()?
529 .join(&path.segments.iter().map(|name| format!("{}", name)).join("/"))
530 .ok()?
531 .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
532 .ok()?
533 .into_string(),
534 strip_prefixes_suffixes(link_text).to_string(),
535 ))
536}
537
538/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
539fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> {
540 if !link.contains("#") && !link.contains(".html") {
541 return None;
542 }
543 let ns = if let Definition::ModuleDef(moddef) = definition {
544 ItemInNs::Types(moddef.clone().into())
545 } else {
546 return None;
547 };
548 let module = definition.module(db)?;
549 let krate = module.krate();
550 let import_map = db.import_map(krate.into());
551 let base = once(format!("{}", krate.display_name(db)?))
552 .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name)))
553 .join("/");
554
555 get_doc_url(db, &krate)
556 .and_then(|url| url.join(&base).ok())
557 .and_then(|url| {
558 get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()
559 })
560 .and_then(|url| url.join(link).ok())
561 .map(|url| url.into_string())
562}
563
564/// Try to get the root URL of the documentation of a crate.
565fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
566 // Look for #![doc(html_root_url = "...")]
567 let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());
568 let doc_attr_q = attrs.by_key("doc");
569
570 let doc_url = if doc_attr_q.exists() {
571 doc_attr_q.tt_values().map(|tt| {
572 let name = tt.token_trees.iter()
573 .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url"))
574 .skip(2)
575 .next();
576
577 match name {
578 Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
579 _ => None
580 }
581 }).flat_map(|t| t).next().map(|s| s.to_string())
582 } else {
583 // Fallback to docs.rs
584 // FIXME: Specify an exact version here (from Cargo.lock)
585 Some(format!("https://docs.rs/{}/*", krate.display_name(db)?))
586 };
587
588 doc_url
589 .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/")
590 .and_then(|s| Url::parse(&s).ok())
591}
592
593/// Get the filename and extension generated for a symbol by rustdoc.
594///
595/// Example: `struct.Shard.html`
596fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option<String> {
597 Some(match definition {
598 Definition::ModuleDef(def) => match def {
599 ModuleDef::Adt(adt) => match adt {
600 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
601 Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
602 Adt::Union(u) => format!("union.{}.html", u.name(db)),
603 },
604 ModuleDef::Module(_) => "index.html".to_string(),
605 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
606 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
607 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
608 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
609 ModuleDef::EnumVariant(ev) => {
610 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
611 }
612 ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
613 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
614 },
615 Definition::Macro(m) => format!("macro.{}.html", m.name(db)?),
616 _ => None?,
617 })
618}
619
338fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 620fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
339 return tokens.max_by_key(priority); 621 return tokens.max_by_key(priority);
340 fn priority(n: &SyntaxToken) -> usize { 622 fn priority(n: &SyntaxToken) -> usize {
@@ -1194,6 +1476,155 @@ fn foo() { let bar = Ba<|>r; }
1194 } 1476 }
1195 1477
1196 #[test] 1478 #[test]
1479 fn test_hover_path_link() {
1480 check_hover_result(
1481 r"
1482 //- /lib.rs
1483 pub struct Foo;
1484 /// [Foo](struct.Foo.html)
1485 pub struct B<|>ar
1486 ",
1487 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1488 );
1489 }
1490
1491 #[test]
1492 fn test_hover_path_link_no_strip() {
1493 check_hover_result(
1494 r"
1495 //- /lib.rs
1496 pub struct Foo;
1497 /// [struct Foo](struct.Foo.html)
1498 pub struct B<|>ar
1499 ",
1500 &["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1501 );
1502 }
1503
1504 #[test]
1505 fn test_hover_intra_link() {
1506 check_hover_result(
1507 r"
1508 //- /lib.rs
1509 pub mod foo {
1510 pub struct Foo;
1511 }
1512 /// [Foo](foo::Foo)
1513 pub struct B<|>ar
1514 ",
1515 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"],
1516 );
1517 }
1518
1519 #[test]
1520 fn test_hover_intra_link_shortlink() {
1521 check_hover_result(
1522 r"
1523 //- /lib.rs
1524 pub struct Foo;
1525 /// [Foo]
1526 pub struct B<|>ar
1527 ",
1528 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1529 );
1530 }
1531
1532 #[test]
1533 fn test_hover_intra_link_shortlink_code() {
1534 check_hover_result(
1535 r"
1536 //- /lib.rs
1537 pub struct Foo;
1538 /// [`Foo`]
1539 pub struct B<|>ar
1540 ",
1541 &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"],
1542 );
1543 }
1544
1545 #[test]
1546 fn test_hover_intra_link_namespaced() {
1547 check_hover_result(
1548 r"
1549 //- /lib.rs
1550 pub struct Foo;
1551 fn Foo() {}
1552 /// [Foo()]
1553 pub struct B<|>ar
1554 ",
1555 &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1556 );
1557 }
1558
1559 #[test]
1560 fn test_hover_intra_link_shortlink_namspaced_code() {
1561 check_hover_result(
1562 r"
1563 //- /lib.rs
1564 pub struct Foo;
1565 /// [`struct Foo`]
1566 pub struct B<|>ar
1567 ",
1568 &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"],
1569 );
1570 }
1571
1572 #[test]
1573 fn test_hover_intra_link_shortlink_namspaced_code_with_at() {
1574 check_hover_result(
1575 r"
1576 //- /lib.rs
1577 pub struct Foo;
1578 /// [`struct@Foo`]
1579 pub struct B<|>ar
1580 ",
1581 &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"],
1582 );
1583 }
1584
1585 #[test]
1586 fn test_hover_intra_link_reference() {
1587 check_hover_result(
1588 r"
1589 //- /lib.rs
1590 pub struct Foo;
1591 /// [my Foo][foo]
1592 ///
1593 /// [foo]: Foo
1594 pub struct B<|>ar
1595 ",
1596 &["pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1597 );
1598 }
1599
1600 #[test]
1601 fn test_hover_external_url() {
1602 check_hover_result(
1603 r"
1604 //- /lib.rs
1605 pub struct Foo;
1606 /// [external](https://www.google.com)
1607 pub struct B<|>ar
1608 ",
1609 &["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"],
1610 );
1611 }
1612
1613 // Check that we don't rewrite links which we can't identify
1614 #[test]
1615 fn test_hover_unknown_target() {
1616 check_hover_result(
1617 r"
1618 //- /lib.rs
1619 pub struct Foo;
1620 /// [baz](Baz)
1621 pub struct B<|>ar
1622 ",
1623 &["pub struct Bar\n```\n___\n\n[baz](Baz)"],
1624 );
1625 }
1626
1627 #[test]
1197 fn test_hover_macro_generated_struct_fn_doc_comment() { 1628 fn test_hover_macro_generated_struct_fn_doc_comment() {
1198 mark::check!(hover_macro_generated_struct_fn_doc_comment); 1629 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1199 1630
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index c7e0f4b58..cf2ee1bfa 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -115,7 +115,7 @@ impl MockAnalysis {
115 root_crate = Some(crate_graph.add_crate_root( 115 root_crate = Some(crate_graph.add_crate_root(
116 file_id, 116 file_id,
117 edition, 117 edition,
118 None, 118 Some("test".to_string()),
119 cfg, 119 cfg,
120 env, 120 env,
121 Default::default(), 121 Default::default(),
diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml
index 2716a38cc..f345f1de8 100644
--- a/crates/ra_ide_db/Cargo.toml
+++ b/crates/ra_ide_db/Cargo.toml
@@ -26,6 +26,7 @@ ra_text_edit = { path = "../ra_text_edit" }
26ra_db = { path = "../ra_db" } 26ra_db = { path = "../ra_db" }
27ra_prof = { path = "../ra_prof" } 27ra_prof = { path = "../ra_prof" }
28test_utils = { path = "../test_utils" } 28test_utils = { path = "../test_utils" }
29ra_hir_def = { path = "../ra_hir_def" }
29 30
30# ra_ide should depend only on the top-level `hir` package. if you need 31# ra_ide should depend only on the top-level `hir` package. if you need
31# something from some `hir_xxx` subpackage, reexport the API via `hir`. 32# something from some `hir_xxx` subpackage, reexport the API via `hir`.
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index df56f2d9e..80c99935d 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -6,6 +6,7 @@
6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). 6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7 7
8use hir::{ 8use hir::{
9 db::{DefDatabase, HirDatabase},
9 Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, 10 Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution,
10 Semantics, TypeParam, Visibility, 11 Semantics, TypeParam, Visibility,
11}; 12};
@@ -16,6 +17,7 @@ use ra_syntax::{
16}; 17};
17 18
18use crate::RootDatabase; 19use crate::RootDatabase;
20use ra_hir_def::resolver::{HasResolver, Resolver};
19 21
20// FIXME: a more precise name would probably be `Symbol`? 22// FIXME: a more precise name would probably be `Symbol`?
21#[derive(Debug, PartialEq, Eq, Copy, Clone)] 23#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -76,6 +78,21 @@ impl Definition {
76 }; 78 };
77 Some(name) 79 Some(name)
78 } 80 }
81
82 pub fn resolver<D: HirDatabase + DefDatabase>(&self, db: &D) -> Option<Resolver> {
83 use hir::VariantDef;
84 use ra_hir_def::*;
85 Some(match self {
86 Definition::ModuleDef(def) => def.resolver(db)?,
87 Definition::Field(field) => {
88 Into::<VariantId>::into(Into::<VariantDef>::into(field.parent_def(db))).resolver(db)
89 }
90 Definition::Macro(m) => Into::<ModuleId>::into(m.module(db)?).resolver(db),
91 Definition::SelfType(imp) => Into::<ImplId>::into(imp.clone()).resolver(db),
92 Definition::Local(local) => Into::<DefWithBodyId>::into(local.parent(db)).resolver(db),
93 Definition::TypeParam(tp) => Into::<ModuleId>::into(tp.module(db)).resolver(db),
94 })
95 }
79} 96}
80 97
81#[derive(Debug)] 98#[derive(Debug)]
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 7370505f8..91ce04731 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -680,5 +680,5 @@ pub fn foo(_input: TokenStream) -> TokenStream {
680 }); 680 });
681 681
682 let value = res.get("contents").unwrap().get("value").unwrap().to_string(); 682 let value = res.get("contents").unwrap().get("value").unwrap().to_string();
683 assert_eq!(value, r#""```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#) 683 assert_eq!(value, r#""\n````rust\nfoo::Bar\n````\n\n````rust\nfn bar()\n````""#)
684} 684}