aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs69
1 files changed, 19 insertions, 50 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index f4b10deac..0da10a08e 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,10 +1,8 @@
1use std::iter::once; 1use std::iter::once;
2use std::path::PathBuf;
3use std::sync::Arc;
4 2
5use hir::{ 3use hir::{
6 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
7 ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene 5 ModuleSource, Semantics, Documentation, AttrDef, Crate, ModPath, Hygiene
8}; 6};
9use itertools::Itertools; 7use itertools::Itertools;
10use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -13,11 +11,9 @@ use ra_ide_db::{
13 RootDatabase, 11 RootDatabase,
14}; 12};
15use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path};
16use ra_project_model::ProjectWorkspace; 14use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, resolver::HasResolver};
17use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver};
18use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; 15use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf};
19use ra_hir_expand::name::AsName; 16use ra_hir_expand::name::AsName;
20use ra_parser::FragmentKind;
21use maplit::{hashset, hashmap}; 17use maplit::{hashset, hashmap};
22 18
23use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; 19use comrak::{parse_document,format_commonmark, ComrakOptions, Arena};
@@ -130,7 +126,7 @@ impl HoverResult {
130// 126//
131// Shows additional information, like type of an expression or documentation for definition when "focusing" code. 127// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
132// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. 128// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
133pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<RangeInfo<HoverResult>> { 129pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
134 let sema = Semantics::new(db); 130 let sema = Semantics::new(db);
135 let file = sema.parse(position.file_id).syntax().clone(); 131 let file = sema.parse(position.file_id).syntax().clone();
136 let token = pick_best(file.token_at_offset(position.offset))?; 132 let token = pick_best(file.token_at_offset(position.offset))?;
@@ -150,7 +146,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc<V
150 } 146 }
151 } { 147 } {
152 let range = sema.original_range(&node).range; 148 let range = sema.original_range(&node).range;
153 let text = hover_text_from_name_kind(db, name_kind.clone()).map(|text| rewrite_links(db, &text, &name_kind, workspaces).unwrap_or(text)); 149 let text = hover_text_from_name_kind(db, name_kind.clone()).map(|text| rewrite_links(db, &text, &name_kind).unwrap_or(text));
154 res.extend(text); 150 res.extend(text);
155 151
156 if !res.is_empty() { 152 if !res.is_empty() {
@@ -393,15 +389,9 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
393} 389}
394 390
395/// Rewrite documentation links in markdown to point to local documentation/docs.rs 391/// Rewrite documentation links in markdown to point to local documentation/docs.rs
396fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, workspaces: Arc<Vec<ProjectWorkspace>>) -> Option<String> { 392fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> {
397 let arena = Arena::new(); 393 let arena = Arena::new();
398 let doc = parse_document(&arena, markdown, &ComrakOptions::default()); 394 let doc = parse_document(&arena, markdown, &ComrakOptions::default());
399 let doc_target_dirs = workspaces
400 .iter()
401 .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None})
402 .map(|workspace| workspace.workspace_root())
403 // TODO: `target` is configurable in cargo config, we should respect it
404 .map(|root| root.join("target/doc"));
405 395
406 iter_nodes(doc, &|node| { 396 iter_nodes(doc, &|node| {
407 match &mut node.data.borrow_mut().value { 397 match &mut node.data.borrow_mut().value {
@@ -415,8 +405,8 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor
415 Err(_) => { 405 Err(_) => {
416 let link_str = String::from_utf8(link.url.clone()).unwrap(); 406 let link_str = String::from_utf8(link.url.clone()).unwrap();
417 let link_text = String::from_utf8(link.title.clone()).unwrap(); 407 let link_text = String::from_utf8(link.title.clone()).unwrap();
418 let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) 408 let resolved = try_resolve_path(db, definition, &link_str)
419 .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_text, &link_str)); 409 .or_else(|| try_resolve_intra(db, definition, &link_text, &link_str));
420 410
421 if let Some(resolved) = resolved { 411 if let Some(resolved) = resolved {
422 link.url = resolved.as_bytes().to_vec(); 412 link.url = resolved.as_bytes().to_vec();
@@ -451,7 +441,7 @@ impl Namespace {
451 441
452 ns_map 442 ns_map
453 .iter() 443 .iter()
454 .filter(|(ns, (prefixes, suffixes))| { 444 .filter(|(_ns, (prefixes, suffixes))| {
455 prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || 445 prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) ||
456 suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) 446 suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond)
457 }) 447 })
@@ -463,7 +453,7 @@ impl Namespace {
463/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). 453/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
464/// 454///
465/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). 455/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
466fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link_text: &str, link_target: &str) -> Option<String> { 456fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str, link_target: &str) -> Option<String> {
467 eprintln!("try_resolve_intra"); 457 eprintln!("try_resolve_intra");
468 458
469 // Set link_target for implied shortlinks 459 // Set link_target for implied shortlinks
@@ -496,13 +486,13 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pa
496 ModuleDef::Trait(t) => Into::<TraitId>::into(t.clone()).resolver(db), 486 ModuleDef::Trait(t) => Into::<TraitId>::into(t.clone()).resolver(db),
497 ModuleDef::TypeAlias(t) => Into::<ModuleId>::into(t.module(db)).resolver(db), 487 ModuleDef::TypeAlias(t) => Into::<ModuleId>::into(t.module(db)).resolver(db),
498 // TODO: This should be a resolver relative to `std` 488 // TODO: This should be a resolver relative to `std`
499 ModuleDef::BuiltinType(t) => Into::<ModuleId>::into(definition.module(db)?).resolver(db) 489 ModuleDef::BuiltinType(_t) => Into::<ModuleId>::into(definition.module(db)?).resolver(db)
500 }, 490 },
501 Definition::Field(field) => Into::<VariantId>::into(Into::<VariantDef>::into(field.parent_def(db))).resolver(db), 491 Definition::Field(field) => Into::<VariantId>::into(Into::<VariantDef>::into(field.parent_def(db))).resolver(db),
502 Definition::Macro(m) => Into::<ModuleId>::into(m.module(db)?).resolver(db), 492 Definition::Macro(m) => Into::<ModuleId>::into(m.module(db)?).resolver(db),
503 Definition::SelfType(imp) => Into::<ImplId>::into(imp.clone()).resolver(db), 493 Definition::SelfType(imp) => Into::<ImplId>::into(imp.clone()).resolver(db),
504 // it's possible, read probable, that other arms of this are also unreachable 494 // it's possible, read probable, that other arms of this are also unreachable
505 Definition::Local(local) => unreachable!(), 495 Definition::Local(_local) => unreachable!(),
506 Definition::TypeParam(tp) => Into::<ModuleId>::into(tp.module(db)).resolver(db) 496 Definition::TypeParam(tp) => Into::<ModuleId>::into(tp.module(db)).resolver(db)
507 } 497 }
508 }; 498 };
@@ -542,13 +532,8 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pa
542 ) 532 )
543} 533}
544 534
545enum UrlMode {
546 Url,
547 File
548}
549
550/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). 535/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
551fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = PathBuf>, definition: &Definition, link: &str, mode: UrlMode) -> Option<String> { 536fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> {
552 eprintln!("try_resolve_path"); 537 eprintln!("try_resolve_path");
553 let ns = if let Definition::ModuleDef(moddef) = definition { 538 let ns = if let Definition::ModuleDef(moddef) = definition {
554 ItemInNs::Types(moddef.clone().into()) 539 ItemInNs::Types(moddef.clone().into())
@@ -561,29 +546,13 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator<Item = Pat
561 // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, 546 // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one,
562 // then hope rustdoc was run locally with `--document-private-items` 547 // then hope rustdoc was run locally with `--document-private-items`
563 let base = import_map.path_of(ns)?; 548 let base = import_map.path_of(ns)?;
564 let mut base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))); 549 let base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))).join("/");
565 550
566 match mode { 551 get_doc_url(db, &krate)
567 UrlMode::Url => { 552 .and_then(|url| url.join(&base).ok())
568 let mut base = base.join("/"); 553 .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten())
569 get_doc_url(db, &krate) 554 .and_then(|url| url.join(link).ok())
570 .and_then(|url| url.join(&base).ok()) 555 .map(|url| url.into_string())
571 .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten())
572 .and_then(|url| url.join(link).ok())
573 .map(|url| url.into_string())
574 },
575 UrlMode::File => {
576 let base = base.collect::<PathBuf>();
577 doc_target_dirs
578 .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link)))
579 .inspect(|path| eprintln!("candidate {}", path.display()))
580 .filter(|path| path.exists())
581 .map(|path| format!("file:///{}", path.display()))
582 // \. is treated as an escape in vscode's markdown hover rendering
583 .map(|path_str| path_str.replace("\\", "/"))
584 .next()
585 }
586 }
587} 556}
588 557
589/// Try to get the root URL of the documentation of a crate. 558/// Try to get the root URL of the documentation of a crate.