diff options
-rw-r--r-- | crates/ra_ide/src/hover.rs | 120 |
1 files changed, 53 insertions, 67 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 91167e30a..f703af434 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | use std::iter::once; | 1 | use std::iter::once; |
2 | use std::path::PathBuf; | ||
3 | use std::sync::Arc; | ||
2 | 4 | ||
3 | use hir::{ | 5 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 6 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
5 | ModuleSource, Semantics, Module, Documentation, AttrDef, Crate | 7 | ModuleSource, Semantics, Documentation, AttrDef, Crate |
6 | }; | 8 | }; |
7 | use itertools::Itertools; | 9 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 10 | use ra_db::SourceDatabase; |
@@ -12,8 +14,12 @@ use ra_ide_db::{ | |||
12 | }; | 14 | }; |
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 15 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | use ra_project_model::ProjectWorkspace; | 16 | use ra_project_model::ProjectWorkspace; |
15 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId}; | 17 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; |
16 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf, Subtree, SmolStr}; | 18 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; |
19 | |||
20 | use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; | ||
21 | use comrak::nodes::NodeValue; | ||
22 | use url::Url; | ||
17 | 23 | ||
18 | use crate::{ | 24 | use crate::{ |
19 | display::{ | 25 | display::{ |
@@ -68,13 +74,6 @@ pub struct HoverGotoTypeData { | |||
68 | pub nav: NavigationTarget, | 74 | pub nav: NavigationTarget, |
69 | } | 75 | } |
70 | 76 | ||
71 | use std::path::{Path, PathBuf}; | ||
72 | use std::sync::Arc; | ||
73 | use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; | ||
74 | use comrak::nodes::NodeValue; | ||
75 | use url::Url; | ||
76 | use ra_ide_db::imports_locator::ImportsLocator; | ||
77 | |||
78 | /// Contains the results when hovering over an item | 77 | /// Contains the results when hovering over an item |
79 | #[derive(Debug, Default)] | 78 | #[derive(Debug, Default)] |
80 | pub struct HoverResult { | 79 | pub struct HoverResult { |
@@ -392,50 +391,42 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
392 | 391 | ||
393 | /// Rewrite documentation links in markdown to point to local documentation/docs.rs | 392 | /// Rewrite documentation links in markdown to point to local documentation/docs.rs |
394 | fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<String> { | 393 | fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<String> { |
395 | // FIXME: Fail early | 394 | let arena = Arena::new(); |
396 | if let (Some(name), Some(module)) = (definition.name(db), definition.module(db)) { | 395 | let doc = parse_document(&arena, markdown, &ComrakOptions::default()); |
397 | let krate_name = module.krate().display_name(db)?; | 396 | let doc_target_dirs = workspaces |
398 | let arena = Arena::new(); | 397 | .iter() |
399 | let doc = parse_document(&arena, markdown, &ComrakOptions::default()); | 398 | .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) |
400 | let path = module.path_to_root(db); | 399 | .map(|workspace| workspace.workspace_root()) |
401 | let mut doc_target_dirs = workspaces | 400 | // TODO: `target` is configurable in cargo config, we should respect it |
402 | .iter() | 401 | .map(|root| root.join("target/doc")); |
403 | .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) | 402 | |
404 | .map(|workspace| workspace.workspace_root()) | 403 | iter_nodes(doc, &|node| { |
405 | // TODO: `target` is configurable in cargo config, we should respect it | 404 | match &mut node.data.borrow_mut().value { |
406 | .map(|root| root.join("target/doc")); | 405 | &mut NodeValue::Link(ref mut link) => { |
407 | 406 | match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { | |
408 | iter_nodes(doc, &|node| { | 407 | // If this is a valid absolute URL don't touch it |
409 | match &mut node.data.borrow_mut().value { | 408 | Ok(_) => (), |
410 | &mut NodeValue::Link(ref mut link) => { | 409 | // If contains .html file-based link to new page |
411 | match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { | 410 | // If starts with #fragment file-based link to fragment on current page |
412 | // If this is a valid absolute URL don't touch it | 411 | // If contains :: module-based link |
413 | Ok(_) => (), | 412 | Err(_) => { |
414 | // If contains .html file-based link to new page | 413 | let link_str = String::from_utf8(link.url.clone()).unwrap(); |
415 | // If starts with #fragment file-based link to fragment on current page | 414 | let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) |
416 | // If contains :: module-based link | 415 | .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); |
417 | Err(_) => { | 416 | |
418 | let link_str = String::from_utf8(link.url.clone()).unwrap(); | 417 | if let Some(resolved) = resolved { |
419 | let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) | 418 | link.url = resolved.as_bytes().to_vec(); |
420 | .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); | ||
421 | |||
422 | if let Some(resolved) = resolved { | ||
423 | link.url = resolved.as_bytes().to_vec(); | ||
424 | } | ||
425 | |||
426 | } | 419 | } |
420 | |||
427 | } | 421 | } |
428 | }, | 422 | } |
429 | _ => () | 423 | }, |
430 | } | 424 | _ => () |
431 | }); | 425 | } |
432 | let mut out = Vec::new(); | 426 | }); |
433 | format_commonmark(doc, &ComrakOptions::default(), &mut out); | 427 | let mut out = Vec::new(); |
434 | Some(String::from_utf8(out).unwrap()) | 428 | format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; |
435 | } else { | 429 | Some(String::from_utf8(out).unwrap()) |
436 | // eprintln!("WARN: Unable to determine name or module for hover; link rewriting disabled."); | ||
437 | None | ||
438 | } | ||
439 | } | 430 | } |
440 | 431 | ||
441 | /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`) | 432 | /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`) |
@@ -467,13 +458,10 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pat | |||
467 | UrlMode::Url => { | 458 | UrlMode::Url => { |
468 | let root = get_doc_url(db, &krate); | 459 | let root = get_doc_url(db, &krate); |
469 | let mut base = base.join("/"); | 460 | let mut base = base.join("/"); |
470 | if let Some(url) = root { | 461 | if link.starts_with("#") { |
471 | eprintln!("root: {:?} base: {:?} link: {} root&base: {} root&base&link: {}", url, &base, link, url.join(&base).unwrap(), url.join(&base).unwrap().join(link).unwrap()); | 462 | base = base + "/" |
472 | if link.starts_with("#") { | 463 | }; |
473 | base = base + "/" | 464 | root.and_then(|url| url.join(&base).ok()).and_then(|url| url.join(link).ok()).map(|url| url.into_string()) |
474 | }; | ||
475 | Some(url.join(&base)?.join(link)?.into_string()) | ||
476 | } else {None} | ||
477 | }, | 465 | }, |
478 | UrlMode::File => { | 466 | UrlMode::File => { |
479 | let base = base.collect::<PathBuf>(); | 467 | let base = base.collect::<PathBuf>(); |
@@ -491,7 +479,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pat | |||
491 | 479 | ||
492 | /// Try to get the root URL of the documentation of a crate. | 480 | /// Try to get the root URL of the documentation of a crate. |
493 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | 481 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { |
494 | // Look for #![doc(html_root_url = "https://docs.rs/...")] | 482 | // Look for #![doc(html_root_url = "...")] |
495 | let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); | 483 | let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); |
496 | let doc_attr_q = attrs.by_key("doc"); | 484 | let doc_attr_q = attrs.by_key("doc"); |
497 | let doc_url = if doc_attr_q.exists() { | 485 | let doc_url = if doc_attr_q.exists() { |
@@ -500,20 +488,18 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | |||
500 | TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident_text, ..})), | 488 | TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident_text, ..})), |
501 | TokenTree::Leaf(Leaf::Punct(Punct{r#char: '=', ..})), | 489 | TokenTree::Leaf(Leaf::Punct(Punct{r#char: '=', ..})), |
502 | TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..})) | 490 | TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..})) |
503 | ] if ident_text == "html_root_url" => Some(text), | 491 | ] if ident_text == "html_root_url" => Some(text.to_string()), |
504 | _ => { | 492 | _ => { |
505 | None | 493 | None |
506 | } | 494 | } |
507 | }).next() | 495 | }).next() |
508 | } else { | 496 | } else { |
509 | None | 497 | // Fallback to docs.rs |
498 | // TODO: Specify an exact version here (from Cargo.lock) | ||
499 | Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) | ||
510 | }; | 500 | }; |
511 | eprintln!("url {:?}", doc_url); | ||
512 | 501 | ||
513 | // TODO: It should be possible to fallback to `format!("https://docs.rs/{}/*", crate_name, *)` | 502 | doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()) |
514 | let url = doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()); | ||
515 | eprintln!("url {:?}", url); | ||
516 | url | ||
517 | } | 503 | } |
518 | 504 | ||
519 | fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) | 505 | fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) |