From 68ee0337622f4025202687ccfac79c04d2046de8 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 9 Jun 2020 19:53:09 +1200 Subject: URL doc outputs in hover --- crates/ra_ide/Cargo.toml | 1 + crates/ra_ide/src/hover.rs | 83 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 16 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index e5fdf5aa1..fa925fc5a 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -34,6 +34,7 @@ ra_assists = { path = "../ra_assists" } ra_ssr = { path = "../ra_ssr" } ra_project_model = { path = "../ra_project_model" } ra_hir_def = { path = "../ra_hir_def" } +ra_tt = { path = "../ra_tt" } # ra_ide should depend only on the top-level `hir` package. if you need # 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 079195184..91167e30a 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -2,7 +2,7 @@ use std::iter::once; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Module, Documentation + ModuleSource, Semantics, Module, Documentation, AttrDef, Crate }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -13,6 +13,7 @@ use ra_ide_db::{ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use ra_project_model::ProjectWorkspace; use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId}; +use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf, Subtree, SmolStr}; use crate::{ display::{ @@ -415,7 +416,7 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor // If contains :: module-based link Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); - let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str) + let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); if let Some(resolved) = resolved { @@ -442,27 +443,77 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { +fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str, mode: UrlMode) -> Option { let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { return None; }; - let krate = definition.module(db)?.krate(); + let module = definition.module(db)?; + let krate = module.krate(); let import_map = db.import_map(krate.into()); - let base = import_map.path_of(ns).unwrap(); - let base = base.segments.iter().map(|name| format!("{}", name)).collect::(); - - doc_target_dirs - .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link))) - .inspect(|path| eprintln!("candidate {}", path.display())) - .filter(|path| path.exists()) - // slice out the UNC '\?\' added by canonicalize - .map(|path| format!("file:///{}", path.display())) - // \. is treated as an escape in vscode's markdown hover rendering - .map(|path_str| path_str.replace("\\", "/")) - .next() + // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, + // then hope rustdoc was run locally with `--document-private-items` + let base = import_map.path_of(ns)?; + let mut base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))); + + match mode { + UrlMode::Url => { + let root = get_doc_url(db, &krate); + let mut base = base.join("/"); + if let Some(url) = root { + eprintln!("root: {:?} base: {:?} link: {} root&base: {} root&base&link: {}", url, &base, link, url.join(&base).unwrap(), url.join(&base).unwrap().join(link).unwrap()); + if link.starts_with("#") { + base = base + "/" + }; + Some(url.join(&base)?.join(link)?.into_string()) + } else {None} + }, + UrlMode::File => { + let base = base.collect::(); + doc_target_dirs + .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link))) + .inspect(|path| eprintln!("candidate {}", path.display())) + .filter(|path| path.exists()) + .map(|path| format!("file:///{}", path.display())) + // \. is treated as an escape in vscode's markdown hover rendering + .map(|path_str| path_str.replace("\\", "/")) + .next() + } + } +} + +/// Try to get the root URL of the documentation of a crate. +fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { + // Look for #![doc(html_root_url = "https://docs.rs/...")] + let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); + let doc_attr_q = attrs.by_key("doc"); + let doc_url = if doc_attr_q.exists() { + doc_attr_q.tt_values().filter_map(|tt| match tt.token_trees.as_slice() { + &[ + TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident_text, ..})), + TokenTree::Leaf(Leaf::Punct(Punct{r#char: '=', ..})), + TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..})) + ] if ident_text == "html_root_url" => Some(text), + _ => { + None + } + }).next() + } else { + None + }; + eprintln!("url {:?}", doc_url); + + // TODO: It should be possible to fallback to `format!("https://docs.rs/{}/*", crate_name, *)` + let url = doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()); + eprintln!("url {:?}", url); + url } fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) -- cgit v1.2.3