aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
-rw-r--r--crates/ra_syntax/src/ast/test.txt15
-rw-r--r--crates/ra_syntax/src/ast/traits.rs36
-rw-r--r--crates/rust-analyzer/src/handlers.rs1
6 files changed, 157 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.
diff --git a/crates/ra_syntax/src/ast/test.txt b/crates/ra_syntax/src/ast/test.txt
new file mode 100644
index 000000000..f746bf1e7
--- /dev/null
+++ b/crates/ra_syntax/src/ast/test.txt
@@ -0,0 +1,15 @@
1The context is a general utility struct provided on event dispatches, which
2helps with dealing with the current "context" of the event dispatch.
3The context also acts as a general high-level interface over the associated
4[`Shard`] which received the event, or the low-level [`http`] module.
5
6The context contains "shortcuts", like for interacting with the shard.
7Methods like [`set_activity`] will unlock the shard and perform an update for
8you to save a bit of work.
9
10A context will only live for the event it was dispatched for. After the
11event handler finished, it is destroyed and will not be re-used.
12
13[`Shard`]: ../gateway/struct.Shard.html
14[`http`]: ../http/index.html
15[`set_activity`]: #method.set_activity
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs
index a8f2454fd..323d78bbc 100644
--- a/crates/ra_syntax/src/ast/traits.rs
+++ b/crates/ra_syntax/src/ast/traits.rs
@@ -146,3 +146,39 @@ impl Iterator for CommentIter {
146 self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast)) 146 self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast))
147 } 147 }
148} 148}
149
150#[cfg(test)]
151mod tests {
152 use comrak::{parse_document,format_commonmark, ComrakOptions, Arena};
153 use comrak::nodes::{AstNode, NodeValue};
154
155 fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F)
156 where F : Fn(&'a AstNode<'a>) {
157 f(node);
158 for c in node.children() {
159 iter_nodes(c, f);
160 }
161 }
162
163 #[allow(non_snake_case)]
164 #[test]
165 fn test_link_rewrite() {
166 let src = include_str!("./test.txt");
167
168 let arena = Arena::new();
169 let doc = parse_document(&arena, src, &ComrakOptions::default());
170
171 iter_nodes(doc, &|node| {
172 match &mut node.data.borrow_mut().value {
173 &mut NodeValue::Link(ref mut link) => {
174 link.url = "https://www.google.com".as_bytes().to_vec();
175 },
176 _ => ()
177 }
178 });
179
180 let mut out = Vec::new();
181 format_commonmark(doc, &ComrakOptions::default(), &mut out);
182 panic!("{}", String::from_utf8(out).unwrap());
183 }
184}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index e35a5e846..19da25f96 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -5,6 +5,7 @@
5use std::{ 5use std::{
6 io::Write as _, 6 io::Write as _,
7 process::{self, Stdio}, 7 process::{self, Stdio},
8 sync::Arc
8}; 9};
9 10
10use lsp_server::ErrorCode; 11use lsp_server::ErrorCode;