aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml4
-rw-r--r--crates/ra_ide/src/hover.rs102
-rw-r--r--crates/ra_ide/src/lib.rs5
3 files changed, 105 insertions, 6 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index bbc6a5c9b..e5fdf5aa1 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -17,6 +17,8 @@ itertools = "0.9.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19rand = { version = "0.7.3", features = ["small_rng"] } 19rand = { version = "0.7.3", features = ["small_rng"] }
20comrak = "0.7.0"
21url = "*"
20 22
21stdx = { path = "../stdx" } 23stdx = { path = "../stdx" }
22 24
@@ -30,6 +32,8 @@ ra_prof = { path = "../ra_prof" }
30test_utils = { path = "../test_utils" } 32test_utils = { path = "../test_utils" }
31ra_assists = { path = "../ra_assists" } 33ra_assists = { path = "../ra_assists" }
32ra_ssr = { path = "../ra_ssr" } 34ra_ssr = { path = "../ra_ssr" }
35ra_project_model = { path = "../ra_project_model" }
36ra_hir_def = { path = "../ra_hir_def" }
33 37
34# ra_ide should depend only on the top-level `hir` package. if you need 38# ra_ide should depend only on the top-level `hir` package. if you need
35# something from some `hir_xxx` subpackage, reexport the API via `hir`. 39# 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 c3e36a387..079195184 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,8 +1,8 @@
1use std::iter::once; 1use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
5 Module, ModuleDef, ModuleSource, Semantics, 5 ModuleSource, Semantics, Module, Documentation
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -11,6 +11,8 @@ use ra_ide_db::{
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14use ra_project_model::ProjectWorkspace;
15use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId};
14 16
15use crate::{ 17use crate::{
16 display::{ 18 display::{
@@ -65,6 +67,13 @@ pub struct HoverGotoTypeData {
65 pub nav: NavigationTarget, 67 pub nav: NavigationTarget,
66} 68}
67 69
70use std::path::{Path, PathBuf};
71use std::sync::Arc;
72use comrak::{parse_document,format_commonmark, ComrakOptions, Arena};
73use comrak::nodes::NodeValue;
74use url::Url;
75use ra_ide_db::imports_locator::ImportsLocator;
76
68/// Contains the results when hovering over an item 77/// Contains the results when hovering over an item
69#[derive(Debug, Default)] 78#[derive(Debug, Default)]
70pub struct HoverResult { 79pub struct HoverResult {
@@ -118,7 +127,7 @@ impl HoverResult {
118// 127//
119// Shows additional information, like type of an expression or documentation for definition when "focusing" code. 128// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
120// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. 129// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
121pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { 130pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<RangeInfo<HoverResult>> {
122 let sema = Semantics::new(db); 131 let sema = Semantics::new(db);
123 let file = sema.parse(position.file_id).syntax().clone(); 132 let file = sema.parse(position.file_id).syntax().clone();
124 let token = pick_best(file.token_at_offset(position.offset))?; 133 let token = pick_best(file.token_at_offset(position.offset))?;
@@ -138,7 +147,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
138 } 147 }
139 } { 148 } {
140 let range = sema.original_range(&node).range; 149 let range = sema.original_range(&node).range;
141 res.extend(hover_text_from_name_kind(db, name_kind)); 150 let text = hover_text_from_name_kind(db, name_kind.clone()).map(|text| rewrite_links(db, &text, &name_kind, workspaces).unwrap_or(text));
151 res.extend(text);
142 152
143 if !res.is_empty() { 153 if !res.is_empty() {
144 if let Some(action) = show_implementations_action(db, name_kind) { 154 if let Some(action) = show_implementations_action(db, name_kind) {
@@ -379,6 +389,90 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
379 } 389 }
380} 390}
381 391
392/// Rewrite documentation links in markdown to point to local documentation/docs.rs
393fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<String> {
394 // FIXME: Fail early
395 if let (Some(name), Some(module)) = (definition.name(db), definition.module(db)) {
396 let krate_name = module.krate().display_name(db)?;
397 let arena = Arena::new();
398 let doc = parse_document(&arena, markdown, &ComrakOptions::default());
399 let path = module.path_to_root(db);
400 let mut doc_target_dirs = workspaces
401 .iter()
402 .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None})
403 .map(|workspace| workspace.workspace_root())
404 // TODO: `target` is configurable in cargo config, we should respect it
405 .map(|root| root.join("target/doc"));
406
407 iter_nodes(doc, &|node| {
408 match &mut node.data.borrow_mut().value {
409 &mut NodeValue::Link(ref mut link) => {
410 match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) {
411 // If this is a valid absolute URL don't touch it
412 Ok(_) => (),
413 // If contains .html file-based link to new page
414 // If starts with #fragment file-based link to fragment on current page
415 // If contains :: module-based link
416 Err(_) => {
417 let link_str = String::from_utf8(link.url.clone()).unwrap();
418 let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str)
419 .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str));
420
421 if let Some(resolved) = resolved {
422 link.url = resolved.as_bytes().to_vec();
423 }
424
425 }
426 }
427 },
428 _ => ()
429 }
430 });
431 let mut out = Vec::new();
432 format_commonmark(doc, &ComrakOptions::default(), &mut out);
433 Some(String::from_utf8(out).unwrap())
434 } else {
435 // eprintln!("WARN: Unable to determine name or module for hover; link rewriting disabled.");
436 None
437 }
438}
439
440/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`)
441fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str) -> Option<String> {
442 None
443}
444
445/// 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) -> Option<String> {
447 let ns = if let Definition::ModuleDef(moddef) = definition {
448 ItemInNs::Types(moddef.clone().into())
449 } else {
450 return None;
451 };
452 let krate = definition.module(db)?.krate();
453 let import_map = db.import_map(krate.into());
454 let base = import_map.path_of(ns).unwrap();
455 let base = base.segments.iter().map(|name| format!("{}", name)).collect::<PathBuf>();
456
457 doc_target_dirs
458 .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link)))
459 .inspect(|path| eprintln!("candidate {}", path.display()))
460 .filter(|path| path.exists())
461 // slice out the UNC '\?\' added by canonicalize
462 .map(|path| format!("file:///{}", path.display()))
463 // \. is treated as an escape in vscode's markdown hover rendering
464 .map(|path_str| path_str.replace("\\", "/"))
465 .next()
466}
467
468fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F)
469 where F : Fn(&'a comrak::nodes::AstNode<'a>) {
470 f(node);
471 for c in node.children() {
472 iter_nodes(c, f);
473 }
474}
475
382fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 476fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
383 return tokens.max_by_key(priority); 477 return tokens.max_by_key(priority);
384 fn priority(n: &SyntaxToken) -> usize { 478 fn priority(n: &SyntaxToken) -> usize {
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index ecac5134e..d56d52d30 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -54,6 +54,7 @@ use ra_ide_db::{
54 LineIndexDatabase, 54 LineIndexDatabase,
55}; 55};
56use ra_syntax::{SourceFile, TextRange, TextSize}; 56use ra_syntax::{SourceFile, TextRange, TextSize};
57use ra_project_model::ProjectWorkspace;
57 58
58use crate::display::ToNav; 59use crate::display::ToNav;
59 60
@@ -389,8 +390,8 @@ impl Analysis {
389 } 390 }
390 391
391 /// Returns a short text describing element at position. 392 /// Returns a short text describing element at position.
392 pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> { 393 pub fn hover(&self, position: FilePosition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Cancelable<Option<RangeInfo<HoverResult>>> {
393 self.with_db(|db| hover::hover(db, position)) 394 self.with_db(|db| hover::hover(db, position, workspaces))
394 } 395 }
395 396
396 /// Computes parameter information for the given call expression. 397 /// Computes parameter information for the given call expression.