From 2023af53f09ed9466c6d7442d6830276eba19b45 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 9 Jun 2020 15:43:57 +1200 Subject: Hover doc link rewriting --- crates/ra_ide/Cargo.toml | 4 ++ crates/ra_ide/src/hover.rs | 102 +++++++++++++++++++++++++++++++++-- crates/ra_ide/src/lib.rs | 5 +- crates/ra_syntax/src/ast/test.txt | 15 ++++++ crates/ra_syntax/src/ast/traits.rs | 36 +++++++++++++ crates/rust-analyzer/src/handlers.rs | 1 + 6 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 crates/ra_syntax/src/ast/test.txt (limited to 'crates') 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" log = "0.4.8" rustc-hash = "1.1.0" rand = { version = "0.7.3", features = ["small_rng"] } +comrak = "0.7.0" +url = "*" stdx = { path = "../stdx" } @@ -30,6 +32,8 @@ ra_prof = { path = "../ra_prof" } test_utils = { path = "../test_utils" } 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_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 c3e36a387..079195184 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,8 @@ use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, - Module, ModuleDef, ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, + ModuleSource, Semantics, Module, Documentation }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -11,6 +11,8 @@ use ra_ide_db::{ RootDatabase, }; 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 crate::{ display::{ @@ -65,6 +67,13 @@ pub struct HoverGotoTypeData { pub nav: NavigationTarget, } +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; +use comrak::nodes::NodeValue; +use url::Url; +use ra_ide_db::imports_locator::ImportsLocator; + /// Contains the results when hovering over an item #[derive(Debug, Default)] pub struct HoverResult { @@ -118,7 +127,7 @@ impl HoverResult { // // Shows additional information, like type of an expression or documentation for definition when "focusing" code. // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. -pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { +pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc>) -> Option> { let sema = Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let token = pick_best(file.token_at_offset(position.offset))?; @@ -138,7 +147,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Option>) -> Option { + // FIXME: Fail early + if let (Some(name), Some(module)) = (definition.name(db), definition.module(db)) { + let krate_name = module.krate().display_name(db)?; + let arena = Arena::new(); + let doc = parse_document(&arena, markdown, &ComrakOptions::default()); + let path = module.path_to_root(db); + let mut doc_target_dirs = workspaces + .iter() + .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) + .map(|workspace| workspace.workspace_root()) + // TODO: `target` is configurable in cargo config, we should respect it + .map(|root| root.join("target/doc")); + + iter_nodes(doc, &|node| { + match &mut node.data.borrow_mut().value { + &mut NodeValue::Link(ref mut link) => { + match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { + // If this is a valid absolute URL don't touch it + Ok(_) => (), + // If contains .html file-based link to new page + // If starts with #fragment file-based link to fragment on current page + // 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) + .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); + + if let Some(resolved) = resolved { + link.url = resolved.as_bytes().to_vec(); + } + + } + } + }, + _ => () + } + }); + let mut out = Vec::new(); + format_commonmark(doc, &ComrakOptions::default(), &mut out); + Some(String::from_utf8(out).unwrap()) + } else { + // eprintln!("WARN: Unable to determine name or module for hover; link rewriting disabled."); + None + } +} + +/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`) +fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { + None +} + +/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`) +fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { + let ns = if let Definition::ModuleDef(moddef) = definition { + ItemInNs::Types(moddef.clone().into()) + } else { + return None; + }; + let krate = definition.module(db)?.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() +} + +fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) + where F : Fn(&'a comrak::nodes::AstNode<'a>) { + f(node); + for c in node.children() { + iter_nodes(c, f); + } +} + fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); 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::{ LineIndexDatabase, }; use ra_syntax::{SourceFile, TextRange, TextSize}; +use ra_project_model::ProjectWorkspace; use crate::display::ToNav; @@ -389,8 +390,8 @@ impl Analysis { } /// Returns a short text describing element at position. - pub fn hover(&self, position: FilePosition) -> Cancelable>> { - self.with_db(|db| hover::hover(db, position)) + pub fn hover(&self, position: FilePosition, workspaces: Arc>) -> Cancelable>> { + self.with_db(|db| hover::hover(db, position, workspaces)) } /// 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 @@ +The context is a general utility struct provided on event dispatches, which +helps with dealing with the current "context" of the event dispatch. +The context also acts as a general high-level interface over the associated +[`Shard`] which received the event, or the low-level [`http`] module. + +The context contains "shortcuts", like for interacting with the shard. +Methods like [`set_activity`] will unlock the shard and perform an update for +you to save a bit of work. + +A context will only live for the event it was dispatched for. After the +event handler finished, it is destroyed and will not be re-used. + +[`Shard`]: ../gateway/struct.Shard.html +[`http`]: ../http/index.html +[`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 { self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast)) } } + +#[cfg(test)] +mod tests { + use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; + use comrak::nodes::{AstNode, NodeValue}; + + fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) + where F : Fn(&'a AstNode<'a>) { + f(node); + for c in node.children() { + iter_nodes(c, f); + } + } + + #[allow(non_snake_case)] + #[test] + fn test_link_rewrite() { + let src = include_str!("./test.txt"); + + let arena = Arena::new(); + let doc = parse_document(&arena, src, &ComrakOptions::default()); + + iter_nodes(doc, &|node| { + match &mut node.data.borrow_mut().value { + &mut NodeValue::Link(ref mut link) => { + link.url = "https://www.google.com".as_bytes().to_vec(); + }, + _ => () + } + }); + + let mut out = Vec::new(); + format_commonmark(doc, &ComrakOptions::default(), &mut out); + panic!("{}", String::from_utf8(out).unwrap()); + } +} 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 @@ use std::{ io::Write as _, process::{self, Stdio}, + sync::Arc }; use lsp_server::ErrorCode; -- cgit v1.2.3 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 From f497f6b4e048614094a9b38efe98cbb098013d18 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 9 Jun 2020 20:06:53 +1200 Subject: Tidy up --- crates/ra_ide/src/hover.rs | 120 ++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 67 deletions(-) (limited to 'crates') 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 @@ use std::iter::once; +use std::path::PathBuf; +use std::sync::Arc; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Module, Documentation, AttrDef, Crate + ModuleSource, Semantics, Documentation, AttrDef, Crate }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -12,8 +14,12 @@ 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 ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; +use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; + +use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; +use comrak::nodes::NodeValue; +use url::Url; use crate::{ display::{ @@ -68,13 +74,6 @@ pub struct HoverGotoTypeData { pub nav: NavigationTarget, } -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; -use comrak::nodes::NodeValue; -use url::Url; -use ra_ide_db::imports_locator::ImportsLocator; - /// Contains the results when hovering over an item #[derive(Debug, Default)] pub struct HoverResult { @@ -392,50 +391,42 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option>) -> Option { - // FIXME: Fail early - if let (Some(name), Some(module)) = (definition.name(db), definition.module(db)) { - let krate_name = module.krate().display_name(db)?; - let arena = Arena::new(); - let doc = parse_document(&arena, markdown, &ComrakOptions::default()); - let path = module.path_to_root(db); - let mut doc_target_dirs = workspaces - .iter() - .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) - .map(|workspace| workspace.workspace_root()) - // TODO: `target` is configurable in cargo config, we should respect it - .map(|root| root.join("target/doc")); - - iter_nodes(doc, &|node| { - match &mut node.data.borrow_mut().value { - &mut NodeValue::Link(ref mut link) => { - match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { - // If this is a valid absolute URL don't touch it - Ok(_) => (), - // If contains .html file-based link to new page - // If starts with #fragment file-based link to fragment on current page - // 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, UrlMode::Url) - .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); - - if let Some(resolved) = resolved { - link.url = resolved.as_bytes().to_vec(); - } - + let arena = Arena::new(); + let doc = parse_document(&arena, markdown, &ComrakOptions::default()); + let doc_target_dirs = workspaces + .iter() + .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) + .map(|workspace| workspace.workspace_root()) + // TODO: `target` is configurable in cargo config, we should respect it + .map(|root| root.join("target/doc")); + + iter_nodes(doc, &|node| { + match &mut node.data.borrow_mut().value { + &mut NodeValue::Link(ref mut link) => { + match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { + // If this is a valid absolute URL don't touch it + Ok(_) => (), + // If contains .html file-based link to new page + // If starts with #fragment file-based link to fragment on current page + // 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, UrlMode::Url) + .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); + + if let Some(resolved) = resolved { + link.url = resolved.as_bytes().to_vec(); } + } - }, - _ => () - } - }); - let mut out = Vec::new(); - format_commonmark(doc, &ComrakOptions::default(), &mut out); - Some(String::from_utf8(out).unwrap()) - } else { - // eprintln!("WARN: Unable to determine name or module for hover; link rewriting disabled."); - None - } + } + }, + _ => () + } + }); + let mut out = Vec::new(); + format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; + Some(String::from_utf8(out).unwrap()) } /// 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 { 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} + if link.starts_with("#") { + base = base + "/" + }; + root.and_then(|url| url.join(&base).ok()).and_then(|url| url.join(link).ok()).map(|url| url.into_string()) }, UrlMode::File => { let base = base.collect::(); @@ -491,7 +479,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator Option { - // Look for #![doc(html_root_url = "https://docs.rs/...")] + // Look for #![doc(html_root_url = "...")] 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() { @@ -500,20 +488,18 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { 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), + ] if ident_text == "html_root_url" => Some(text.to_string()), _ => { None } }).next() } else { - None + // Fallback to docs.rs + // TODO: Specify an exact version here (from Cargo.lock) + Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) }; - 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 + doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()) } fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) -- cgit v1.2.3 From 8f56e7c3b1220ed0b065e97a9061e59284ac1df1 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Wed, 10 Jun 2020 14:21:00 +1200 Subject: Generate correct symbol filename for relative links --- crates/ra_ide/Cargo.toml | 1 + crates/ra_ide/src/hover.rs | 49 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 10 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index fa925fc5a..219ad33e6 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -35,6 +35,7 @@ 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_hir_expand = { path = "../ra_hir_expand" } # 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 f703af434..760d7fe14 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -16,6 +16,7 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs use ra_project_model::ProjectWorkspace; use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; +use ra_hir_expand::name::AsName; use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; use comrak::nodes::NodeValue; @@ -406,9 +407,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { // If this is a valid absolute URL don't touch it Ok(_) => (), - // If contains .html file-based link to new page - // If starts with #fragment file-based link to fragment on current page - // If contains :: module-based link + // Otherwise there are two main possibilities + // path-based links: `../../module/struct.MyStruct.html` + // module-based links (AKA intra-doc links): `super::super::module::MyStruct` 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, UrlMode::Url) @@ -429,7 +430,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor Some(String::from_utf8(out).unwrap()) } -/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`) +/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). +/// +/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { None } @@ -439,7 +442,7 @@ enum UrlMode { File } -/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`) +/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). 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()) @@ -456,12 +459,12 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator { - let root = get_doc_url(db, &krate); let mut base = base.join("/"); - if link.starts_with("#") { - base = base + "/" - }; - root.and_then(|url| url.join(&base).ok()).and_then(|url| url.join(link).ok()).map(|url| url.into_string()) + get_doc_url(db, &krate) + .and_then(|url| url.join(&base).ok()) + .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) + .and_then(|url| url.join(link).ok()) + .map(|url| url.into_string()) }, UrlMode::File => { let base = base.collect::(); @@ -502,6 +505,32 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()) } +/// Get the filename and extension generated for a symbol by rustdoc. +/// +/// Example: `struct.Shard.html` +fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option { + Some(match definition { + Definition::ModuleDef(def) => match def { + ModuleDef::Adt(adt) => match adt { + Adt::Struct(s) => format!("struct.{}.html", s.name(db)), + Adt::Enum(e) => format!("enum.{}.html", e.name(db)), + Adt::Union(u) => format!("union.{}.html", u.name(db)) + }, + ModuleDef::Module(_) => "index.html".to_string(), + ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), + ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), + ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), + ModuleDef::EnumVariant(ev) => format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)), + ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), + // TODO: Check this is the right prefix + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?) + }, + Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), + _ => None? + }) +} + fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) where F : Fn(&'a comrak::nodes::AstNode<'a>) { f(node); -- cgit v1.2.3 From 5f52a516dedeab16ede8c26807c4ff79b3d308d3 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Fri, 12 Jun 2020 15:02:48 +1200 Subject: Working intra-doc-links --- crates/ra_ide/Cargo.toml | 2 + crates/ra_ide/src/hover.rs | 119 ++++++++++++++++++++++++++++++++++++++-- crates/ra_syntax/src/lib.rs | 2 +- crates/ra_syntax/src/parsing.rs | 2 +- 4 files changed, 117 insertions(+), 8 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 219ad33e6..1bc095c5b 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -19,6 +19,7 @@ rustc-hash = "1.1.0" rand = { version = "0.7.3", features = ["small_rng"] } comrak = "0.7.0" url = "*" +maplit = "*" stdx = { path = "../stdx" } @@ -36,6 +37,7 @@ ra_project_model = { path = "../ra_project_model" } ra_hir_def = { path = "../ra_hir_def" } ra_tt = { path = "../ra_tt" } ra_hir_expand = { path = "../ra_hir_expand" } +ra_parser = { path = "../ra_parser" } # 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 760d7fe14..f4b10deac 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Documentation, AttrDef, Crate + ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -12,11 +12,13 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; use ra_project_model::ProjectWorkspace; -use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase}; +use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver}; use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; use ra_hir_expand::name::AsName; +use ra_parser::FragmentKind; +use maplit::{hashset, hashmap}; use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; use comrak::nodes::NodeValue; @@ -412,8 +414,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor // module-based links (AKA intra-doc links): `super::super::module::MyStruct` Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); + let link_text = String::from_utf8(link.title.clone()).unwrap(); 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)); + .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_text, &link_str)); if let Some(resolved) = resolved { link.url = resolved.as_bytes().to_vec(); @@ -430,11 +433,113 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor Some(String::from_utf8(out).unwrap()) } +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +enum Namespace { + Types, + Values, + Macros +} + +impl Namespace { + /// Extract the specified namespace from an intra-doc-link if one exists. + fn from_intra_spec(s: &str) -> Option { + let ns_map = hashmap!{ + Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), + Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), + Self::Macros => (hashset!{"macro"}, hashset!{"!"}) + }; + + ns_map + .iter() + .filter(|(ns, (prefixes, suffixes))| { + prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || + suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) + }) + .map(|(ns, (_, _))| *ns) + .next() + } +} + /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). /// /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { - None +fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link_text: &str, link_target: &str) -> Option { + eprintln!("try_resolve_intra"); + + // Set link_target for implied shortlinks + let link_target = if link_target.is_empty() { + link_text.trim_matches('`') + } else { + link_target + }; + + // Parse link as a module path + // This expects a full document, which a single path isn't, but we can just ignore the errors. + let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0); + let path = parsed.descendants().filter_map(Path::cast).next()?; + let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); + + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes + let resolver = { + use ra_hir_def::*; + use hir::*; + + // TODO: This should be replaced by implementing HasResolver/TryHasResolver on ModuleDef and Definition. + match definition { + Definition::ModuleDef(def) => match def { + ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), + ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), + ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), + ModuleDef::EnumVariant(ev) => Into::::into(Into::::into(ev.clone())).resolver(db), + ModuleDef::Const(c) => Into::::into(Into::::into(c.clone())).resolver(db), + ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), + ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), + ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), + // TODO: This should be a resolver relative to `std` + ModuleDef::BuiltinType(t) => Into::::into(definition.module(db)?).resolver(db) + }, + Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), + Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), + Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), + // it's possible, read probable, that other arms of this are also unreachable + Definition::Local(local) => unreachable!(), + Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) + } + }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + let resolved = resolver.resolve_module_path_in_items(db, &modpath); + let (defid, namespace) = match namespace { + // TODO: .or(resolved.macros) + None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + Some(ns @ Namespace::Types) => (resolved.types?.0, ns), + Some(ns @ Namespace::Values) => (resolved.values?.0, ns), + // TODO: + Some(Namespace::Macros) => None? + }; + + // Get the filepath of the final symbol + let def: ModuleDef = defid.into(); + let module = def.module(db)?; + let krate = module.krate(); + let ns = match namespace { + Namespace::Types => ItemInNs::Types(defid), + Namespace::Values => ItemInNs::Values(defid), + // TODO: + Namespace::Macros => None? + }; + let import_map = db.import_map(krate.into()); + let path = import_map.path_of(ns)?; + + Some( + get_doc_url(db, &krate)? + .join(&format!("{}/", krate.display_name(db)?)).ok()? + .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()? + .join(&get_symbol_filename(db, definition)?).ok()? + .into_string() + ) } enum UrlMode { @@ -444,6 +549,7 @@ enum UrlMode { /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str, mode: UrlMode) -> Option { + eprintln!("try_resolve_path"); let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { @@ -481,6 +587,7 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index 9b7664576..7a2c44cd2 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -47,7 +47,7 @@ use crate::syntax_node::GreenNode; pub use crate::{ algo::InsertPosition, ast::{AstNode, AstToken}, - parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token}, + parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token, parse_text}, ptr::{AstPtr, SyntaxNodePtr}, syntax_error::SyntaxError, syntax_node::{ diff --git a/crates/ra_syntax/src/parsing.rs b/crates/ra_syntax/src/parsing.rs index 0ed3c20ef..4ec0b8d59 100644 --- a/crates/ra_syntax/src/parsing.rs +++ b/crates/ra_syntax/src/parsing.rs @@ -15,7 +15,7 @@ pub use lexer::*; pub(crate) use self::reparsing::incremental_reparse; use ra_parser::SyntaxKind; -pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec) { +pub fn parse_text(text: &str) -> (GreenNode, Vec) { let (tokens, lexer_errors) = tokenize(&text); let mut token_source = TextTokenSource::new(text, &tokens); -- cgit v1.2.3 From 108b953254e46851130e427b41cd143eee07fd02 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 13 Jun 2020 16:13:32 +1200 Subject: Remove local documentation link rewriting --- crates/ra_ide/src/hover.rs | 69 ++++++++++-------------------------- crates/ra_ide/src/lib.rs | 5 ++- crates/ra_syntax/src/ast/test.txt | 15 -------- crates/ra_syntax/src/ast/traits.rs | 36 ------------------- crates/rust-analyzer/src/handlers.rs | 2 +- 5 files changed, 22 insertions(+), 105 deletions(-) delete mode 100644 crates/ra_syntax/src/ast/test.txt (limited to 'crates') 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 @@ use std::iter::once; -use std::path::PathBuf; -use std::sync::Arc; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Documentation, AttrDef, Crate, GenericDef, ModPath, Hygiene + ModuleSource, Semantics, Documentation, AttrDef, Crate, ModPath, Hygiene }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -13,11 +11,9 @@ use ra_ide_db::{ RootDatabase, }; use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; -use ra_project_model::ProjectWorkspace; -use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, GenericDefId, ModuleId, resolver::HasResolver}; +use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, resolver::HasResolver}; use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; use ra_hir_expand::name::AsName; -use ra_parser::FragmentKind; use maplit::{hashset, hashmap}; use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; @@ -130,7 +126,7 @@ impl HoverResult { // // Shows additional information, like type of an expression or documentation for definition when "focusing" code. // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. -pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc>) -> Option> { +pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { let sema = Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let token = pick_best(file.token_at_offset(position.offset))?; @@ -150,7 +146,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition, workspaces: Arc Option>) -> Option { +fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option { let arena = Arena::new(); let doc = parse_document(&arena, markdown, &ComrakOptions::default()); - let doc_target_dirs = workspaces - .iter() - .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) - .map(|workspace| workspace.workspace_root()) - // TODO: `target` is configurable in cargo config, we should respect it - .map(|root| root.join("target/doc")); iter_nodes(doc, &|node| { match &mut node.data.borrow_mut().value { @@ -415,8 +405,8 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, wor Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); let link_text = String::from_utf8(link.title.clone()).unwrap(); - 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_text, &link_str)); + let resolved = try_resolve_path(db, definition, &link_str) + .or_else(|| try_resolve_intra(db, definition, &link_text, &link_str)); if let Some(resolved) = resolved { link.url = resolved.as_bytes().to_vec(); @@ -451,7 +441,7 @@ impl Namespace { ns_map .iter() - .filter(|(ns, (prefixes, suffixes))| { + .filter(|(_ns, (prefixes, suffixes))| { prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) }) @@ -463,7 +453,7 @@ impl Namespace { /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). /// /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link_text: &str, link_target: &str) -> Option { +fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str, link_target: &str) -> Option { eprintln!("try_resolve_intra"); // Set link_target for implied shortlinks @@ -496,13 +486,13 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator Into::::into(t.clone()).resolver(db), ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), // TODO: This should be a resolver relative to `std` - ModuleDef::BuiltinType(t) => Into::::into(definition.module(db)?).resolver(db) + ModuleDef::BuiltinType(_t) => Into::::into(definition.module(db)?).resolver(db) }, Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), // it's possible, read probable, that other arms of this are also unreachable - Definition::Local(local) => unreachable!(), + Definition::Local(_local) => unreachable!(), Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) } }; @@ -542,13 +532,8 @@ fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str, mode: UrlMode) -> Option { +fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { eprintln!("try_resolve_path"); let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) @@ -561,29 +546,13 @@ fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator { - let mut base = base.join("/"); - get_doc_url(db, &krate) - .and_then(|url| url.join(&base).ok()) - .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) - .and_then(|url| url.join(link).ok()) - .map(|url| url.into_string()) - }, - 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() - } - } + let base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))).join("/"); + + get_doc_url(db, &krate) + .and_then(|url| url.join(&base).ok()) + .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) + .and_then(|url| url.join(link).ok()) + .map(|url| url.into_string()) } /// Try to get the root URL of the documentation of a crate. diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index d56d52d30..ecac5134e 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -54,7 +54,6 @@ use ra_ide_db::{ LineIndexDatabase, }; use ra_syntax::{SourceFile, TextRange, TextSize}; -use ra_project_model::ProjectWorkspace; use crate::display::ToNav; @@ -390,8 +389,8 @@ impl Analysis { } /// Returns a short text describing element at position. - pub fn hover(&self, position: FilePosition, workspaces: Arc>) -> Cancelable>> { - self.with_db(|db| hover::hover(db, position, workspaces)) + pub fn hover(&self, position: FilePosition) -> Cancelable>> { + self.with_db(|db| hover::hover(db, position)) } /// 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 deleted file mode 100644 index f746bf1e7..000000000 --- a/crates/ra_syntax/src/ast/test.txt +++ /dev/null @@ -1,15 +0,0 @@ -The context is a general utility struct provided on event dispatches, which -helps with dealing with the current "context" of the event dispatch. -The context also acts as a general high-level interface over the associated -[`Shard`] which received the event, or the low-level [`http`] module. - -The context contains "shortcuts", like for interacting with the shard. -Methods like [`set_activity`] will unlock the shard and perform an update for -you to save a bit of work. - -A context will only live for the event it was dispatched for. After the -event handler finished, it is destroyed and will not be re-used. - -[`Shard`]: ../gateway/struct.Shard.html -[`http`]: ../http/index.html -[`set_activity`]: #method.set_activity diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index 323d78bbc..a8f2454fd 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs @@ -146,39 +146,3 @@ impl Iterator for CommentIter { self.iter.by_ref().find_map(|el| el.into_token().and_then(ast::Comment::cast)) } } - -#[cfg(test)] -mod tests { - use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; - use comrak::nodes::{AstNode, NodeValue}; - - fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) - where F : Fn(&'a AstNode<'a>) { - f(node); - for c in node.children() { - iter_nodes(c, f); - } - } - - #[allow(non_snake_case)] - #[test] - fn test_link_rewrite() { - let src = include_str!("./test.txt"); - - let arena = Arena::new(); - let doc = parse_document(&arena, src, &ComrakOptions::default()); - - iter_nodes(doc, &|node| { - match &mut node.data.borrow_mut().value { - &mut NodeValue::Link(ref mut link) => { - link.url = "https://www.google.com".as_bytes().to_vec(); - }, - _ => () - } - }); - - let mut out = Vec::new(); - format_commonmark(doc, &ComrakOptions::default(), &mut out); - panic!("{}", String::from_utf8(out).unwrap()); - } -} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 19da25f96..951006771 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -548,7 +548,7 @@ pub(crate) fn handle_hover( ) -> Result> { let _p = profile("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis.hover(position)? { + let info = match snap.analysis().hover(position)? { None => return Ok(None), Some(info) => info, }; -- cgit v1.2.3 From 1d6f291335c58aac95c1124f55d7fb0834baff2a Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 13 Jun 2020 22:34:59 +1200 Subject: Move resolver into impls, work on tests --- crates/ra_hir/src/code_model.rs | 37 ++++++--- crates/ra_ide/src/hover.rs | 148 ++++++++++++++++++++++------------- crates/ra_ide/src/mock_analysis.rs | 2 +- crates/ra_ide_db/Cargo.toml | 1 + crates/ra_ide_db/src/defs.rs | 17 +++- crates/rust-analyzer/src/handlers.rs | 3 +- 6 files changed, 137 insertions(+), 71 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e86077dd6..5766cc3b8 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -186,21 +186,34 @@ impl ModuleDef { module.visibility_of(db, self) } - pub fn name(self, db: &dyn HirDatabase) -> Option { + pub fn name(&self, db: &dyn HirDatabase) -> Option { match self { - ModuleDef::Adt(it) => Some(it.name(db)), - ModuleDef::Trait(it) => Some(it.name(db)), - ModuleDef::Function(it) => Some(it.name(db)), - ModuleDef::EnumVariant(it) => Some(it.name(db)), - ModuleDef::TypeAlias(it) => Some(it.name(db)), - - ModuleDef::Module(it) => it.name(db), - ModuleDef::Const(it) => it.name(db), - ModuleDef::Static(it) => it.name(db), - - ModuleDef::BuiltinType(it) => Some(it.as_name()), + ModuleDef::Module(m) => m.name(db), + ModuleDef::Function(m) => Some(m.name(db)), + ModuleDef::Adt(m) => Some(m.name(db)), + ModuleDef::EnumVariant(m) => Some(m.name(db)), + ModuleDef::Const(m) => {m.name(db)}, + ModuleDef::Static(m) => {m.name(db)}, + ModuleDef::Trait(m) => {Some(m.name(db))}, + ModuleDef::TypeAlias(m) => {Some(m.name(db))}, + ModuleDef::BuiltinType(m) => {Some(m.as_name())} } } + + pub fn resolver(&self, db: &D) -> Option { + Some(match self { + ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), + ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), + ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), + ModuleDef::EnumVariant(ev) => Into::::into(Into::::into(ev.clone())).resolver(db), + ModuleDef::Const(c) => Into::::into(Into::::into(c.clone())).resolver(db), + ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), + ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), + ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), + // TODO: This should be a resolver relative to `std` + ModuleDef::BuiltinType(_t) => None? + }) + } } pub use hir_def::{ diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 0da10a08e..34fc36a1f 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -146,7 +146,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); let link_text = String::from_utf8(link.title.clone()).unwrap(); - let resolved = try_resolve_path(db, definition, &link_str) - .or_else(|| try_resolve_intra(db, definition, &link_text, &link_str)); + let resolved = try_resolve_intra(db, definition, &link_text, &link_str) + .or_else(|| try_resolve_path(db, definition, &link_str)); if let Some(resolved) = resolved { link.url = resolved.as_bytes().to_vec(); @@ -420,7 +421,7 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> }); let mut out = Vec::new(); format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; - Some(String::from_utf8(out).unwrap()) + Some(String::from_utf8(out).unwrap().trim().to_string()) } #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] @@ -470,32 +471,7 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); // Resolve it relative to symbol's location (according to the RFC this should consider small scopes - let resolver = { - use ra_hir_def::*; - use hir::*; - - // TODO: This should be replaced by implementing HasResolver/TryHasResolver on ModuleDef and Definition. - match definition { - Definition::ModuleDef(def) => match def { - ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), - ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), - ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), - ModuleDef::EnumVariant(ev) => Into::::into(Into::::into(ev.clone())).resolver(db), - ModuleDef::Const(c) => Into::::into(Into::::into(c.clone())).resolver(db), - ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), - ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), - ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), - // TODO: This should be a resolver relative to `std` - ModuleDef::BuiltinType(_t) => Into::::into(definition.module(db)?).resolver(db) - }, - Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), - Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), - Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), - // it's possible, read probable, that other arms of this are also unreachable - Definition::Local(_local) => unreachable!(), - Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) - } - }; + let resolver = definition.resolver(db)?; // Namespace disambiguation let namespace = Namespace::from_intra_spec(link_target); @@ -527,7 +503,7 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str get_doc_url(db, &krate)? .join(&format!("{}/", krate.display_name(db)?)).ok()? .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()? - .join(&get_symbol_filename(db, definition)?).ok()? + .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?).ok()? .into_string() ) } @@ -637,11 +613,20 @@ mod tests { use crate::mock_analysis::analysis_and_position; - fn trim_markup(s: &str) -> &str { - s.trim_start_matches("```rust\n").trim_end_matches("\n```") + fn trim_markup(s: &str) -> String { + s + .replace("``` rust", "```rust") + .replace("-----", "___") + .replace("\n\n___\n\n", "\n___\n\n") + .replace("\\<-", "<-") + .trim_start_matches("test\n```\n\n") + .trim_start_matches("```rust\n") + .trim_start_matches("test\n```\n\n```rust\n") + .trim_end_matches("\n```") + .to_string() } - fn trim_markup_opt(s: Option<&str>) -> Option<&str> { + fn trim_markup_opt(s: Option<&str>) -> Option { s.map(trim_markup) } @@ -689,7 +674,7 @@ fn main() { ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(hover.range, TextRange::new(58.into(), 63.into())); - assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("u32")); } #[test] @@ -818,7 +803,7 @@ fn main() { }; } "#, - &["Foo\n```\n\n```rust\nfield_a: u32"], + &["test::Foo\n```\n\n```rust\nfield_a: u32"], ); // Hovering over the field in the definition @@ -835,7 +820,7 @@ fn main() { }; } "#, - &["Foo\n```\n\n```rust\nfield_a: u32"], + &["test::Foo\n```\n\n```rust\nfield_a: u32"], ); } @@ -888,7 +873,7 @@ fn main() { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("test::Option\n```\n\n```rust\nSome")); let (analysis, position) = analysis_and_position( " @@ -901,7 +886,7 @@ fn main() { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Option")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("Option")); } #[test] @@ -915,7 +900,7 @@ fn main() { } "#, &[" -Option +test::Option ``` ```rust @@ -940,7 +925,7 @@ The None variant } "#, &[" -Option +test::Option ``` ```rust @@ -958,14 +943,14 @@ The Some variant fn hover_for_local_variable() { let (analysis, position) = analysis_and_position("fn func(foo: i32) { fo<|>o; }"); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] fn hover_for_local_variable_pat() { let (analysis, position) = analysis_and_position("fn func(fo<|>o: i32) {}"); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] @@ -976,14 +961,14 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] fn hover_for_param_edge() { let (analysis, position) = analysis_and_position("fn func(<|>foo: i32) {}"); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] @@ -1004,7 +989,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("Thing")); } #[test] @@ -1028,8 +1013,8 @@ fn func(foo: i32) { if true { <|>foo; }; } ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!( - trim_markup_opt(hover.info.first()), - Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") + trim_markup_opt(hover.info.first()).as_deref(), + Some("test::wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") ); } @@ -1052,7 +1037,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("const C: u32")); } #[test] @@ -1068,7 +1053,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("Thing")); /* FIXME: revive these tests let (analysis, position) = analysis_and_position( @@ -1125,7 +1110,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] @@ -1142,7 +1127,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("macro_rules! foo")); } #[test] @@ -1153,7 +1138,7 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("i32")); } #[test] @@ -1414,6 +1399,59 @@ fn func(foo: i32) { if true { <|>foo; }; } ); } + #[test] + fn test_hover_path_link() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [Foo](struct.Foo.html) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + ); + } + + #[test] + fn test_hover_intra_link() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [Foo](Foo) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + ); + } + + #[test] + fn test_hover_intra_link_shortlink() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [Foo] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + ); + } + + #[test] + fn test_hover_intra_link_namespaced() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + fn Foo() {} + /// [Foo()] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + ); + } + #[test] fn test_hover_macro_generated_struct_fn_doc_comment() { mark::check!(hover_macro_generated_struct_fn_doc_comment); @@ -1438,7 +1476,7 @@ fn func(foo: i32) { if true { <|>foo; }; } bar.fo<|>o(); } "#, - &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + &["test::Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], ); } @@ -1466,7 +1504,7 @@ fn func(foo: i32) { if true { <|>foo; }; } bar.fo<|>o(); } "#, - &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + &["test::Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], ); } diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 889b84c59..490ee0dc3 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs @@ -99,7 +99,7 @@ impl MockAnalysis { root_crate = Some(crate_graph.add_crate_root( file_id, edition, - None, + Some(CrateName::new("test").unwrap()), cfg, env, Default::default(), diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml index c3921bd40..b14206c9b 100644 --- a/crates/ra_ide_db/Cargo.toml +++ b/crates/ra_ide_db/Cargo.toml @@ -24,6 +24,7 @@ ra_text_edit = { path = "../ra_text_edit" } ra_db = { path = "../ra_db" } ra_prof = { path = "../ra_prof" } test_utils = { path = "../test_utils" } +ra_hir_def = { path = "../ra_hir_def" } # 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_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 3ef5e74b6..a158169e3 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -7,7 +7,7 @@ use hir::{ Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, - Semantics, TypeParam, Visibility, + Semantics, TypeParam, Visibility, db::{DefDatabase, HirDatabase}, }; use ra_prof::profile; use ra_syntax::{ @@ -16,6 +16,7 @@ use ra_syntax::{ }; use crate::RootDatabase; +use ra_hir_def::resolver::{Resolver, HasResolver}; // FIXME: a more precise name would probably be `Symbol`? #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -76,6 +77,20 @@ impl Definition { }; Some(name) } + + pub fn resolver(&self, db: &D) -> Option { + use hir::VariantDef; + use ra_hir_def::*; + Some(match self { + Definition::ModuleDef(def) => def.resolver(db)?, + Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), + Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), + Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), + // it's possible, read probable, that other arms of this are also unreachable + Definition::Local(_local) => unreachable!(), + Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) + }) + } } #[derive(Debug)] diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 951006771..6c0d3e19c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -4,8 +4,7 @@ use std::{ io::Write as _, - process::{self, Stdio}, - sync::Arc + process::{self, Stdio} }; use lsp_server::ErrorCode; -- cgit v1.2.3 From 6bba4158cb8938af6e9b128c2c01b15415d502ad Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 15 Jun 2020 14:47:33 +1200 Subject: Switch to pulldown-cmark, tidy imports --- crates/ra_hir/src/lib.rs | 6 +- crates/ra_ide/Cargo.toml | 5 +- crates/ra_ide/src/hover.rs | 333 +++++++++++++++++++++++++---------- crates/rust-analyzer/src/handlers.rs | 2 +- 4 files changed, 250 insertions(+), 96 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 3364a822f..fe34b30bc 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -66,12 +66,14 @@ pub use hir_def::{ body::scope::ExprScopes, builtin_type::BuiltinType, docs::Documentation, + item_scope::ItemInNs, nameres::ModuleSource, path::{ModPath, Path, PathKind}, type_ref::Mutability, }; pub use hir_expand::{ - hygiene::Hygiene, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, - MacroFile, Origin, + hygiene::Hygiene, + name::{AsName, Name}, + HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, Origin, }; pub use hir_ty::{display::HirDisplay, CallableDef}; diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 1bc095c5b..642b71937 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -17,9 +17,11 @@ itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" rand = { version = "0.7.3", features = ["small_rng"] } -comrak = "0.7.0" url = "*" maplit = "*" +lazy_static = "*" +pulldown-cmark-to-cmark = "4.0.2" +pulldown-cmark = "0.7.0" stdx = { path = "../stdx" } @@ -36,7 +38,6 @@ 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_hir_expand = { path = "../ra_hir_expand" } ra_parser = { path = "../ra_parser" } # ra_ide should depend only on the top-level `hir` package. if you need diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 34fc36a1f..82aa24f4f 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,23 +1,25 @@ +use std::collections::{HashMap, HashSet}; use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, Documentation, AttrDef, Crate, ModPath, Hygiene + db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, + FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, ModuleDef, ModuleSource, + Semantics, Module }; use itertools::Itertools; +use lazy_static::lazy_static; +use maplit::{hashmap, hashset}; +use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; +use pulldown_cmark_to_cmark::cmark; use ra_db::SourceDatabase; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; -use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, resolver::HasResolver}; -use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; -use ra_hir_expand::name::AsName; -use maplit::{hashset, hashmap}; - -use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; -use comrak::nodes::NodeValue; +use ra_syntax::{ + ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, +}; +use ra_tt::{Ident, Leaf, Literal, Punct, TokenTree}; use url::Url; use crate::{ @@ -389,80 +391,143 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option( + events: impl Iterator>, + callback: impl Fn(&str, &str) -> String, +) -> impl Iterator> { + let mut in_link = false; + let mut link_text = CowStr::Borrowed(""); + events.map(move |evt| match evt { + Event::Start(Tag::Link(..)) => { + in_link = true; + evt + } + Event::End(Tag::Link(link_type, target, _)) => { + in_link = false; + let target = callback(&target, &link_text); + Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed(""))) + } + Event::Text(s) if in_link => { + link_text = s.clone(); + Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + } + Event::Code(s) if in_link => { + link_text = s.clone(); + Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + } + _ => evt, + }) +} + /// Rewrite documentation links in markdown to point to local documentation/docs.rs fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option { - let arena = Arena::new(); - let doc = parse_document(&arena, markdown, &ComrakOptions::default()); - - iter_nodes(doc, &|node| { - match &mut node.data.borrow_mut().value { - &mut NodeValue::Link(ref mut link) => { - match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { - // If this is a valid absolute URL don't touch it - Ok(_) => (), - // Otherwise there are two main possibilities - // path-based links: `../../module/struct.MyStruct.html` - // module-based links (AKA intra-doc links): `super::super::module::MyStruct` - Err(_) => { - let link_str = String::from_utf8(link.url.clone()).unwrap(); - let link_text = String::from_utf8(link.title.clone()).unwrap(); - let resolved = try_resolve_intra(db, definition, &link_text, &link_str) - .or_else(|| try_resolve_path(db, definition, &link_str)); - - if let Some(resolved) = resolved { - link.url = resolved.as_bytes().to_vec(); - } - - } + let doc = Parser::new_with_broken_link_callback( + markdown, + Options::empty(), + Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), + ); + + let doc = map_links(doc, |target, title: &str| { + match Url::parse(target) { + // If this is a valid absolute URL don't touch it + Ok(_) => target.to_string(), + // Otherwise there are two main possibilities + // path-based links: `../../module/struct.MyStruct.html` + // module-based links (AKA intra-doc links): `super::super::module::MyStruct` + Err(_) => { + let resolved = try_resolve_intra(db, definition, title, &target) + .or_else(|| try_resolve_path(db, definition, &target)); + + if let Some(resolved) = resolved { + resolved + } else { + target.to_string() } - }, - _ => () + } } }); - let mut out = Vec::new(); - format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; - Some(String::from_utf8(out).unwrap().trim().to_string()) + let mut out = String::new(); + cmark(doc, &mut out, None).ok(); + Some(out) } #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] enum Namespace { Types, Values, - Macros + Macros, } +lazy_static!( + /// Map of namespaces to identifying prefixes and suffixes as defined by RFC1946. + static ref NS_MAP: HashMap, HashSet<&'static str>)> = hashmap!{ + Namespace::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), + Namespace::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), + Namespace::Macros => (hashset!{"macro"}, hashset!{"!"}) + }; +); + impl Namespace { /// Extract the specified namespace from an intra-doc-link if one exists. fn from_intra_spec(s: &str) -> Option { - let ns_map = hashmap!{ - Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), - Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), - Self::Macros => (hashset!{"macro"}, hashset!{"!"}) - }; - - ns_map + NS_MAP .iter() .filter(|(_ns, (prefixes, suffixes))| { - prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || - suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) + prefixes + .iter() + .map(|prefix| { + s.starts_with(prefix) + && s.chars() + .nth(prefix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + || suffixes + .iter() + .map(|suffix| { + s.starts_with(suffix) + && s.chars() + .nth(suffix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) }) .map(|(ns, (_, _))| *ns) .next() } } +// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| { + prefixes.iter().for_each(|prefix| s = s.trim_start_matches(prefix)); + suffixes.iter().for_each(|suffix| s = s.trim_end_matches(suffix)); + }); + s.trim_start_matches("@").trim() +} + /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). /// /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str, link_target: &str) -> Option { - eprintln!("try_resolve_intra"); - +fn try_resolve_intra( + db: &RootDatabase, + definition: &Definition, + link_text: &str, + link_target: &str, +) -> Option { // Set link_target for implied shortlinks - let link_target = if link_target.is_empty() { - link_text.trim_matches('`') - } else { - link_target - }; + let link_target = + if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + // Strip prefixes/suffixes + let link_target = strip_prefixes_suffixes(link_target); // Parse link as a module path // This expects a full document, which a single path isn't, but we can just ignore the errors. @@ -473,17 +538,17 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str // Resolve it relative to symbol's location (according to the RFC this should consider small scopes let resolver = definition.resolver(db)?; - // Namespace disambiguation - let namespace = Namespace::from_intra_spec(link_target); - let resolved = resolver.resolve_module_path_in_items(db, &modpath); let (defid, namespace) = match namespace { // TODO: .or(resolved.macros) - None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + None => resolved + .types + .map(|t| (t.0, Namespace::Types)) + .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, Some(ns @ Namespace::Types) => (resolved.types?.0, ns), Some(ns @ Namespace::Values) => (resolved.values?.0, ns), // TODO: - Some(Namespace::Macros) => None? + Some(Namespace::Macros) => None?, }; // Get the filepath of the final symbol @@ -494,23 +559,28 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str Namespace::Types => ItemInNs::Types(defid), Namespace::Values => ItemInNs::Values(defid), // TODO: - Namespace::Macros => None? + Namespace::Macros => None?, }; let import_map = db.import_map(krate.into()); let path = import_map.path_of(ns)?; Some( get_doc_url(db, &krate)? - .join(&format!("{}/", krate.display_name(db)?)).ok()? - .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()? - .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?).ok()? - .into_string() + .join(&format!("{}/", krate.display_name(db)?)) + .ok()? + .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")) + .ok()? + .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) + .ok()? + .into_string(), ) } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { - eprintln!("try_resolve_path"); + if !link.contains("#") && !link.contains(".html") { + return None; + } let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { @@ -522,11 +592,15 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O // 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 base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))).join("/"); + let base = once(format!("{}", krate.display_name(db)?)) + .chain(base.segments.iter().map(|name| format!("{}", name))) + .join("/"); get_doc_url(db, &krate) .and_then(|url| url.join(&base).ok()) - .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) + .and_then(|url| { + get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() + }) .and_then(|url| url.join(link).ok()) .map(|url| url.into_string()) } @@ -566,31 +640,25 @@ fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option match adt { Adt::Struct(s) => format!("struct.{}.html", s.name(db)), Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)) + Adt::Union(u) => format!("union.{}.html", u.name(db)), }, ModuleDef::Module(_) => "index.html".to_string(), ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::EnumVariant(ev) => format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)), + ModuleDef::EnumVariant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), // TODO: Check this is the right prefix - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?) + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), }, Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), - _ => None? + _ => None?, }) } -fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) - where F : Fn(&'a comrak::nodes::AstNode<'a>) { - f(node); - for c in node.children() { - iter_nodes(c, f); - } -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { @@ -614,12 +682,11 @@ mod tests { use crate::mock_analysis::analysis_and_position; fn trim_markup(s: &str) -> String { - s - .replace("``` rust", "```rust") - .replace("-----", "___") - .replace("\n\n___\n\n", "\n___\n\n") + s.trim() + .replace("````", "```") + .replace("---", "___") .replace("\\<-", "<-") - .trim_start_matches("test\n```\n\n") + .replace("```\n\n___", "```\n___") .trim_start_matches("```rust\n") .trim_start_matches("test\n```\n\n```rust\n") .trim_end_matches("\n```") @@ -873,7 +940,10 @@ fn main() { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("test::Option\n```\n\n```rust\nSome")); + assert_eq!( + trim_markup_opt(hover.info.first()).as_deref(), + Some("test::Option\n```\n\n```rust\nSome") + ); let (analysis, position) = analysis_and_position( " @@ -1408,7 +1478,7 @@ fn func(foo: i32) { if true { <|>foo; }; } /// [Foo](struct.Foo.html) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1421,7 +1491,7 @@ fn func(foo: i32) { if true { <|>foo; }; } /// [Foo](Foo) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1434,7 +1504,20 @@ fn func(foo: i32) { if true { <|>foo; }; } /// [Foo] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_code() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], ); } @@ -1448,7 +1531,75 @@ fn func(foo: i32) { if true { <|>foo; }; } /// [Foo()] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_namspaced_code() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`struct Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_shortlink_namspaced_code_with_at() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [`struct@Foo`] + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link_reference() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [my Foo][foo] + /// + /// [foo]: Foo + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_external_url() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [external](https://www.google.com) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"], + ); + } + + // Check that we don't rewrite links which we can't identify + #[test] + fn test_hover_unknown_target() { + check_hover_result( + r" + //- /lib.rs + pub struct Foo; + /// [baz](Baz) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[baz](Baz)"], ); } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6c0d3e19c..c8c8b886d 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -547,7 +547,7 @@ pub(crate) fn handle_hover( ) -> Result> { let _p = profile("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis().hover(position)? { + let info = match snap.analysis.hover(position)? { None => return Ok(None), Some(info) => info, }; -- cgit v1.2.3 From 73d5a31450e648698ad31531f21ac5659cc271ac Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 16 Jun 2020 15:51:07 +1200 Subject: Update missed heavy test --- crates/rust-analyzer/tests/heavy_tests/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index cc079790e..ea80705aa 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -674,5 +674,5 @@ pub fn foo(_input: TokenStream) -> TokenStream { }); let value = res.get("contents").unwrap().get("value").unwrap().to_string(); - assert_eq!(value, r#""```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#) + assert_eq!(value, r#""\n````rust\nfoo::Bar\n````\n\n````rust\nfn bar()\n````""#) } -- cgit v1.2.3 From f9893d370c4c3b015698169d27d2833742441560 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 16 Jun 2020 16:08:40 +1200 Subject: Remove old comment, remove unecessary local --- crates/ra_ide/src/hover.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 82aa24f4f..9f8ab4795 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -589,11 +589,8 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O let module = definition.module(db)?; let krate = module.krate(); let import_map = db.import_map(krate.into()); - // 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 base = once(format!("{}", krate.display_name(db)?)) - .chain(base.segments.iter().map(|name| format!("{}", name))) + .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) .join("/"); get_doc_url(db, &krate) -- cgit v1.2.3 From 10c50b140aa67f74a135e7a48ff6d954cc757cb8 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 16 Jun 2020 16:52:40 +1200 Subject: Format & replace todos with 'fixme' --- crates/ra_hir/src/code_model.rs | 22 +++++++++++++--------- crates/ra_ide/src/hover.rs | 11 +++++------ crates/ra_ide_db/src/defs.rs | 11 +++++++---- crates/ra_syntax/src/lib.rs | 2 +- crates/rust-analyzer/src/handlers.rs | 2 +- 5 files changed, 27 insertions(+), 21 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 5766cc3b8..839eb475e 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -192,11 +192,11 @@ impl ModuleDef { ModuleDef::Function(m) => Some(m.name(db)), ModuleDef::Adt(m) => Some(m.name(db)), ModuleDef::EnumVariant(m) => Some(m.name(db)), - ModuleDef::Const(m) => {m.name(db)}, - ModuleDef::Static(m) => {m.name(db)}, - ModuleDef::Trait(m) => {Some(m.name(db))}, - ModuleDef::TypeAlias(m) => {Some(m.name(db))}, - ModuleDef::BuiltinType(m) => {Some(m.as_name())} + ModuleDef::Const(m) => m.name(db), + ModuleDef::Static(m) => m.name(db), + ModuleDef::Trait(m) => Some(m.name(db)), + ModuleDef::TypeAlias(m) => Some(m.name(db)), + ModuleDef::BuiltinType(m) => Some(m.as_name()), } } @@ -205,13 +205,17 @@ impl ModuleDef { ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), - ModuleDef::EnumVariant(ev) => Into::::into(Into::::into(ev.clone())).resolver(db), - ModuleDef::Const(c) => Into::::into(Into::::into(c.clone())).resolver(db), + ModuleDef::EnumVariant(ev) => { + Into::::into(Into::::into(ev.clone())).resolver(db) + } + ModuleDef::Const(c) => { + Into::::into(Into::::into(c.clone())).resolver(db) + } ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), - // TODO: This should be a resolver relative to `std` - ModuleDef::BuiltinType(_t) => None? + // FIXME: This should be a resolver relative to `std/core` + ModuleDef::BuiltinType(_t) => None?, }) } } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 9f8ab4795..185139250 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -540,14 +540,14 @@ fn try_resolve_intra( let resolved = resolver.resolve_module_path_in_items(db, &modpath); let (defid, namespace) = match namespace { - // TODO: .or(resolved.macros) + // FIXME: .or(resolved.macros) None => resolved .types .map(|t| (t.0, Namespace::Types)) .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, Some(ns @ Namespace::Types) => (resolved.types?.0, ns), Some(ns @ Namespace::Values) => (resolved.values?.0, ns), - // TODO: + // FIXME: Some(Namespace::Macros) => None?, }; @@ -558,7 +558,7 @@ fn try_resolve_intra( let ns = match namespace { Namespace::Types => ItemInNs::Types(defid), Namespace::Values => ItemInNs::Values(defid), - // TODO: + // FIXME: Namespace::Macros => None?, }; let import_map = db.import_map(krate.into()); @@ -603,7 +603,7 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O } /// Try to get the root URL of the documentation of a crate. -// TODO: Special case standard, core, alloc libraries +// FIXME: Special case standard, core, alloc libraries fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); @@ -621,7 +621,7 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { }).next() } else { // Fallback to docs.rs - // TODO: Specify an exact version here (from Cargo.lock) + // FIXME: Specify an exact version here (from Cargo.lock) Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) }; @@ -648,7 +648,6 @@ fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option format!("const.{}.html", c.name(db)?), - // TODO: Check this is the right prefix ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), }, Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index a158169e3..b07395a64 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -6,8 +6,9 @@ // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). use hir::{ + db::{DefDatabase, HirDatabase}, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, - Semantics, TypeParam, Visibility, db::{DefDatabase, HirDatabase}, + Semantics, TypeParam, Visibility, }; use ra_prof::profile; use ra_syntax::{ @@ -16,7 +17,7 @@ use ra_syntax::{ }; use crate::RootDatabase; -use ra_hir_def::resolver::{Resolver, HasResolver}; +use ra_hir_def::resolver::{HasResolver, Resolver}; // FIXME: a more precise name would probably be `Symbol`? #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -83,12 +84,14 @@ impl Definition { use ra_hir_def::*; Some(match self { Definition::ModuleDef(def) => def.resolver(db)?, - Definition::Field(field) => Into::::into(Into::::into(field.parent_def(db))).resolver(db), + Definition::Field(field) => { + Into::::into(Into::::into(field.parent_def(db))).resolver(db) + } Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), // it's possible, read probable, that other arms of this are also unreachable Definition::Local(_local) => unreachable!(), - Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db) + Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db), }) } } diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index 7a2c44cd2..bd83eb2d2 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -47,7 +47,7 @@ use crate::syntax_node::GreenNode; pub use crate::{ algo::InsertPosition, ast::{AstNode, AstToken}, - parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token, parse_text}, + parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, parse_text, tokenize, Token}, ptr::{AstPtr, SyntaxNodePtr}, syntax_error::SyntaxError, syntax_node::{ diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index c8c8b886d..e35a5e846 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -4,7 +4,7 @@ use std::{ io::Write as _, - process::{self, Stdio} + process::{self, Stdio}, }; use lsp_server::ErrorCode; -- cgit v1.2.3 From f98e311617f66654b7799a966fc16b272438ed00 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 16 Jun 2020 17:50:18 +1200 Subject: Return a resolver for Definition::Local --- crates/ra_ide_db/src/defs.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index b07395a64..bc6e89cbc 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -89,8 +89,7 @@ impl Definition { } Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), - // it's possible, read probable, that other arms of this are also unreachable - Definition::Local(_local) => unreachable!(), + Definition::Local(local) => Into::::into(local.parent(db)).resolver(db), Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db), }) } -- cgit v1.2.3 From 1833036daf6d2278df99f7b66aca24b2a66d8d2a Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 27 Jun 2020 13:32:46 +1200 Subject: Fix doc attr parsing, fixes links from std/core/alloc --- crates/ra_ide/src/hover.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 185139250..8c972bb41 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -19,7 +19,7 @@ use ra_ide_db::{ use ra_syntax::{ ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, }; -use ra_tt::{Ident, Leaf, Literal, Punct, TokenTree}; +use ra_tt::{Ident, Leaf, Literal, TokenTree}; use url::Url; use crate::{ @@ -410,6 +410,8 @@ fn map_links<'e>( } Event::Text(s) if in_link => { link_text = s.clone(); + // TODO: This can unintentionally strip words from path-based links. + // See std::box::Box -> std::box link as an example. Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) } Event::Code(s) if in_link => { @@ -603,29 +605,33 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O } /// Try to get the root URL of the documentation of a crate. -// FIXME: Special case standard, core, alloc libraries fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); let doc_attr_q = attrs.by_key("doc"); + + // TODO: Tests for this parsing 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.to_string()), - _ => { - None - } - }).next() + doc_attr_q.tt_values().map(|tt| { + let name = tt.token_trees.iter() + .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) + .skip(2) + .next(); + + match name { + Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), + _ => None + } + }).flat_map(|t| t).next().map(|s| s.to_string()) } else { // Fallback to docs.rs // FIXME: Specify an exact version here (from Cargo.lock) Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) }; - doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()) + doc_url + .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") + .and_then(|s| Url::parse(&s).ok()) } /// Get the filename and extension generated for a symbol by rustdoc. -- cgit v1.2.3 From 48d858f54d5b5a1a2808c8621c5d570d12874b88 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 30 Jun 2020 19:52:25 +1200 Subject: Don't strip affixes from path links --- crates/ra_ide/src/hover.rs | 88 ++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 35 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 8c972bb41..f7a0af037 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -17,7 +17,7 @@ use ra_ide_db::{ RootDatabase, }; use ra_syntax::{ - ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, + ast, ast::{Path, MacroCall}, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, }; use ra_tt::{Ident, Leaf, Literal, TokenTree}; use url::Url; @@ -394,35 +394,36 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option( events: impl Iterator>, - callback: impl Fn(&str, &str) -> String, + callback: impl Fn(&str, &str) -> (String, String), ) -> impl Iterator> { let mut in_link = false; - let mut link_text = CowStr::Borrowed(""); + let mut link_target: Option = None; + events.map(move |evt| match evt { - Event::Start(Tag::Link(..)) => { + Event::Start(Tag::Link(_link_type, ref target, _)) => { in_link = true; + link_target = Some(target.clone()); evt } - Event::End(Tag::Link(link_type, target, _)) => { + Event::End(Tag::Link(link_type, _target, _)) => { in_link = false; - let target = callback(&target, &link_text); - Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed(""))) + Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed(""))) } Event::Text(s) if in_link => { - link_text = s.clone(); - // TODO: This can unintentionally strip words from path-based links. - // See std::box::Box -> std::box link as an example. - Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); + link_target = Some(CowStr::Boxed(link_target_s.into())); + Event::Text(CowStr::Boxed(link_name.into())) } Event::Code(s) if in_link => { - link_text = s.clone(); - Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) + let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); + link_target = Some(CowStr::Boxed(link_target_s.into())); + Event::Code(CowStr::Boxed(link_name.into())) } _ => evt, }) } -/// Rewrite documentation links in markdown to point to local documentation/docs.rs +/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option { let doc = Parser::new_with_broken_link_callback( markdown, @@ -431,21 +432,22 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> ); let doc = map_links(doc, |target, title: &str| { - match Url::parse(target) { - // If this is a valid absolute URL don't touch it - Ok(_) => target.to_string(), - // Otherwise there are two main possibilities - // path-based links: `../../module/struct.MyStruct.html` - // module-based links (AKA intra-doc links): `super::super::module::MyStruct` - Err(_) => { - let resolved = try_resolve_intra(db, definition, title, &target) - .or_else(|| try_resolve_path(db, definition, &target)); - - if let Some(resolved) = resolved { - resolved - } else { - target.to_string() - } + // This check is imperfect, there's some overlap between valid intra-doc links + // and valid URLs so we choose to be too eager to try to resolve what might be + // a URL. + if target.contains("://") { + (target.to_string(), title.to_string()) + } else { + // Two posibilities: + // * path-based links: `../../module/struct.MyStruct.html` + // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` + let resolved = try_resolve_intra(db, definition, title, &target) + .or_else(|| try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))); + + if let Some((target, title)) = resolved { + (target, title) + } else { + (target.to_string(), title.to_string()) } } }); @@ -520,7 +522,7 @@ fn try_resolve_intra( definition: &Definition, link_text: &str, link_target: &str, -) -> Option { +) -> Option<(String, String)> { // Set link_target for implied shortlinks let link_target = if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; @@ -534,6 +536,7 @@ fn try_resolve_intra( // Parse link as a module path // This expects a full document, which a single path isn't, but we can just ignore the errors. let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0); + // TODO: Proper parsing let path = parsed.descendants().filter_map(Path::cast).next()?; let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); @@ -566,7 +569,7 @@ fn try_resolve_intra( let import_map = db.import_map(krate.into()); let path = import_map.path_of(ns)?; - Some( + Some(( get_doc_url(db, &krate)? .join(&format!("{}/", krate.display_name(db)?)) .ok()? @@ -575,7 +578,7 @@ fn try_resolve_intra( .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) .ok()? .into_string(), - ) + strip_prefixes_suffixes(link_text).to_string())) } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). @@ -1485,15 +1488,30 @@ fn func(foo: i32) { if true { <|>foo; }; } } #[test] - fn test_hover_intra_link() { + fn test_hover_path_link_no_strip() { check_hover_result( r" //- /lib.rs pub struct Foo; - /// [Foo](Foo) + /// [struct Foo](struct.Foo.html) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + &["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + ); + } + + #[test] + fn test_hover_intra_link() { + check_hover_result( + r" + //- /lib.rs + pub mod foo { + pub struct Foo; + } + /// [Foo](foo::Foo) + pub struct B<|>ar + ", + &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"], ); } -- cgit v1.2.3 From 3e22f80f443469f470561bceb2d84427ebab677f Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 30 Jun 2020 20:23:06 +1200 Subject: Switch to Path::parse and add tests --- crates/ra_ide/src/hover.rs | 1338 ++++++++++++++++++++++---------------------- 1 file changed, 667 insertions(+), 671 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index f7a0af037..78a661c85 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -17,7 +17,7 @@ use ra_ide_db::{ RootDatabase, }; use ra_syntax::{ - ast, ast::{Path, MacroCall}, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, + ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, }; use ra_tt::{Ident, Leaf, Literal, TokenTree}; use url::Url; @@ -534,10 +534,7 @@ fn try_resolve_intra( let link_target = strip_prefixes_suffixes(link_target); // Parse link as a module path - // This expects a full document, which a single path isn't, but we can just ignore the errors. - let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0); - // TODO: Proper parsing - let path = parsed.descendants().filter_map(Path::cast).next()?; + let path = Path::parse(link_target).ok()?; let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); // Resolve it relative to symbol's location (according to the RFC this should consider small scopes @@ -613,7 +610,6 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); let doc_attr_q = attrs.by_key("doc"); - // TODO: Tests for this parsing let doc_url = if doc_attr_q.exists() { doc_attr_q.tt_values().map(|tt| { let name = tt.token_trees.iter() @@ -1830,32 +1826,32 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..19, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 7..8, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..19, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 7..8, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -1874,51 +1870,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 17..37, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 24..25, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 17..37, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 24..25, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "Arg", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..16, - name: "Arg", - kind: STRUCT_DEF, - focus_range: Some( - 7..10, - ), - container_name: None, - description: Some( - "struct Arg", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::Arg", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..16, + name: "Arg", + kind: STRUCT_DEF, + focus_range: Some( + 7..10, + ), + container_name: None, + description: Some( + "struct Arg", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -1937,51 +1933,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 17..37, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 24..25, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 17..37, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 24..25, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "Arg", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..16, - name: "Arg", - kind: STRUCT_DEF, - focus_range: Some( - 7..10, - ), - container_name: None, - description: Some( - "struct Arg", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::Arg", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..16, + name: "Arg", + kind: STRUCT_DEF, + focus_range: Some( + 7..10, + ), + container_name: None, + description: Some( + "struct Arg", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2003,70 +1999,70 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "A", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..14, - name: "A", - kind: STRUCT_DEF, - focus_range: Some( - 7..8, - ), - container_name: None, - description: Some( - "struct A", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::A", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..14, + name: "A", + kind: STRUCT_DEF, + focus_range: Some( + 7..8, + ), + container_name: None, + description: Some( + "struct A", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "B", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 15..29, - name: "B", - kind: STRUCT_DEF, - focus_range: Some( - 22..23, - ), - container_name: None, - description: Some( - "struct B", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::B", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 15..29, + name: "B", + kind: STRUCT_DEF, + focus_range: Some( + 22..23, + ), + container_name: None, + description: Some( + "struct B", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "M::C", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 42..60, - name: "C", - kind: STRUCT_DEF, - focus_range: Some( - 53..54, - ), - container_name: None, - description: Some( - "pub struct C", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::M::C", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 42..60, + name: "C", + kind: STRUCT_DEF, + focus_range: Some( + 53..54, + ), + container_name: None, + description: Some( + "pub struct C", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2086,32 +2082,32 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..12, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2132,51 +2128,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..15, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 16..25, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 23..24, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..25, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2197,51 +2193,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..12, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "Bar", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 13..25, - name: "Bar", - kind: TRAIT_DEF, - focus_range: Some( - 19..22, - ), - container_name: None, - description: Some( - "trait Bar", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 13..25, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 19..22, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2264,89 +2260,89 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..15, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "Bar", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 16..31, - name: "Bar", - kind: TRAIT_DEF, - focus_range: Some( - 22..25, - ), - container_name: None, - description: Some( - "trait Bar", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..31, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 22..25, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S1", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 32..44, - name: "S1", - kind: STRUCT_DEF, - focus_range: Some( - 39..41, - ), - container_name: None, - description: Some( - "struct S1", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S1", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 32..44, + name: "S1", + kind: STRUCT_DEF, + focus_range: Some( + 39..41, + ), + container_name: None, + description: Some( + "struct S1", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S2", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 45..57, - name: "S2", - kind: STRUCT_DEF, - focus_range: Some( - 52..54, - ), - container_name: None, - description: Some( - "struct S2", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S2", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 45..57, + name: "S2", + kind: STRUCT_DEF, + focus_range: Some( + 52..54, + ), + container_name: None, + description: Some( + "struct S2", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2361,32 +2357,32 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..12, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2404,70 +2400,70 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..12, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "Bar", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 13..28, - name: "Bar", - kind: TRAIT_DEF, - focus_range: Some( - 19..22, - ), - container_name: None, - description: Some( - "trait Bar", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 13..28, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 19..22, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 29..39, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 36..37, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 29..39, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 36..37, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2483,51 +2479,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..15, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 16..27, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 23..24, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..27, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2555,7 +2551,7 @@ fn func(foo: i32) { if true { <|>foo; }; } GoToType( [ HoverGotoTypeData { - mod_path: "B", + mod_path: "test::B", nav: NavigationTarget { file_id: FileId( 1, @@ -2574,7 +2570,7 @@ fn func(foo: i32) { if true { <|>foo; }; } }, }, HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2610,32 +2606,32 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..12, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2651,51 +2647,51 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..15, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 16..27, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 23..24, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..27, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2714,89 +2710,89 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "ImplTrait", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..21, - name: "ImplTrait", - kind: TRAIT_DEF, - focus_range: Some( - 6..15, - ), - container_name: None, - description: Some( - "trait ImplTrait", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::ImplTrait", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..21, + name: "ImplTrait", + kind: TRAIT_DEF, + focus_range: Some( + 6..15, + ), + container_name: None, + description: Some( + "trait ImplTrait", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "B", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 43..57, - name: "B", - kind: STRUCT_DEF, - focus_range: Some( - 50..51, - ), - container_name: None, - description: Some( - "struct B", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::B", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 43..57, + name: "B", + kind: STRUCT_DEF, + focus_range: Some( + 50..51, + ), + container_name: None, + description: Some( + "struct B", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "DynTrait", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 22..42, - name: "DynTrait", - kind: TRAIT_DEF, - focus_range: Some( - 28..36, - ), - container_name: None, - description: Some( - "trait DynTrait", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::DynTrait", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 22..42, + name: "DynTrait", + kind: TRAIT_DEF, + focus_range: Some( + 28..36, + ), + container_name: None, + description: Some( + "trait DynTrait", + ), + docs: None, }, - HoverGotoTypeData { - mod_path: "S", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 58..69, - name: "S", - kind: STRUCT_DEF, - focus_range: Some( - 65..66, - ), - container_name: None, - description: Some( - "struct S", - ), - docs: None, - }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 58..69, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 65..66, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } #[test] @@ -2828,31 +2824,31 @@ fn func(foo: i32) { if true { <|>foo; }; } ); assert_debug_snapshot!(actions, @r###" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "Foo", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 0..62, - name: "Foo", - kind: TRAIT_DEF, - focus_range: Some( - 6..9, - ), - container_name: None, - description: Some( - "trait Foo", - ), - docs: None, - }, + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..62, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, }, - ], - ), - ] - "###); + }, + ], + ), + ] + "###); } } -- cgit v1.2.3 From dc32f756e0958368648ac151aab1bde40102a506 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 30 Jun 2020 21:16:12 +1200 Subject: Format --- crates/ra_ide/src/hover.rs | 16 ++++++++-------- crates/ra_proc_macro_srv/src/tests/mod.rs | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 78a661c85..f36b9de7e 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -3,8 +3,8 @@ use std::iter::once; use hir::{ db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, - FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, ModuleDef, ModuleSource, - Semantics, Module + FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, Module, ModuleDef, + ModuleSource, Semantics, }; use itertools::Itertools; use lazy_static::lazy_static; @@ -16,9 +16,7 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use ra_tt::{Ident, Leaf, Literal, TokenTree}; use url::Url; @@ -441,8 +439,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> // Two posibilities: // * path-based links: `../../module/struct.MyStruct.html` // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` - let resolved = try_resolve_intra(db, definition, title, &target) - .or_else(|| try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))); + let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| { + try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())) + }); if let Some((target, title)) = resolved { (target, title) @@ -575,7 +574,8 @@ fn try_resolve_intra( .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) .ok()? .into_string(), - strip_prefixes_suffixes(link_text).to_string())) + strip_prefixes_suffixes(link_text).to_string(), + )) } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). diff --git a/crates/ra_proc_macro_srv/src/tests/mod.rs b/crates/ra_proc_macro_srv/src/tests/mod.rs index 82cefbb29..1057408e5 100644 --- a/crates/ra_proc_macro_srv/src/tests/mod.rs +++ b/crates/ra_proc_macro_srv/src/tests/mod.rs @@ -6,6 +6,7 @@ use test_utils::assert_eq_text; use utils::*; #[test] +#[ignore] fn test_derive_serialize_proc_macro() { assert_expand( "serde_derive", @@ -17,6 +18,7 @@ fn test_derive_serialize_proc_macro() { } #[test] +#[ignore] fn test_derive_serialize_proc_macro_failed() { assert_expand( "serde_derive", -- cgit v1.2.3 From d6f9e8806a26c5998a691974ba8e3655a2186766 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 30 Jun 2020 21:37:42 +1200 Subject: Remove unintentional changes --- crates/ra_hir/src/code_model.rs | 21 +++++++++++---------- crates/ra_ide/Cargo.toml | 1 - crates/ra_proc_macro_srv/src/tests/mod.rs | 2 -- crates/ra_syntax/src/lib.rs | 2 +- crates/ra_syntax/src/parsing.rs | 2 +- 5 files changed, 13 insertions(+), 15 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 839eb475e..56b2481e6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -186,17 +186,18 @@ impl ModuleDef { module.visibility_of(db, self) } - pub fn name(&self, db: &dyn HirDatabase) -> Option { + pub fn name(self, db: &dyn HirDatabase) -> Option { match self { - ModuleDef::Module(m) => m.name(db), - ModuleDef::Function(m) => Some(m.name(db)), - ModuleDef::Adt(m) => Some(m.name(db)), - ModuleDef::EnumVariant(m) => Some(m.name(db)), - ModuleDef::Const(m) => m.name(db), - ModuleDef::Static(m) => m.name(db), - ModuleDef::Trait(m) => Some(m.name(db)), - ModuleDef::TypeAlias(m) => Some(m.name(db)), - ModuleDef::BuiltinType(m) => Some(m.as_name()), + ModuleDef::Adt(it) => Some(it.name(db)), + ModuleDef::Trait(it) => Some(it.name(db)), + ModuleDef::Function(it) => Some(it.name(db)), + ModuleDef::EnumVariant(it) => Some(it.name(db)), + ModuleDef::TypeAlias(it) => Some(it.name(db)), + ModuleDef::Module(it) => it.name(db), + ModuleDef::Const(it) => it.name(db), + ModuleDef::Static(it) => it.name(db), + + ModuleDef::BuiltinType(it) => Some(it.as_name()), } } diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 642b71937..d0eb018d5 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -35,7 +35,6 @@ ra_prof = { path = "../ra_prof" } test_utils = { path = "../test_utils" } 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_parser = { path = "../ra_parser" } diff --git a/crates/ra_proc_macro_srv/src/tests/mod.rs b/crates/ra_proc_macro_srv/src/tests/mod.rs index 1057408e5..82cefbb29 100644 --- a/crates/ra_proc_macro_srv/src/tests/mod.rs +++ b/crates/ra_proc_macro_srv/src/tests/mod.rs @@ -6,7 +6,6 @@ use test_utils::assert_eq_text; use utils::*; #[test] -#[ignore] fn test_derive_serialize_proc_macro() { assert_expand( "serde_derive", @@ -18,7 +17,6 @@ fn test_derive_serialize_proc_macro() { } #[test] -#[ignore] fn test_derive_serialize_proc_macro_failed() { assert_expand( "serde_derive", diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index bd83eb2d2..9b7664576 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -47,7 +47,7 @@ use crate::syntax_node::GreenNode; pub use crate::{ algo::InsertPosition, ast::{AstNode, AstToken}, - parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, parse_text, tokenize, Token}, + parsing::{lex_single_syntax_kind, lex_single_valid_syntax_kind, tokenize, Token}, ptr::{AstPtr, SyntaxNodePtr}, syntax_error::SyntaxError, syntax_node::{ diff --git a/crates/ra_syntax/src/parsing.rs b/crates/ra_syntax/src/parsing.rs index 4ec0b8d59..0ed3c20ef 100644 --- a/crates/ra_syntax/src/parsing.rs +++ b/crates/ra_syntax/src/parsing.rs @@ -15,7 +15,7 @@ pub use lexer::*; pub(crate) use self::reparsing::incremental_reparse; use ra_parser::SyntaxKind; -pub fn parse_text(text: &str) -> (GreenNode, Vec) { +pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec) { let (tokens, lexer_errors) = tokenize(&text); let mut token_source = TextTokenSource::new(text, &tokens); -- cgit v1.2.3 From 73ff610e41959e3e7c78a2b4b25b086883132956 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 11 Jul 2020 11:23:33 +1200 Subject: Use From instead of Into in resolver method --- crates/ra_hir/src/code_model.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 56b2481e6..8c5e1b746 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -203,18 +203,18 @@ impl ModuleDef { pub fn resolver(&self, db: &D) -> Option { Some(match self { - ModuleDef::Module(m) => Into::::into(m.clone()).resolver(db), - ModuleDef::Function(f) => Into::::into(f.clone()).resolver(db), - ModuleDef::Adt(adt) => Into::::into(adt.clone()).resolver(db), + ModuleDef::Module(m) => ModuleId::from(m.clone()).resolver(db), + ModuleDef::Function(f) => FunctionId::from(f.clone()).resolver(db), + ModuleDef::Adt(adt) => AdtId::from(adt.clone()).resolver(db), ModuleDef::EnumVariant(ev) => { - Into::::into(Into::::into(ev.clone())).resolver(db) + GenericDefId::from(GenericDef::from(ev.clone())).resolver(db) } ModuleDef::Const(c) => { - Into::::into(Into::::into(c.clone())).resolver(db) + GenericDefId::from(GenericDef::from(c.clone())).resolver(db) } - ModuleDef::Static(s) => Into::::into(s.clone()).resolver(db), - ModuleDef::Trait(t) => Into::::into(t.clone()).resolver(db), - ModuleDef::TypeAlias(t) => Into::::into(t.module(db)).resolver(db), + ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db), + ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db), + ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db), // FIXME: This should be a resolver relative to `std/core` ModuleDef::BuiltinType(_t) => None?, }) -- cgit v1.2.3 From 9522a06fd444c670202fd64c91d1ba62e332ff70 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Fri, 31 Jul 2020 14:28:33 +1200 Subject: Update test calls to 'expect' --- crates/ra_ide/src/hover.rs | 58 ++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 22 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index ad68bc43c..02099b05d 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1477,33 +1477,35 @@ fn foo() { let bar = Ba<|>r; } #[test] fn test_hover_path_link() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [Foo](struct.Foo.html) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_path_link_no_strip() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [struct Foo](struct.Foo.html) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"]], ); } #[test] fn test_hover_intra_link() { - check_hover_result( + check( r" //- /lib.rs pub mod foo { @@ -1512,39 +1514,43 @@ fn foo() { let bar = Ba<|>r; } /// [Foo](foo::Foo) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"], + expect![["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"]], ); } #[test] fn test_hover_intra_link_shortlink() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [Foo] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_intra_link_shortlink_code() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [`Foo`] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_intra_link_namespaced() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; @@ -1552,39 +1558,45 @@ fn foo() { let bar = Ba<|>r; } /// [Foo()] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_intra_link_shortlink_namspaced_code() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [`struct Foo`] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_intra_link_shortlink_namspaced_code_with_at() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [`struct@Foo`] pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_intra_link_reference() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; @@ -1593,34 +1605,36 @@ fn foo() { let bar = Ba<|>r; } /// [foo]: Foo pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)"], + expect![[ + "pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)" + ]], ); } #[test] fn test_hover_external_url() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [external](https://www.google.com) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"], + expect![["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"]], ); } // Check that we don't rewrite links which we can't identify #[test] fn test_hover_unknown_target() { - check_hover_result( + check( r" //- /lib.rs pub struct Foo; /// [baz](Baz) pub struct B<|>ar ", - &["pub struct Bar\n```\n___\n\n[baz](Baz)"], + expect![["pub struct Bar\n```\n___\n\n[baz](Baz)"]], ); } -- cgit v1.2.3 From 1fa842c8c9e9fed28a45fe1e3b109f2f902d128c Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Fri, 31 Jul 2020 14:34:49 +1200 Subject: Update expect tests --- crates/ra_hir/src/code_model.rs | 4 +- crates/ra_ide/src/hover.rs | 580 ++++++++++++++++++++++++++++------------ 2 files changed, 417 insertions(+), 167 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 5d88c5c64..8284ab757 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -212,9 +212,7 @@ impl ModuleDef { ModuleDef::EnumVariant(ev) => { GenericDefId::from(GenericDef::from(ev.clone())).resolver(db) } - ModuleDef::Const(c) => { - GenericDefId::from(GenericDef::from(c.clone())).resolver(db) - } + ModuleDef::Const(c) => GenericDefId::from(GenericDef::from(c.clone())).resolver(db), ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db), ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db), ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db), diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 02099b05d..a82fe6714 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -704,9 +704,10 @@ fn main() { "#, expect![[r#" *iter* - ```rust + + ````rust Iter>, |&mut u32, &u32, &mut u32| -> Option, u32>> - ``` + ```` "#]], ); } @@ -722,9 +723,14 @@ fn main() { let foo_test = fo<|>o(); } "#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust pub fn foo() -> u32 - ``` + ```` "#]], ); @@ -766,9 +772,14 @@ fn main() { let foo_test = fo<|>o(); } "#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str - ``` + ```` "#]], ); } @@ -783,9 +794,14 @@ fn main() { } "#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust pub fn foo(a: u32, b: u32) -> u32 - ``` + ```` "#]], ); } @@ -803,13 +819,14 @@ fn main() { "#, expect![[r#" *field_a* - ```rust - Foo - ``` - ```rust + ````rust + test::Foo + ```` + + ````rust field_a: u32 - ``` + ```` "#]], ); @@ -824,13 +841,14 @@ fn main() { "#, expect![[r#" *field_a* - ```rust - Foo - ``` - ```rust + ````rust + test::Foo + ```` + + ````rust field_a: u32 - ``` + ```` "#]], ); } @@ -841,18 +859,28 @@ fn main() { r#"const foo<|>: u32 = 0;"#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust const foo: u32 - ``` + ```` "#]], ); check( r#"static foo<|>: u32 = 0;"#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust static foo: u32 - ``` + ```` "#]], ); } @@ -868,9 +896,10 @@ fn main() { }"#, expect![[r#" *zz* - ```rust + + ````rust Test - ``` + ```` "#]], ); } @@ -886,13 +915,14 @@ fn main() { So<|>me(12); } "#, expect![[r#" *Some* - ```rust - Option - ``` - ```rust + ````rust + test::Option + ```` + + ````rust Some - ``` + ```` "#]], ); @@ -905,9 +935,10 @@ fn main() { let b<|>ar = Some(12); } "#, expect![[r#" *bar* - ```rust + + ````rust Option - ``` + ```` "#]], ); } @@ -923,14 +954,16 @@ enum Option { "#, expect![[r#" *None* - ```rust - Option - ``` - ```rust + ````rust + test::Option + ```` + + ````rust None - ``` - ___ + ```` + + --- The None variant "#]], @@ -948,14 +981,16 @@ fn main() { "#, expect![[r#" *Some* - ```rust - Option - ``` - ```rust + ````rust + test::Option + ```` + + ````rust Some - ``` - ___ + ```` + + --- The Some variant "#]], @@ -968,9 +1003,10 @@ fn main() { r#"fn func(foo: i32) { fo<|>o; }"#, expect![[r#" *foo* - ```rust + + ````rust i32 - ``` + ```` "#]], ) } @@ -981,9 +1017,10 @@ fn main() { r#"fn func(fo<|>o: i32) {}"#, expect![[r#" *foo* - ```rust + + ````rust i32 - ``` + ```` "#]], ) } @@ -994,9 +1031,10 @@ fn main() { r#"fn func(foo: i32) { if true { <|>foo; }; }"#, expect![[r#" *foo* - ```rust + + ````rust i32 - ``` + ```` "#]], ) } @@ -1007,9 +1045,10 @@ fn main() { r#"fn func(<|>foo: i32) {}"#, expect![[r#" *foo* - ```rust + + ````rust i32 - ``` + ```` "#]], ) } @@ -1028,9 +1067,10 @@ fn main() { let foo_<|>test = Thing::new(); } "#, expect![[r#" *foo_test* - ```rust + + ````rust Thing - ``` + ```` "#]], ) } @@ -1051,13 +1091,14 @@ fn main() { let foo_test = wrapper::Thing::new<|>(); } "#, expect![[r#" *new* - ```rust - wrapper::Thing - ``` - ```rust + ````rust + test::wrapper::Thing + ```` + + ````rust fn new() -> Thing - ``` + ```` "#]], ) } @@ -1081,9 +1122,14 @@ fn main() { "#, expect![[r#" *C* - ```rust + + ````rust + test + ```` + + ````rust const C: u32 - ``` + ```` "#]], ) } @@ -1158,9 +1204,10 @@ fn y() { "#, expect![[r#" *x* - ```rust + + ````rust i32 - ``` + ```` "#]], ) } @@ -1175,9 +1222,14 @@ fn f() { fo<|>o!(); } "#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust macro_rules! foo - ``` + ```` "#]], ) } @@ -1205,9 +1257,14 @@ id! { "#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust fn foo() - ``` + ```` "#]], ); } @@ -1221,9 +1278,10 @@ fn foo(bar:u32) { let a = id!(ba<|>r); } "#, expect![[r#" *bar* - ```rust + + ````rust u32 - ``` + ```` "#]], ); } @@ -1238,9 +1296,10 @@ fn foo(bar:u32) { let a = id!(ba<|>r); } "#, expect![[r#" *bar* - ```rust + + ````rust u32 - ``` + ```` "#]], ); } @@ -1296,9 +1355,14 @@ fn foo() { "#, expect![[r#" *bar* - ```rust + + ````rust + test + ```` + + ````rust fn bar() -> bool - ``` + ```` "#]], ); } @@ -1328,12 +1392,18 @@ fn bar() { fo<|>o(); } ", expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust fn foo() - ``` - ___ + ```` + + --- - <- ` ` here + \<- ` ` here "#]], ); } @@ -1344,27 +1414,42 @@ fn bar() { fo<|>o(); } r#"async fn foo<|>() {}"#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust async fn foo() - ``` + ```` "#]], ); check( r#"pub const unsafe fn foo<|>() {}"#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust pub const unsafe fn foo() - ``` + ```` "#]], ); check( r#"pub(crate) async unsafe extern "C" fn foo<|>() {}"#, expect![[r#" *foo* - ```rust + + ````rust + test + ```` + + ````rust pub(crate) async unsafe extern "C" fn foo() - ``` + ```` "#]], ); } @@ -1399,9 +1484,14 @@ fn my() {} "#, expect![[r#" *my* - ```rust + + ````rust + test + ```` + + ````rust mod my - ``` + ```` "#]], ); } @@ -1417,10 +1507,16 @@ fn foo() { let bar = Ba<|>r; } "#, expect![[r#" *Bar* - ```rust + + ````rust + test + ```` + + ````rust struct Bar - ``` - ___ + ```` + + --- bar docs "#]], @@ -1438,10 +1534,16 @@ fn foo() { let bar = Ba<|>r; } "#, expect![[r#" *Bar* - ```rust + + ````rust + test + ```` + + ````rust struct Bar - ``` - ___ + ```` + + --- bar docs "#]], @@ -1461,10 +1563,16 @@ fn foo() { let bar = Ba<|>r; } "#, expect![[r#" *Bar* - ```rust + + ````rust + test + ```` + + ````rust struct Bar - ``` - ___ + ```` + + --- bar docs 0 @@ -1484,9 +1592,21 @@ fn foo() { let bar = Ba<|>r; } /// [Foo](struct.Foo.html) pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1499,7 +1619,21 @@ fn foo() { let bar = Ba<|>r; } /// [struct Foo](struct.Foo.html) pub struct B<|>ar ", - expect![["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [struct Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1514,7 +1648,21 @@ fn foo() { let bar = Ba<|>r; } /// [Foo](foo::Foo) pub struct B<|>ar ", - expect![["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [Foo](https://docs.rs/test/*/test/foo/struct.Foo.html) + "#]], ); } @@ -1527,9 +1675,21 @@ fn foo() { let bar = Ba<|>r; } /// [Foo] pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1542,9 +1702,21 @@ fn foo() { let bar = Ba<|>r; } /// [`Foo`] pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1558,9 +1730,21 @@ fn foo() { let bar = Ba<|>r; } /// [Foo()] pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1573,9 +1757,21 @@ fn foo() { let bar = Ba<|>r; } /// [`struct Foo`] pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1588,9 +1784,21 @@ fn foo() { let bar = Ba<|>r; } /// [`struct@Foo`] pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1605,9 +1813,21 @@ fn foo() { let bar = Ba<|>r; } /// [foo]: Foo pub struct B<|>ar ", - expect![[ - "pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)" - ]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [my Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], ); } @@ -1620,7 +1840,21 @@ fn foo() { let bar = Ba<|>r; } /// [external](https://www.google.com) pub struct B<|>ar ", - expect![["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [external](https://www.google.com) + "#]], ); } @@ -1634,7 +1868,21 @@ fn foo() { let bar = Ba<|>r; } /// [baz](Baz) pub struct B<|>ar ", - expect![["pub struct Bar\n```\n___\n\n[baz](Baz)"]], + expect![[r#" + *Bar* + + ````rust + test + ```` + + ````rust + pub struct Bar + ```` + + --- + + [baz](Baz) + "#]], ); } @@ -1660,16 +1908,18 @@ fn foo() { let bar = Bar; bar.fo<|>o(); } "#, expect![[r#" *foo* - ```rust - Bar - ``` - ```rust + ````rust + test::Bar + ```` + + ````rust fn foo(&self) - ``` - ___ + ```` + + --- - Do the foo + Do the foo "#]], ); } @@ -1696,14 +1946,16 @@ fn foo() { let bar = Bar; bar.fo<|>o(); } "#, expect![[r#" *foo* - ```rust - Bar - ``` - ```rust + ````rust + test::Bar + ```` + + ````rust fn foo(&self) - ``` - ___ + ```` + + --- Do the foo "#]], @@ -1878,7 +2130,7 @@ fn main() { let s<|>t = S{ f1:0 }; } GoToType( [ HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -1917,7 +2169,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; } GoToType( [ HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -1936,7 +2188,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; } }, }, HoverGotoTypeData { - mod_path: "Arg", + mod_path: "test::Arg", nav: NavigationTarget { file_id: FileId( 1, @@ -1975,7 +2227,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; } GoToType( [ HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -1994,7 +2246,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; } }, }, HoverGotoTypeData { - mod_path: "Arg", + mod_path: "test::Arg", nav: NavigationTarget { file_id: FileId( 1, @@ -2036,7 +2288,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); } GoToType( [ HoverGotoTypeData { - mod_path: "A", + mod_path: "test::A", nav: NavigationTarget { file_id: FileId( 1, @@ -2055,7 +2307,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); } }, }, HoverGotoTypeData { - mod_path: "B", + mod_path: "test::B", nav: NavigationTarget { file_id: FileId( 1, @@ -2074,7 +2326,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); } }, }, HoverGotoTypeData { - mod_path: "M::C", + mod_path: "test::M::C", nav: NavigationTarget { file_id: FileId( 1, @@ -2113,7 +2365,7 @@ fn main() { let s<|>t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2153,7 +2405,7 @@ fn main() { let s<|>t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2172,7 +2424,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -2212,7 +2464,7 @@ fn main() { let s<|>t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2231,7 +2483,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "Bar", + mod_path: "test::Bar", nav: NavigationTarget { file_id: FileId( 1, @@ -2274,7 +2526,7 @@ fn main() { let s<|>t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2293,7 +2545,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "Bar", + mod_path: "test::Bar", nav: NavigationTarget { file_id: FileId( 1, @@ -2312,7 +2564,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "S1", + mod_path: "test::S1", nav: NavigationTarget { file_id: FileId( 1, @@ -2331,7 +2583,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "S2", + mod_path: "test::S2", nav: NavigationTarget { file_id: FileId( 1, @@ -2368,7 +2620,7 @@ fn foo(ar<|>g: &impl Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2408,7 +2660,7 @@ fn foo(ar<|>g: &impl Foo + Bar) {} GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2427,7 +2679,7 @@ fn foo(ar<|>g: &impl Foo + Bar) {} }, }, HoverGotoTypeData { - mod_path: "Bar", + mod_path: "test::Bar", nav: NavigationTarget { file_id: FileId( 1, @@ -2446,7 +2698,7 @@ fn foo(ar<|>g: &impl Foo + Bar) {} }, }, HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -2484,7 +2736,7 @@ fn foo(ar<|>g: &impl Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2503,7 +2755,7 @@ fn foo(ar<|>g: &impl Foo) {} }, }, HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -2546,7 +2798,7 @@ fn main() { let s<|>t = foo(); } GoToType( [ HoverGotoTypeData { - mod_path: "B", + mod_path: "test::B", nav: NavigationTarget { file_id: FileId( 1, @@ -2565,7 +2817,7 @@ fn main() { let s<|>t = foo(); } }, }, HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2602,7 +2854,7 @@ fn foo(ar<|>g: &dyn Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2640,7 +2892,7 @@ fn foo(ar<|>g: &dyn Foo) {} GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, @@ -2659,7 +2911,7 @@ fn foo(ar<|>g: &dyn Foo) {} }, }, HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -2700,7 +2952,7 @@ fn foo(a<|>rg: &impl ImplTrait>>>) {} GoToType( [ HoverGotoTypeData { - mod_path: "ImplTrait", + mod_path: "test::ImplTrait", nav: NavigationTarget { file_id: FileId( 1, @@ -2719,7 +2971,7 @@ fn foo(a<|>rg: &impl ImplTrait>>>) {} }, }, HoverGotoTypeData { - mod_path: "B", + mod_path: "test::B", nav: NavigationTarget { file_id: FileId( 1, @@ -2738,7 +2990,7 @@ fn foo(a<|>rg: &impl ImplTrait>>>) {} }, }, HoverGotoTypeData { - mod_path: "DynTrait", + mod_path: "test::DynTrait", nav: NavigationTarget { file_id: FileId( 1, @@ -2757,7 +3009,7 @@ fn foo(a<|>rg: &impl ImplTrait>>>) {} }, }, HoverGotoTypeData { - mod_path: "S", + mod_path: "test::S", nav: NavigationTarget { file_id: FileId( 1, @@ -2805,7 +3057,7 @@ fn main() { let s<|>t = test().get(); } GoToType( [ HoverGotoTypeData { - mod_path: "Foo", + mod_path: "test::Foo", nav: NavigationTarget { file_id: FileId( 1, -- cgit v1.2.3 From 4c745d219ffdb445606047fa476159b836209bf7 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 11:47:27 +1200 Subject: remove some crates.io deps --- crates/ra_ide/Cargo.toml | 4 +--- crates/ra_ide/src/hover.rs | 44 +++++++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 18 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 4e2ba6d61..6d5c9daf5 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -17,13 +17,11 @@ indexmap = "1.3.2" itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" -rand = { version = "0.7.3", features = ["small_rng"] } url = "*" -maplit = "*" -lazy_static = "*" pulldown-cmark-to-cmark = "4.0.2" pulldown-cmark = "0.7.0" oorandom = "11.1.2" +once_cell = "1" stdx = { path = "../stdx" } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index a82fe6714..1531aca2e 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,5 +1,8 @@ use std::collections::{HashMap, HashSet}; -use std::iter::once; +use std::{ + iter::{once, FromIterator}, + sync::Mutex, +}; use hir::{ db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, @@ -7,8 +10,7 @@ use hir::{ ModuleSource, Semantics, }; use itertools::Itertools; -use lazy_static::lazy_static; -use maplit::{hashmap, hashset}; +use once_cell::sync::Lazy; use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; use pulldown_cmark_to_cmark::cmark; use ra_db::SourceDatabase; @@ -419,14 +421,26 @@ enum Namespace { Macros, } -lazy_static!( - /// Map of namespaces to identifying prefixes and suffixes as defined by RFC1946. - static ref NS_MAP: HashMap, HashSet<&'static str>)> = hashmap!{ - Namespace::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), - Namespace::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), - Namespace::Macros => (hashset!{"macro"}, hashset!{"!"}) - }; -); +static NS_MAP: Lazy< + HashMap, HashSet<&'static &'static str>)>, +> = Lazy::new(|| { + let mut map = HashMap::new(); + map.insert(Namespace::Types, (HashSet::new(), HashSet::new())); + map.insert( + Namespace::Values, + ( + HashSet::from_iter( + ["value", "function", "fn", "method", "const", "static", "mod", "module"].iter(), + ), + HashSet::from_iter(["()"].iter()), + ), + ); + map.insert( + Namespace::Macros, + (HashSet::from_iter(["macro"].iter()), HashSet::from_iter(["!"].iter())), + ); + map +}); impl Namespace { /// Extract the specified namespace from an intra-doc-link if one exists. @@ -437,7 +451,7 @@ impl Namespace { prefixes .iter() .map(|prefix| { - s.starts_with(prefix) + s.starts_with(*prefix) && s.chars() .nth(prefix.len() + 1) .map(|c| c == '@' || c == ' ') @@ -447,7 +461,7 @@ impl Namespace { || suffixes .iter() .map(|suffix| { - s.starts_with(suffix) + s.starts_with(*suffix) && s.chars() .nth(suffix.len() + 1) .map(|c| c == '@' || c == ' ') @@ -464,8 +478,8 @@ impl Namespace { fn strip_prefixes_suffixes(mut s: &str) -> &str { s = s.trim_matches('`'); NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| { - prefixes.iter().for_each(|prefix| s = s.trim_start_matches(prefix)); - suffixes.iter().for_each(|suffix| s = s.trim_end_matches(suffix)); + prefixes.iter().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.iter().for_each(|suffix| s = s.trim_end_matches(*suffix)); }); s.trim_start_matches("@").trim() } -- cgit v1.2.3 From 7bd48a63f62ab3ffa1b86f8a550ede690ea8f9e8 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 12:32:49 +1200 Subject: use static data for namespace recognition --- crates/ra_ide/Cargo.toml | 1 - crates/ra_ide/src/hover.rs | 102 +++++++++++++++++++++------------------------ 2 files changed, 48 insertions(+), 55 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 6d5c9daf5..fdbd7566f 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -21,7 +21,6 @@ url = "*" pulldown-cmark-to-cmark = "4.0.2" pulldown-cmark = "0.7.0" oorandom = "11.1.2" -once_cell = "1" stdx = { path = "../stdx" } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 1531aca2e..f6854a18d 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,4 @@ -use std::collections::{HashMap, HashSet}; -use std::{ - iter::{once, FromIterator}, - sync::Mutex, -}; +use std::iter::once; use hir::{ db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, @@ -10,7 +6,6 @@ use hir::{ ModuleSource, Semantics, }; use itertools::Itertools; -use once_cell::sync::Lazy; use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; use pulldown_cmark_to_cmark::cmark; use ra_db::SourceDatabase; @@ -402,10 +397,9 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())) }); - if let Some((target, title)) = resolved { - (target, title) - } else { - (target.to_string(), title.to_string()) + match resolved { + Some((target, title)) => (target, title), + None => (target.to_string(), title.to_string()), } } }); @@ -421,65 +415,61 @@ enum Namespace { Macros, } -static NS_MAP: Lazy< - HashMap, HashSet<&'static &'static str>)>, -> = Lazy::new(|| { - let mut map = HashMap::new(); - map.insert(Namespace::Types, (HashSet::new(), HashSet::new())); - map.insert( - Namespace::Values, - ( - HashSet::from_iter( - ["value", "function", "fn", "method", "const", "static", "mod", "module"].iter(), - ), - HashSet::from_iter(["()"].iter()), - ), - ); - map.insert( - Namespace::Macros, - (HashSet::from_iter(["macro"].iter()), HashSet::from_iter(["!"].iter())), - ); - map -}); +static TYPES: ([&str; 7], [&str; 0]) = + (["type", "struct", "enum", "mod", "trait", "union", "module"], []); +static VALUES: ([&str; 8], [&str; 1]) = + (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); +static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); impl Namespace { /// Extract the specified namespace from an intra-doc-link if one exists. fn from_intra_spec(s: &str) -> Option { - NS_MAP - .iter() - .filter(|(_ns, (prefixes, suffixes))| { - prefixes - .iter() - .map(|prefix| { - s.starts_with(*prefix) + [ + (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), + (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), + (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), + ] + .iter() + .filter(|(_ns, (prefixes, suffixes))| { + prefixes + .clone() + .map(|prefix| { + s.starts_with(*prefix) + && s.chars() + .nth(prefix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + || suffixes + .clone() + .map(|suffix| { + s.starts_with(*suffix) && s.chars() - .nth(prefix.len() + 1) + .nth(suffix.len() + 1) .map(|c| c == '@' || c == ' ') .unwrap_or(false) }) .any(|cond| cond) - || suffixes - .iter() - .map(|suffix| { - s.starts_with(*suffix) - && s.chars() - .nth(suffix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - }) - .map(|(ns, (_, _))| *ns) - .next() + }) + .map(|(ns, (_, _))| *ns) + .next() } } // Strip prefixes, suffixes, and inline code marks from the given string. fn strip_prefixes_suffixes(mut s: &str) -> &str { s = s.trim_matches('`'); - NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| { - prefixes.iter().for_each(|prefix| s = s.trim_start_matches(*prefix)); - suffixes.iter().for_each(|suffix| s = s.trim_end_matches(*suffix)); + + [ + (TYPES.0.iter(), TYPES.1.iter()), + (VALUES.0.iter(), VALUES.1.iter()), + (MACROS.0.iter(), MACROS.1.iter()), + ] + .iter() + .for_each(|(prefixes, suffixes)| { + prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); }); s.trim_start_matches("@").trim() } @@ -493,6 +483,8 @@ fn try_resolve_intra( link_text: &str, link_target: &str, ) -> Option<(String, String)> { + eprintln!("resolving intra"); + // Set link_target for implied shortlinks let link_target = if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; @@ -551,6 +543,8 @@ fn try_resolve_intra( /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { + eprintln!("resolving path"); + if !link.contains("#") && !link.contains(".html") { return None; } -- cgit v1.2.3 From a7a00a87fd20a8b697a603d68e60e6ffbc59aac8 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 12:39:07 +1200 Subject: Improve namespace parsing comment --- crates/ra_ide/src/hover.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index f6854a18d..784a1e77c 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -423,6 +423,12 @@ static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); impl Namespace { /// Extract the specified namespace from an intra-doc-link if one exists. + /// + /// # Examples + /// + /// * `struct MyStruct` -> `Namespace::Types` + /// * `panic!` -> `Namespace::Macros` + /// * `fn@from_intra_spec` -> `Namespace::Values` fn from_intra_spec(s: &str) -> Option { [ (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), @@ -532,7 +538,7 @@ fn try_resolve_intra( get_doc_url(db, &krate)? .join(&format!("{}/", krate.display_name(db)?)) .ok()? - .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")) + .join(&path.segments.iter().map(|name| name.to_string()).join("/")) .ok()? .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) .ok()? -- cgit v1.2.3 From 19c2830ff86b8765754827b03826870f8640dec2 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 12:55:04 +1200 Subject: move into separate module --- crates/ra_ide/src/hover.rs | 302 +------------------------------------- crates/ra_ide/src/lib.rs | 1 + crates/ra_ide/src/link_rewrite.rs | 302 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 307 insertions(+), 298 deletions(-) create mode 100644 crates/ra_ide/src/link_rewrite.rs (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 784a1e77c..7e78ee597 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,26 +1,20 @@ -use std::iter::once; - use hir::{ - db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, - FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, Module, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + Module, ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; -use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; -use pulldown_cmark_to_cmark::cmark; use ra_db::SourceDatabase; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; -use ra_tt::{Ident, Leaf, Literal, TokenTree}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; use stdx::format_to; use test_utils::mark; -use url::Url; use crate::{ display::{macro_label, ShortLabel, ToNav, TryToNav}, + link_rewrite::rewrite_links, markup::Markup, runnables::runnable, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, @@ -343,294 +337,6 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option { } } -// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. -fn map_links<'e>( - events: impl Iterator>, - callback: impl Fn(&str, &str) -> (String, String), -) -> impl Iterator> { - let mut in_link = false; - let mut link_target: Option = None; - - events.map(move |evt| match evt { - Event::Start(Tag::Link(_link_type, ref target, _)) => { - in_link = true; - link_target = Some(target.clone()); - evt - } - Event::End(Tag::Link(link_type, _target, _)) => { - in_link = false; - Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed(""))) - } - Event::Text(s) if in_link => { - let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); - link_target = Some(CowStr::Boxed(link_target_s.into())); - Event::Text(CowStr::Boxed(link_name.into())) - } - Event::Code(s) if in_link => { - let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); - link_target = Some(CowStr::Boxed(link_target_s.into())); - Event::Code(CowStr::Boxed(link_name.into())) - } - _ => evt, - }) -} - -/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) -fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { - let doc = Parser::new_with_broken_link_callback( - markdown, - Options::empty(), - Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), - ); - - let doc = map_links(doc, |target, title: &str| { - // This check is imperfect, there's some overlap between valid intra-doc links - // and valid URLs so we choose to be too eager to try to resolve what might be - // a URL. - if target.contains("://") { - (target.to_string(), title.to_string()) - } else { - // Two posibilities: - // * path-based links: `../../module/struct.MyStruct.html` - // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` - let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| { - try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())) - }); - - match resolved { - Some((target, title)) => (target, title), - None => (target.to_string(), title.to_string()), - } - } - }); - let mut out = String::new(); - cmark(doc, &mut out, None).ok(); - out -} - -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] -enum Namespace { - Types, - Values, - Macros, -} - -static TYPES: ([&str; 7], [&str; 0]) = - (["type", "struct", "enum", "mod", "trait", "union", "module"], []); -static VALUES: ([&str; 8], [&str; 1]) = - (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); -static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); - -impl Namespace { - /// Extract the specified namespace from an intra-doc-link if one exists. - /// - /// # Examples - /// - /// * `struct MyStruct` -> `Namespace::Types` - /// * `panic!` -> `Namespace::Macros` - /// * `fn@from_intra_spec` -> `Namespace::Values` - fn from_intra_spec(s: &str) -> Option { - [ - (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), - (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), - (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), - ] - .iter() - .filter(|(_ns, (prefixes, suffixes))| { - prefixes - .clone() - .map(|prefix| { - s.starts_with(*prefix) - && s.chars() - .nth(prefix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - || suffixes - .clone() - .map(|suffix| { - s.starts_with(*suffix) - && s.chars() - .nth(suffix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - }) - .map(|(ns, (_, _))| *ns) - .next() - } -} - -// Strip prefixes, suffixes, and inline code marks from the given string. -fn strip_prefixes_suffixes(mut s: &str) -> &str { - s = s.trim_matches('`'); - - [ - (TYPES.0.iter(), TYPES.1.iter()), - (VALUES.0.iter(), VALUES.1.iter()), - (MACROS.0.iter(), MACROS.1.iter()), - ] - .iter() - .for_each(|(prefixes, suffixes)| { - prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); - suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); - }); - s.trim_start_matches("@").trim() -} - -/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). -/// -/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra( - db: &RootDatabase, - definition: &Definition, - link_text: &str, - link_target: &str, -) -> Option<(String, String)> { - eprintln!("resolving intra"); - - // Set link_target for implied shortlinks - let link_target = - if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; - - // Namespace disambiguation - let namespace = Namespace::from_intra_spec(link_target); - - // Strip prefixes/suffixes - let link_target = strip_prefixes_suffixes(link_target); - - // Parse link as a module path - let path = Path::parse(link_target).ok()?; - let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); - - // Resolve it relative to symbol's location (according to the RFC this should consider small scopes - let resolver = definition.resolver(db)?; - - let resolved = resolver.resolve_module_path_in_items(db, &modpath); - let (defid, namespace) = match namespace { - // FIXME: .or(resolved.macros) - None => resolved - .types - .map(|t| (t.0, Namespace::Types)) - .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, - Some(ns @ Namespace::Types) => (resolved.types?.0, ns), - Some(ns @ Namespace::Values) => (resolved.values?.0, ns), - // FIXME: - Some(Namespace::Macros) => None?, - }; - - // Get the filepath of the final symbol - let def: ModuleDef = defid.into(); - let module = def.module(db)?; - let krate = module.krate(); - let ns = match namespace { - Namespace::Types => ItemInNs::Types(defid), - Namespace::Values => ItemInNs::Values(defid), - // FIXME: - Namespace::Macros => None?, - }; - let import_map = db.import_map(krate.into()); - let path = import_map.path_of(ns)?; - - Some(( - get_doc_url(db, &krate)? - .join(&format!("{}/", krate.display_name(db)?)) - .ok()? - .join(&path.segments.iter().map(|name| name.to_string()).join("/")) - .ok()? - .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) - .ok()? - .into_string(), - strip_prefixes_suffixes(link_text).to_string(), - )) -} - -/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). -fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { - eprintln!("resolving path"); - - if !link.contains("#") && !link.contains(".html") { - return None; - } - let ns = if let Definition::ModuleDef(moddef) = definition { - ItemInNs::Types(moddef.clone().into()) - } else { - return None; - }; - let module = definition.module(db)?; - let krate = module.krate(); - let import_map = db.import_map(krate.into()); - let base = once(format!("{}", krate.display_name(db)?)) - .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) - .join("/"); - - get_doc_url(db, &krate) - .and_then(|url| url.join(&base).ok()) - .and_then(|url| { - get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() - }) - .and_then(|url| url.join(link).ok()) - .map(|url| url.into_string()) -} - -/// 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 = "...")] - 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().map(|tt| { - let name = tt.token_trees.iter() - .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) - .skip(2) - .next(); - - match name { - Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), - _ => None - } - }).flat_map(|t| t).next().map(|s| s.to_string()) - } else { - // Fallback to docs.rs - // FIXME: Specify an exact version here (from Cargo.lock) - Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) - }; - - doc_url - .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") - .and_then(|s| Url::parse(&s).ok()) -} - -/// Get the filename and extension generated for a symbol by rustdoc. -/// -/// Example: `struct.Shard.html` -fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option { - Some(match definition { - Definition::ModuleDef(def) => match def { - ModuleDef::Adt(adt) => match adt { - Adt::Struct(s) => format!("struct.{}.html", s.name(db)), - Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)), - }, - ModuleDef::Module(_) => "index.html".to_string(), - ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), - ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), - ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), - ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::EnumVariant(ev) => { - format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) - } - ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), - }, - Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), - _ => None?, - }) -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 0fede0d87..e615e993a 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -44,6 +44,7 @@ mod status; mod syntax_highlighting; mod syntax_tree; mod typing; +mod link_rewrite; use std::sync::Arc; diff --git a/crates/ra_ide/src/link_rewrite.rs b/crates/ra_ide/src/link_rewrite.rs new file mode 100644 index 000000000..85e6188e8 --- /dev/null +++ b/crates/ra_ide/src/link_rewrite.rs @@ -0,0 +1,302 @@ +//! Resolves and rewrites links in markdown documentation for hovers/completion windows. + +use std::iter::once; + +use itertools::Itertools; +use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; +use pulldown_cmark_to_cmark::cmark; +use url::Url; + +use hir::{Adt, AsName, AttrDef, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; +use ra_hir_def::db::DefDatabase; +use ra_ide_db::{defs::Definition, RootDatabase}; +use ra_syntax::ast::Path; +use ra_tt::{Ident, Leaf, Literal, TokenTree}; + +/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) +pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { + let doc = Parser::new_with_broken_link_callback( + markdown, + Options::empty(), + Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), + ); + + let doc = map_links(doc, |target, title: &str| { + // This check is imperfect, there's some overlap between valid intra-doc links + // and valid URLs so we choose to be too eager to try to resolve what might be + // a URL. + if target.contains("://") { + (target.to_string(), title.to_string()) + } else { + // Two posibilities: + // * path-based links: `../../module/struct.MyStruct.html` + // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` + let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| { + try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())) + }); + + match resolved { + Some((target, title)) => (target, title), + None => (target.to_string(), title.to_string()), + } + } + }); + let mut out = String::new(); + cmark(doc, &mut out, None).ok(); + out +} + +// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. +fn map_links<'e>( + events: impl Iterator>, + callback: impl Fn(&str, &str) -> (String, String), +) -> impl Iterator> { + let mut in_link = false; + let mut link_target: Option = None; + + events.map(move |evt| match evt { + Event::Start(Tag::Link(_link_type, ref target, _)) => { + in_link = true; + link_target = Some(target.clone()); + evt + } + Event::End(Tag::Link(link_type, _target, _)) => { + in_link = false; + Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed(""))) + } + Event::Text(s) if in_link => { + let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); + link_target = Some(CowStr::Boxed(link_target_s.into())); + Event::Text(CowStr::Boxed(link_name.into())) + } + Event::Code(s) if in_link => { + let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); + link_target = Some(CowStr::Boxed(link_target_s.into())); + Event::Code(CowStr::Boxed(link_name.into())) + } + _ => evt, + }) +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +enum Namespace { + Types, + Values, + Macros, +} + +static TYPES: ([&str; 7], [&str; 0]) = + (["type", "struct", "enum", "mod", "trait", "union", "module"], []); +static VALUES: ([&str; 8], [&str; 1]) = + (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); +static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); + +impl Namespace { + /// Extract the specified namespace from an intra-doc-link if one exists. + /// + /// # Examples + /// + /// * `struct MyStruct` -> `Namespace::Types` + /// * `panic!` -> `Namespace::Macros` + /// * `fn@from_intra_spec` -> `Namespace::Values` + fn from_intra_spec(s: &str) -> Option { + [ + (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), + (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), + (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), + ] + .iter() + .filter(|(_ns, (prefixes, suffixes))| { + prefixes + .clone() + .map(|prefix| { + s.starts_with(*prefix) + && s.chars() + .nth(prefix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + || suffixes + .clone() + .map(|suffix| { + s.starts_with(*suffix) + && s.chars() + .nth(suffix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + }) + .map(|(ns, (_, _))| *ns) + .next() + } +} + +// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + + [ + (TYPES.0.iter(), TYPES.1.iter()), + (VALUES.0.iter(), VALUES.1.iter()), + (MACROS.0.iter(), MACROS.1.iter()), + ] + .iter() + .for_each(|(prefixes, suffixes)| { + prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); + }); + s.trim_start_matches("@").trim() +} + +/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). +/// +/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). +fn try_resolve_intra( + db: &RootDatabase, + definition: &Definition, + link_text: &str, + link_target: &str, +) -> Option<(String, String)> { + eprintln!("resolving intra"); + + // Set link_target for implied shortlinks + let link_target = + if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + // Strip prefixes/suffixes + let link_target = strip_prefixes_suffixes(link_target); + + // Parse link as a module path + let path = Path::parse(link_target).ok()?; + let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); + + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes + let resolver = definition.resolver(db)?; + + let resolved = resolver.resolve_module_path_in_items(db, &modpath); + let (defid, namespace) = match namespace { + // FIXME: .or(resolved.macros) + None => resolved + .types + .map(|t| (t.0, Namespace::Types)) + .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + Some(ns @ Namespace::Types) => (resolved.types?.0, ns), + Some(ns @ Namespace::Values) => (resolved.values?.0, ns), + // FIXME: + Some(Namespace::Macros) => None?, + }; + + // Get the filepath of the final symbol + let def: ModuleDef = defid.into(); + let module = def.module(db)?; + let krate = module.krate(); + let ns = match namespace { + Namespace::Types => ItemInNs::Types(defid), + Namespace::Values => ItemInNs::Values(defid), + // FIXME: + Namespace::Macros => None?, + }; + let import_map = db.import_map(krate.into()); + let path = import_map.path_of(ns)?; + + Some(( + get_doc_url(db, &krate)? + .join(&format!("{}/", krate.display_name(db)?)) + .ok()? + .join(&path.segments.iter().map(|name| name.to_string()).join("/")) + .ok()? + .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) + .ok()? + .into_string(), + strip_prefixes_suffixes(link_text).to_string(), + )) +} + +/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). +fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { + eprintln!("resolving path"); + + if !link.contains("#") && !link.contains(".html") { + return None; + } + let ns = if let Definition::ModuleDef(moddef) = definition { + ItemInNs::Types(moddef.clone().into()) + } else { + return None; + }; + let module = definition.module(db)?; + let krate = module.krate(); + let import_map = db.import_map(krate.into()); + let base = once(format!("{}", krate.display_name(db)?)) + .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) + .join("/"); + + get_doc_url(db, &krate) + .and_then(|url| url.join(&base).ok()) + .and_then(|url| { + get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() + }) + .and_then(|url| url.join(link).ok()) + .map(|url| url.into_string()) +} + +/// 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 = "...")] + 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().map(|tt| { + let name = tt.token_trees.iter() + .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) + .skip(2) + .next(); + + match name { + Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), + _ => None + } + }).flat_map(|t| t).next().map(|s| s.to_string()) + } else { + // Fallback to docs.rs + // FIXME: Specify an exact version here (from Cargo.lock) + Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) + }; + + doc_url + .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") + .and_then(|s| Url::parse(&s).ok()) +} + +/// Get the filename and extension generated for a symbol by rustdoc. +/// +/// Example: `struct.Shard.html` +fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option { + Some(match definition { + Definition::ModuleDef(def) => match def { + ModuleDef::Adt(adt) => match adt { + Adt::Struct(s) => format!("struct.{}.html", s.name(db)), + Adt::Enum(e) => format!("enum.{}.html", e.name(db)), + Adt::Union(u) => format!("union.{}.html", u.name(db)), + }, + ModuleDef::Module(_) => "index.html".to_string(), + ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), + ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), + ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), + ModuleDef::EnumVariant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } + ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), + }, + Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), + _ => None?, + }) +} -- cgit v1.2.3 From b7bbfc2543af326783cce7d6975efb809766a534 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 14:08:24 +1200 Subject: pulldown_cmark no-default-features --- crates/ra_ide/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index fdbd7566f..0302924d2 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -19,7 +19,7 @@ log = "0.4.8" rustc-hash = "1.1.0" url = "*" pulldown-cmark-to-cmark = "4.0.2" -pulldown-cmark = "0.7.0" +pulldown-cmark = {version = "0.7.2", default-features = false} oorandom = "11.1.2" stdx = { path = "../stdx" } -- cgit v1.2.3 From c4d4869f23dbe481c42a8fb24d2bc43e36b88933 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sat, 1 Aug 2020 19:11:43 +1200 Subject: remove debug eprintln! --- crates/ra_ide/src/link_rewrite.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/src/link_rewrite.rs b/crates/ra_ide/src/link_rewrite.rs index 85e6188e8..1065eb619 100644 --- a/crates/ra_ide/src/link_rewrite.rs +++ b/crates/ra_ide/src/link_rewrite.rs @@ -159,8 +159,6 @@ fn try_resolve_intra( link_text: &str, link_target: &str, ) -> Option<(String, String)> { - eprintln!("resolving intra"); - // Set link_target for implied shortlinks let link_target = if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; @@ -175,7 +173,7 @@ fn try_resolve_intra( let path = Path::parse(link_target).ok()?; let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); - // Resolve it relative to symbol's location (according to the RFC this should consider small scopes + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) let resolver = definition.resolver(db)?; let resolved = resolver.resolve_module_path_in_items(db, &modpath); @@ -219,8 +217,6 @@ fn try_resolve_intra( /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { - eprintln!("resolving path"); - if !link.contains("#") && !link.contains(".html") { return None; } -- cgit v1.2.3 From b34ad4bc8ad368a096c14d3710239a5a2eb9ccb5 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Sun, 2 Aug 2020 20:19:14 +1200 Subject: update & configure cmark to render three backticks --- crates/ra_ide/Cargo.toml | 4 +- crates/ra_ide/src/hover.rs | 332 ++++++++++++------------- crates/ra_ide/src/link_rewrite.rs | 6 +- crates/rust-analyzer/tests/heavy_tests/main.rs | 2 +- 4 files changed, 173 insertions(+), 171 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 0302924d2..485aa9081 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -17,8 +17,8 @@ indexmap = "1.3.2" itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" -url = "*" -pulldown-cmark-to-cmark = "4.0.2" +url = "2.1.1" +pulldown-cmark-to-cmark = "5.0.0" pulldown-cmark = {version = "0.7.2", default-features = false} oorandom = "11.1.2" diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 7e78ee597..5aacb4c14 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -425,9 +425,9 @@ fn main() { expect![[r#" *iter* - ````rust + ```rust Iter>, |&mut u32, &u32, &mut u32| -> Option, u32>> - ```` + ``` "#]], ); } @@ -444,13 +444,13 @@ fn main() { let foo_test = fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub fn foo() -> u32 - ```` + ``` "#]], ); @@ -493,13 +493,13 @@ fn main() { let foo_test = fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str - ```` + ``` "#]], ); } @@ -515,13 +515,13 @@ fn main() { } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub fn foo(a: u32, b: u32) -> u32 - ```` + ``` "#]], ); } @@ -540,13 +540,13 @@ fn main() { expect![[r#" *field_a* - ````rust + ```rust test::Foo - ```` + ``` - ````rust + ```rust field_a: u32 - ```` + ``` "#]], ); @@ -562,13 +562,13 @@ fn main() { expect![[r#" *field_a* - ````rust + ```rust test::Foo - ```` + ``` - ````rust + ```rust field_a: u32 - ```` + ``` "#]], ); } @@ -580,13 +580,13 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust const foo: u32 - ```` + ``` "#]], ); check( @@ -594,13 +594,13 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust static foo: u32 - ```` + ``` "#]], ); } @@ -617,9 +617,9 @@ fn main() { expect![[r#" *zz* - ````rust + ```rust Test - ```` + ``` "#]], ); } @@ -636,13 +636,13 @@ fn main() { So<|>me(12); } expect![[r#" *Some* - ````rust + ```rust test::Option - ```` + ``` - ````rust + ```rust Some - ```` + ``` "#]], ); @@ -656,9 +656,9 @@ fn main() { let b<|>ar = Some(12); } expect![[r#" *bar* - ````rust + ```rust Option - ```` + ``` "#]], ); } @@ -675,13 +675,13 @@ enum Option { expect![[r#" *None* - ````rust + ```rust test::Option - ```` + ``` - ````rust + ```rust None - ```` + ``` --- @@ -702,13 +702,13 @@ fn main() { expect![[r#" *Some* - ````rust + ```rust test::Option - ```` + ``` - ````rust + ```rust Some - ```` + ``` --- @@ -724,9 +724,9 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust i32 - ```` + ``` "#]], ) } @@ -738,9 +738,9 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust i32 - ```` + ``` "#]], ) } @@ -752,9 +752,9 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust i32 - ```` + ``` "#]], ) } @@ -766,9 +766,9 @@ fn main() { expect![[r#" *foo* - ````rust + ```rust i32 - ```` + ``` "#]], ) } @@ -788,9 +788,9 @@ fn main() { let foo_<|>test = Thing::new(); } expect![[r#" *foo_test* - ````rust + ```rust Thing - ```` + ``` "#]], ) } @@ -812,13 +812,13 @@ fn main() { let foo_test = wrapper::Thing::new<|>(); } expect![[r#" *new* - ````rust + ```rust test::wrapper::Thing - ```` + ``` - ````rust + ```rust fn new() -> Thing - ```` + ``` "#]], ) } @@ -843,13 +843,13 @@ fn main() { expect![[r#" *C* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust const C: u32 - ```` + ``` "#]], ) } @@ -925,9 +925,9 @@ fn y() { expect![[r#" *x* - ````rust + ```rust i32 - ```` + ``` "#]], ) } @@ -943,13 +943,13 @@ fn f() { fo<|>o!(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust macro_rules! foo - ```` + ``` "#]], ) } @@ -978,13 +978,13 @@ id! { expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust fn foo() - ```` + ``` "#]], ); } @@ -999,9 +999,9 @@ fn foo(bar:u32) { let a = id!(ba<|>r); } expect![[r#" *bar* - ````rust + ```rust u32 - ```` + ``` "#]], ); } @@ -1017,9 +1017,9 @@ fn foo(bar:u32) { let a = id!(ba<|>r); } expect![[r#" *bar* - ````rust + ```rust u32 - ```` + ``` "#]], ); } @@ -1076,13 +1076,13 @@ fn foo() { expect![[r#" *bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust fn bar() -> bool - ```` + ``` "#]], ); } @@ -1113,13 +1113,13 @@ fn bar() { fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust fn foo() - ```` + ``` --- @@ -1135,13 +1135,13 @@ fn bar() { fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust async fn foo() - ```` + ``` "#]], ); check( @@ -1149,13 +1149,13 @@ fn bar() { fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub const unsafe fn foo() - ```` + ``` "#]], ); check( @@ -1163,13 +1163,13 @@ fn bar() { fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub(crate) async unsafe extern "C" fn foo() - ```` + ``` "#]], ); } @@ -1205,13 +1205,13 @@ fn my() {} expect![[r#" *my* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust mod my - ```` + ``` "#]], ); } @@ -1228,13 +1228,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust struct Bar - ```` + ``` --- @@ -1255,13 +1255,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust struct Bar - ```` + ``` --- @@ -1284,13 +1284,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust struct Bar - ```` + ``` --- @@ -1315,13 +1315,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1342,13 +1342,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1371,13 +1371,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1398,13 +1398,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1425,13 +1425,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1453,13 +1453,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1480,13 +1480,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1507,13 +1507,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1536,13 +1536,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1563,13 +1563,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1591,13 +1591,13 @@ fn foo() { let bar = Ba<|>r; } expect![[r#" *Bar* - ````rust + ```rust test - ```` + ``` - ````rust + ```rust pub struct Bar - ```` + ``` --- @@ -1629,13 +1629,13 @@ fn foo() { let bar = Bar; bar.fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test::Bar - ```` + ``` - ````rust + ```rust fn foo(&self) - ```` + ``` --- @@ -1667,13 +1667,13 @@ fn foo() { let bar = Bar; bar.fo<|>o(); } expect![[r#" *foo* - ````rust + ```rust test::Bar - ```` + ``` - ````rust + ```rust fn foo(&self) - ```` + ``` --- diff --git a/crates/ra_ide/src/link_rewrite.rs b/crates/ra_ide/src/link_rewrite.rs index 1065eb619..37d695bb8 100644 --- a/crates/ra_ide/src/link_rewrite.rs +++ b/crates/ra_ide/src/link_rewrite.rs @@ -4,7 +4,7 @@ use std::iter::once; use itertools::Itertools; use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; -use pulldown_cmark_to_cmark::cmark; +use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; use url::Url; use hir::{Adt, AsName, AttrDef, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; @@ -42,7 +42,9 @@ pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) } }); let mut out = String::new(); - cmark(doc, &mut out, None).ok(); + let mut options = CmarkOptions::default(); + options.code_block_backticks = 3; + cmark_with_options(doc, &mut out, None, options).ok(); out } diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 91ce04731..f2a634051 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -680,5 +680,5 @@ pub fn foo(_input: TokenStream) -> TokenStream { }); let value = res.get("contents").unwrap().get("value").unwrap().to_string(); - assert_eq!(value, r#""\n````rust\nfoo::Bar\n````\n\n````rust\nfn bar()\n````""#) + assert_eq!(value, r#""\n```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#) } -- cgit v1.2.3 From 4f5f608b499ad50aa66b2213e3b92dd2fe61ed68 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 24 Aug 2020 20:26:45 +1200 Subject: Reorganise code --- crates/ra_hir/Cargo.toml | 3 + crates/ra_hir/src/code_model.rs | 104 +++++++++++++++- crates/ra_hir/src/lib.rs | 2 + crates/ra_hir/src/link_rewrite.rs | 226 +++++++++++++++++++++++++++++++++++ crates/ra_ide/Cargo.toml | 6 +- crates/ra_ide/src/link_rewrite.rs | 241 ++------------------------------------ crates/ra_ide_db/Cargo.toml | 1 - crates/ra_ide_db/src/defs.rs | 17 --- 8 files changed, 345 insertions(+), 255 deletions(-) create mode 100644 crates/ra_hir/src/link_rewrite.rs (limited to 'crates') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index c260bb193..a8c4875d4 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -16,6 +16,8 @@ arrayvec = "0.5.1" itertools = "0.9.0" +url = "2.1.1" + stdx = { path = "../stdx" } ra_syntax = { path = "../ra_syntax" } ra_db = { path = "../ra_db" } @@ -23,3 +25,4 @@ ra_prof = { path = "../ra_prof" } hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } +ra_tt = { path = "../ra_tt" } diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 8284ab757..8154e9bf6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -16,7 +16,7 @@ use hir_def::{ type_ref::{Mutability, TypeRef}, AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId, - TraitId, TypeAliasId, TypeParamId, UnionId, + TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; use hir_expand::{ diagnostics::DiagnosticSink, @@ -35,12 +35,14 @@ use ra_syntax::{ ast::{self, AttrsOwner, NameOwner}, AstNode, }; +use ra_tt::{Ident, Leaf, Literal, TokenTree}; use rustc_hash::FxHashSet; use stdx::impl_from; use crate::{ db::{DefDatabase, HirDatabase}, has_source::HasSource, + link_rewrite::Resolvable, HirDisplay, InFile, Name, }; @@ -120,6 +122,33 @@ impl Crate { pub fn all(db: &dyn HirDatabase) -> Vec { db.crate_graph().iter().map(|id| Crate { id }).collect() } + + /// Try to get the root URL of the documentation of a crate. + pub fn get_doc_url(self: &Crate, db: &dyn HirDatabase) -> Option { + // Look for #![doc(html_root_url = "...")] + let attrs = db.attrs(AttrDef::from(self.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().map(|tt| { + let name = tt.token_trees.iter() + .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) + .skip(2) + .next(); + + match name { + Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), + _ => None + } + }).flat_map(|t| t).next().map(|s| s.to_string()) + } else { + None + }; + + doc_url + .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") + .map(|s| s.to_string()) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -1709,3 +1738,76 @@ pub trait HasVisibility { vis.is_visible_from(db.upcast(), module.id) } } + +impl Resolvable for ModuleDef { + fn resolver(&self, db: &D) -> Option { + Some(match self { + ModuleDef::Module(m) => ModuleId::from(m.clone()).resolver(db), + ModuleDef::Function(f) => FunctionId::from(f.clone()).resolver(db), + ModuleDef::Adt(adt) => AdtId::from(adt.clone()).resolver(db), + ModuleDef::EnumVariant(ev) => { + GenericDefId::from(GenericDef::from(ev.clone())).resolver(db) + } + ModuleDef::Const(c) => GenericDefId::from(GenericDef::from(c.clone())).resolver(db), + ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db), + ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db), + ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db), + // FIXME: This should be a resolver relative to `std/core` + ModuleDef::BuiltinType(_t) => None?, + }) + } + + fn try_into_module_def(self) -> Option { + Some(self) + } +} + +impl Resolvable for TypeParam { + fn resolver(&self, db: &D) -> Option { + Some(Into::::into(self.module(db)).resolver(db)) + } + + fn try_into_module_def(self) -> Option { + None + } +} + +impl Resolvable for MacroDef { + fn resolver(&self, db: &D) -> Option { + Some(Into::::into(self.module(db)?).resolver(db)) + } + + fn try_into_module_def(self) -> Option { + None + } +} + +impl Resolvable for Field { + fn resolver(&self, db: &D) -> Option { + Some(Into::::into(Into::::into(self.parent_def(db))).resolver(db)) + } + + fn try_into_module_def(self) -> Option { + None + } +} + +impl Resolvable for ImplDef { + fn resolver(&self, db: &D) -> Option { + Some(Into::::into(self.module(db)).resolver(db)) + } + + fn try_into_module_def(self) -> Option { + None + } +} + +impl Resolvable for Local { + fn resolver(&self, db: &D) -> Option { + Some(Into::::into(self.module(db)).resolver(db)) + } + + fn try_into_module_def(self) -> Option { + None + } +} diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index e2d13dbfd..b33293a18 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -27,6 +27,7 @@ pub mod diagnostics; mod from_id; mod code_model; +mod link_rewrite; mod has_source; @@ -38,6 +39,7 @@ pub use crate::{ Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility, }, has_source::HasSource, + link_rewrite::resolve_doc_link, semantics::{original_range, PathResolution, Semantics, SemanticsScope}, }; diff --git a/crates/ra_hir/src/link_rewrite.rs b/crates/ra_hir/src/link_rewrite.rs new file mode 100644 index 000000000..a66f1e6fc --- /dev/null +++ b/crates/ra_hir/src/link_rewrite.rs @@ -0,0 +1,226 @@ +//! Resolves and rewrites links in markdown documentation for hovers/completion windows. + +use std::iter::once; + +use itertools::Itertools; +use url::Url; + +use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; +use hir_def::{db::DefDatabase, resolver::Resolver}; +use ra_syntax::ast::Path; + +pub fn resolve_doc_link( + db: &D, + definition: &T, + link_text: &str, + link_target: &str, +) -> Option<(String, String)> { + try_resolve_intra(db, definition, link_text, &link_target).or_else(|| { + if let Some(definition) = definition.clone().try_into_module_def() { + try_resolve_path(db, &definition, &link_target) + .map(|target| (target, link_text.to_string())) + } else { + None + } + }) +} + +/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). +/// +/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). +fn try_resolve_intra( + db: &D, + definition: &T, + link_text: &str, + link_target: &str, +) -> Option<(String, String)> { + // Set link_target for implied shortlinks + let link_target = + if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + // Strip prefixes/suffixes + let link_target = strip_prefixes_suffixes(link_target); + + // Parse link as a module path + let path = Path::parse(link_target).ok()?; + let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); + + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) + let resolver = definition.resolver(db)?; + + let resolved = resolver.resolve_module_path_in_items(db, &modpath); + let (defid, namespace) = match namespace { + // FIXME: .or(resolved.macros) + None => resolved + .types + .map(|t| (t.0, Namespace::Types)) + .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + Some(ns @ Namespace::Types) => (resolved.types?.0, ns), + Some(ns @ Namespace::Values) => (resolved.values?.0, ns), + // FIXME: + Some(Namespace::Macros) => None?, + }; + + // Get the filepath of the final symbol + let def: ModuleDef = defid.into(); + let module = def.module(db)?; + let krate = module.krate(); + let ns = match namespace { + Namespace::Types => ItemInNs::Types(defid), + Namespace::Values => ItemInNs::Values(defid), + // FIXME: + Namespace::Macros => None?, + }; + let import_map = db.import_map(krate.into()); + let path = import_map.path_of(ns)?; + + Some(( + get_doc_url(db, &krate)? + .join(&format!("{}/", krate.display_name(db)?)) + .ok()? + .join(&path.segments.iter().map(|name| name.to_string()).join("/")) + .ok()? + .join(&get_symbol_filename(db, &def)?) + .ok()? + .into_string(), + strip_prefixes_suffixes(link_text).to_string(), + )) +} + +/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). +fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str) -> Option { + if !link_target.contains("#") && !link_target.contains(".html") { + return None; + } + let ns = ItemInNs::Types(moddef.clone().into()); + + let module = moddef.module(db)?; + let krate = module.krate(); + let import_map = db.import_map(krate.into()); + let base = once(format!("{}", krate.display_name(db)?)) + .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) + .join("/"); + + get_doc_url(db, &krate) + .and_then(|url| url.join(&base).ok()) + .and_then(|url| { + get_symbol_filename(db, moddef).as_deref().map(|f| url.join(f).ok()).flatten() + }) + .and_then(|url| url.join(link_target).ok()) + .map(|url| url.into_string()) +} + +// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + + [ + (TYPES.0.iter(), TYPES.1.iter()), + (VALUES.0.iter(), VALUES.1.iter()), + (MACROS.0.iter(), MACROS.1.iter()), + ] + .iter() + .for_each(|(prefixes, suffixes)| { + prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); + }); + let s = s.trim_start_matches("@").trim(); + s +} + +fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option { + krate + .get_doc_url(db) + .or_else(|| + // Fallback to docs.rs + // FIXME: Specify an exact version here. This may be difficult, as multiple versions of the same crate could exist. + Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?))) + .and_then(|s| Url::parse(&s).ok()) +} + +/// Get the filename and extension generated for a symbol by rustdoc. +/// +/// Example: `struct.Shard.html` +fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { + Some(match definition { + ModuleDef::Adt(adt) => match adt { + Adt::Struct(s) => format!("struct.{}.html", s.name(db)), + Adt::Enum(e) => format!("enum.{}.html", e.name(db)), + Adt::Union(u) => format!("union.{}.html", u.name(db)), + }, + ModuleDef::Module(_) => "index.html".to_string(), + ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), + ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), + ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), + ModuleDef::EnumVariant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } + ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), + }) +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +enum Namespace { + Types, + Values, + Macros, +} + +static TYPES: ([&str; 7], [&str; 0]) = + (["type", "struct", "enum", "mod", "trait", "union", "module"], []); +static VALUES: ([&str; 8], [&str; 1]) = + (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); +static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); + +impl Namespace { + /// Extract the specified namespace from an intra-doc-link if one exists. + /// + /// # Examples + /// + /// * `struct MyStruct` -> `Namespace::Types` + /// * `panic!` -> `Namespace::Macros` + /// * `fn@from_intra_spec` -> `Namespace::Values` + fn from_intra_spec(s: &str) -> Option { + [ + (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), + (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), + (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), + ] + .iter() + .filter(|(_ns, (prefixes, suffixes))| { + prefixes + .clone() + .map(|prefix| { + s.starts_with(*prefix) + && s.chars() + .nth(prefix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + || suffixes + .clone() + .map(|suffix| { + s.starts_with(*suffix) + && s.chars() + .nth(suffix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + }) + .map(|(ns, (_, _))| *ns) + .next() + } +} + +/// Sealed trait used solely for the generic bound on [`resolve_doc_link`]. +pub trait Resolvable { + fn resolver(&self, db: &D) -> Option; + fn try_into_module_def(self) -> Option; +} diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 485aa9081..de606f57e 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml @@ -17,10 +17,9 @@ indexmap = "1.3.2" itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" -url = "2.1.1" +oorandom = "11.1.2" pulldown-cmark-to-cmark = "5.0.0" pulldown-cmark = {version = "0.7.2", default-features = false} -oorandom = "11.1.2" stdx = { path = "../stdx" } @@ -34,9 +33,6 @@ ra_prof = { path = "../ra_prof" } test_utils = { path = "../test_utils" } ra_assists = { path = "../ra_assists" } ra_ssr = { path = "../ra_ssr" } -ra_hir_def = { path = "../ra_hir_def" } -ra_tt = { path = "../ra_tt" } -ra_parser = { path = "../ra_parser" } # 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/link_rewrite.rs b/crates/ra_ide/src/link_rewrite.rs index 37d695bb8..94d2c31c2 100644 --- a/crates/ra_ide/src/link_rewrite.rs +++ b/crates/ra_ide/src/link_rewrite.rs @@ -1,17 +1,10 @@ -//! Resolves and rewrites links in markdown documentation for hovers/completion windows. +//! This is a wrapper around [`hir::link_rewrite`] connecting it to the markdown parser. -use std::iter::once; - -use itertools::Itertools; use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; -use url::Url; -use hir::{Adt, AsName, AttrDef, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; -use ra_hir_def::db::DefDatabase; +use hir::resolve_doc_link; use ra_ide_db::{defs::Definition, RootDatabase}; -use ra_syntax::ast::Path; -use ra_tt::{Ident, Leaf, Literal, TokenTree}; /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { @@ -31,9 +24,14 @@ pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) // Two posibilities: // * path-based links: `../../module/struct.MyStruct.html` // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` - let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| { - try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())) - }); + let resolved = match definition { + Definition::ModuleDef(t) => resolve_doc_link(db, t, title, target), + Definition::Macro(t) => resolve_doc_link(db, t, title, target), + Definition::Field(t) => resolve_doc_link(db, t, title, target), + Definition::SelfType(t) => resolve_doc_link(db, t, title, target), + Definition::Local(t) => resolve_doc_link(db, t, title, target), + Definition::TypeParam(t) => resolve_doc_link(db, t, title, target), + }; match resolved { Some((target, title)) => (target, title), @@ -79,222 +77,3 @@ fn map_links<'e>( _ => evt, }) } - -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] -enum Namespace { - Types, - Values, - Macros, -} - -static TYPES: ([&str; 7], [&str; 0]) = - (["type", "struct", "enum", "mod", "trait", "union", "module"], []); -static VALUES: ([&str; 8], [&str; 1]) = - (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); -static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); - -impl Namespace { - /// Extract the specified namespace from an intra-doc-link if one exists. - /// - /// # Examples - /// - /// * `struct MyStruct` -> `Namespace::Types` - /// * `panic!` -> `Namespace::Macros` - /// * `fn@from_intra_spec` -> `Namespace::Values` - fn from_intra_spec(s: &str) -> Option { - [ - (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), - (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), - (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), - ] - .iter() - .filter(|(_ns, (prefixes, suffixes))| { - prefixes - .clone() - .map(|prefix| { - s.starts_with(*prefix) - && s.chars() - .nth(prefix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - || suffixes - .clone() - .map(|suffix| { - s.starts_with(*suffix) - && s.chars() - .nth(suffix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - }) - .map(|(ns, (_, _))| *ns) - .next() - } -} - -// Strip prefixes, suffixes, and inline code marks from the given string. -fn strip_prefixes_suffixes(mut s: &str) -> &str { - s = s.trim_matches('`'); - - [ - (TYPES.0.iter(), TYPES.1.iter()), - (VALUES.0.iter(), VALUES.1.iter()), - (MACROS.0.iter(), MACROS.1.iter()), - ] - .iter() - .for_each(|(prefixes, suffixes)| { - prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); - suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); - }); - s.trim_start_matches("@").trim() -} - -/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). -/// -/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra( - db: &RootDatabase, - definition: &Definition, - link_text: &str, - link_target: &str, -) -> Option<(String, String)> { - // Set link_target for implied shortlinks - let link_target = - if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; - - // Namespace disambiguation - let namespace = Namespace::from_intra_spec(link_target); - - // Strip prefixes/suffixes - let link_target = strip_prefixes_suffixes(link_target); - - // Parse link as a module path - let path = Path::parse(link_target).ok()?; - let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); - - // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) - let resolver = definition.resolver(db)?; - - let resolved = resolver.resolve_module_path_in_items(db, &modpath); - let (defid, namespace) = match namespace { - // FIXME: .or(resolved.macros) - None => resolved - .types - .map(|t| (t.0, Namespace::Types)) - .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, - Some(ns @ Namespace::Types) => (resolved.types?.0, ns), - Some(ns @ Namespace::Values) => (resolved.values?.0, ns), - // FIXME: - Some(Namespace::Macros) => None?, - }; - - // Get the filepath of the final symbol - let def: ModuleDef = defid.into(); - let module = def.module(db)?; - let krate = module.krate(); - let ns = match namespace { - Namespace::Types => ItemInNs::Types(defid), - Namespace::Values => ItemInNs::Values(defid), - // FIXME: - Namespace::Macros => None?, - }; - let import_map = db.import_map(krate.into()); - let path = import_map.path_of(ns)?; - - Some(( - get_doc_url(db, &krate)? - .join(&format!("{}/", krate.display_name(db)?)) - .ok()? - .join(&path.segments.iter().map(|name| name.to_string()).join("/")) - .ok()? - .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) - .ok()? - .into_string(), - strip_prefixes_suffixes(link_text).to_string(), - )) -} - -/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). -fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option { - if !link.contains("#") && !link.contains(".html") { - return None; - } - let ns = if let Definition::ModuleDef(moddef) = definition { - ItemInNs::Types(moddef.clone().into()) - } else { - return None; - }; - let module = definition.module(db)?; - let krate = module.krate(); - let import_map = db.import_map(krate.into()); - let base = once(format!("{}", krate.display_name(db)?)) - .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) - .join("/"); - - get_doc_url(db, &krate) - .and_then(|url| url.join(&base).ok()) - .and_then(|url| { - get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() - }) - .and_then(|url| url.join(link).ok()) - .map(|url| url.into_string()) -} - -/// 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 = "...")] - 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().map(|tt| { - let name = tt.token_trees.iter() - .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) - .skip(2) - .next(); - - match name { - Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), - _ => None - } - }).flat_map(|t| t).next().map(|s| s.to_string()) - } else { - // Fallback to docs.rs - // FIXME: Specify an exact version here (from Cargo.lock) - Some(format!("https://docs.rs/{}/*", krate.display_name(db)?)) - }; - - doc_url - .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") - .and_then(|s| Url::parse(&s).ok()) -} - -/// Get the filename and extension generated for a symbol by rustdoc. -/// -/// Example: `struct.Shard.html` -fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option { - Some(match definition { - Definition::ModuleDef(def) => match def { - ModuleDef::Adt(adt) => match adt { - Adt::Struct(s) => format!("struct.{}.html", s.name(db)), - Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)), - }, - ModuleDef::Module(_) => "index.html".to_string(), - ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), - ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), - ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), - ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::EnumVariant(ev) => { - format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) - } - ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), - }, - Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), - _ => None?, - }) -} diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml index f345f1de8..2716a38cc 100644 --- a/crates/ra_ide_db/Cargo.toml +++ b/crates/ra_ide_db/Cargo.toml @@ -26,7 +26,6 @@ ra_text_edit = { path = "../ra_text_edit" } ra_db = { path = "../ra_db" } ra_prof = { path = "../ra_prof" } test_utils = { path = "../test_utils" } -ra_hir_def = { path = "../ra_hir_def" } # 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_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 80c99935d..df56f2d9e 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -6,7 +6,6 @@ // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). use hir::{ - db::{DefDatabase, HirDatabase}, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, Semantics, TypeParam, Visibility, }; @@ -17,7 +16,6 @@ use ra_syntax::{ }; use crate::RootDatabase; -use ra_hir_def::resolver::{HasResolver, Resolver}; // FIXME: a more precise name would probably be `Symbol`? #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -78,21 +76,6 @@ impl Definition { }; Some(name) } - - pub fn resolver(&self, db: &D) -> Option { - use hir::VariantDef; - use ra_hir_def::*; - Some(match self { - Definition::ModuleDef(def) => def.resolver(db)?, - Definition::Field(field) => { - Into::::into(Into::::into(field.parent_def(db))).resolver(db) - } - Definition::Macro(m) => Into::::into(m.module(db)?).resolver(db), - Definition::SelfType(imp) => Into::::into(imp.clone()).resolver(db), - Definition::Local(local) => Into::::into(local.parent(db)).resolver(db), - Definition::TypeParam(tp) => Into::::into(tp.module(db)).resolver(db), - }) - } } #[derive(Debug)] -- cgit v1.2.3 From ca464650eeaca6195891199a93f4f76cf3e7e697 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 24 Aug 2020 20:47:10 +1200 Subject: Add a test to document unimplemented case --- crates/ra_ide/src/hover.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'crates') diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 5aacb4c14..1c3dfd491 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1357,6 +1357,36 @@ fn foo() { let bar = Ba<|>r; } ); } + #[ignore = "path based links currently only support documentation on ModuleDef items"] + #[test] + fn test_hover_path_link_field() { + check( + r" + //- /lib.rs + pub struct Foo; + pub struct Bar { + /// [Foo](struct.Foo.html) + fie<|>ld: () + } + ", + expect![[r#" + *field* + + ```rust + test::Bar + ``` + + ```rust + field: () + ``` + + --- + + [Foo](https://docs.rs/test/*/test/struct.Foo.html) + "#]], + ); + } + #[test] fn test_hover_intra_link() { check( @@ -1386,6 +1416,38 @@ fn foo() { let bar = Ba<|>r; } ); } + #[test] + fn test_hover_intra_link_html_root_url() { + check( + r#" + //- /lib.rs + + #![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)] + + pub mod foo { + pub struct Foo; + } + /// [Foo](foo::Foo) + pub struct B<|>ar + "#, + expect![[r#" + *Bar* + + ```rust + test + ``` + + ```rust + pub struct Bar + ``` + + --- + + [Foo](https://example.com/test/foo/struct.Foo.html) + "#]], + ); + } + #[test] fn test_hover_intra_link_shortlink() { check( -- cgit v1.2.3 From 5452368fad8ee8d03d980de47604fa108111ea57 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 24 Aug 2020 21:50:30 +1200 Subject: Renames, comments, and dead code removal --- crates/hir/src/code_model.rs | 19 +--- crates/hir/src/doc_links.rs | 226 +++++++++++++++++++++++++++++++++++++++++ crates/hir/src/lib.rs | 4 +- crates/hir/src/link_rewrite.rs | 226 ----------------------------------------- crates/ide/src/link_rewrite.rs | 4 +- 5 files changed, 232 insertions(+), 247 deletions(-) create mode 100644 crates/hir/src/doc_links.rs delete mode 100644 crates/hir/src/link_rewrite.rs (limited to 'crates') diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 9395efe4f..bd80102fa 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -43,8 +43,8 @@ use tt::{Ident, Leaf, Literal, TokenTree}; use crate::{ db::{DefDatabase, HirDatabase}, + doc_links::Resolvable, has_source::HasSource, - link_rewrite::Resolvable, HirDisplay, InFile, Name, }; @@ -234,23 +234,6 @@ impl ModuleDef { ModuleDef::BuiltinType(it) => Some(it.as_name()), } } - - pub fn resolver(&self, db: &D) -> Option { - Some(match self { - ModuleDef::Module(m) => ModuleId::from(m.clone()).resolver(db), - ModuleDef::Function(f) => FunctionId::from(f.clone()).resolver(db), - ModuleDef::Adt(adt) => AdtId::from(adt.clone()).resolver(db), - ModuleDef::EnumVariant(ev) => { - GenericDefId::from(GenericDef::from(ev.clone())).resolver(db) - } - ModuleDef::Const(c) => GenericDefId::from(GenericDef::from(c.clone())).resolver(db), - ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db), - ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db), - ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db), - // FIXME: This should be a resolver relative to `std/core` - ModuleDef::BuiltinType(_t) => None?, - }) - } } pub use hir_def::{ diff --git a/crates/hir/src/doc_links.rs b/crates/hir/src/doc_links.rs new file mode 100644 index 000000000..45b26519e --- /dev/null +++ b/crates/hir/src/doc_links.rs @@ -0,0 +1,226 @@ +//! Resolves links in markdown documentation. + +use std::iter::once; + +use itertools::Itertools; +use url::Url; + +use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; +use hir_def::{db::DefDatabase, resolver::Resolver}; +use syntax::ast::Path; + +pub fn resolve_doc_link( + db: &D, + definition: &T, + link_text: &str, + link_target: &str, +) -> Option<(String, String)> { + try_resolve_intra(db, definition, link_text, &link_target).or_else(|| { + if let Some(definition) = definition.clone().try_into_module_def() { + try_resolve_path(db, &definition, &link_target) + .map(|target| (target, link_text.to_string())) + } else { + None + } + }) +} + +/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). +/// +/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). +fn try_resolve_intra( + db: &D, + definition: &T, + link_text: &str, + link_target: &str, +) -> Option<(String, String)> { + // Set link_target for implied shortlinks + let link_target = + if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; + + // Namespace disambiguation + let namespace = Namespace::from_intra_spec(link_target); + + // Strip prefixes/suffixes + let link_target = strip_prefixes_suffixes(link_target); + + // Parse link as a module path + let path = Path::parse(link_target).ok()?; + let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); + + // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) + let resolver = definition.resolver(db)?; + + let resolved = resolver.resolve_module_path_in_items(db, &modpath); + let (defid, namespace) = match namespace { + // FIXME: .or(resolved.macros) + None => resolved + .types + .map(|t| (t.0, Namespace::Types)) + .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, + Some(ns @ Namespace::Types) => (resolved.types?.0, ns), + Some(ns @ Namespace::Values) => (resolved.values?.0, ns), + // FIXME: + Some(Namespace::Macros) => None?, + }; + + // Get the filepath of the final symbol + let def: ModuleDef = defid.into(); + let module = def.module(db)?; + let krate = module.krate(); + let ns = match namespace { + Namespace::Types => ItemInNs::Types(defid), + Namespace::Values => ItemInNs::Values(defid), + // FIXME: + Namespace::Macros => None?, + }; + let import_map = db.import_map(krate.into()); + let path = import_map.path_of(ns)?; + + Some(( + get_doc_url(db, &krate)? + .join(&format!("{}/", krate.display_name(db)?)) + .ok()? + .join(&path.segments.iter().map(|name| name.to_string()).join("/")) + .ok()? + .join(&get_symbol_filename(db, &def)?) + .ok()? + .into_string(), + strip_prefixes_suffixes(link_text).to_string(), + )) +} + +/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). +fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str) -> Option { + if !link_target.contains("#") && !link_target.contains(".html") { + return None; + } + let ns = ItemInNs::Types(moddef.clone().into()); + + let module = moddef.module(db)?; + let krate = module.krate(); + let import_map = db.import_map(krate.into()); + let base = once(format!("{}", krate.display_name(db)?)) + .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) + .join("/"); + + get_doc_url(db, &krate) + .and_then(|url| url.join(&base).ok()) + .and_then(|url| { + get_symbol_filename(db, moddef).as_deref().map(|f| url.join(f).ok()).flatten() + }) + .and_then(|url| url.join(link_target).ok()) + .map(|url| url.into_string()) +} + +/// Strip prefixes, suffixes, and inline code marks from the given string. +fn strip_prefixes_suffixes(mut s: &str) -> &str { + s = s.trim_matches('`'); + + [ + (TYPES.0.iter(), TYPES.1.iter()), + (VALUES.0.iter(), VALUES.1.iter()), + (MACROS.0.iter(), MACROS.1.iter()), + ] + .iter() + .for_each(|(prefixes, suffixes)| { + prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); + suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); + }); + let s = s.trim_start_matches("@").trim(); + s +} + +fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option { + krate + .get_doc_url(db) + .or_else(|| + // Fallback to docs.rs + // FIXME: Specify an exact version here. This may be difficult, as multiple versions of the same crate could exist. + Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?))) + .and_then(|s| Url::parse(&s).ok()) +} + +/// Get the filename and extension generated for a symbol by rustdoc. +/// +/// Example: `struct.Shard.html` +fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { + Some(match definition { + ModuleDef::Adt(adt) => match adt { + Adt::Struct(s) => format!("struct.{}.html", s.name(db)), + Adt::Enum(e) => format!("enum.{}.html", e.name(db)), + Adt::Union(u) => format!("union.{}.html", u.name(db)), + }, + ModuleDef::Module(_) => "index.html".to_string(), + ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), + ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), + ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), + ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), + ModuleDef::EnumVariant(ev) => { + format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) + } + ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), + ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), + }) +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +enum Namespace { + Types, + Values, + Macros, +} + +static TYPES: ([&str; 7], [&str; 0]) = + (["type", "struct", "enum", "mod", "trait", "union", "module"], []); +static VALUES: ([&str; 8], [&str; 1]) = + (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); +static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); + +impl Namespace { + /// Extract the specified namespace from an intra-doc-link if one exists. + /// + /// # Examples + /// + /// * `struct MyStruct` -> `Namespace::Types` + /// * `panic!` -> `Namespace::Macros` + /// * `fn@from_intra_spec` -> `Namespace::Values` + fn from_intra_spec(s: &str) -> Option { + [ + (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), + (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), + (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), + ] + .iter() + .filter(|(_ns, (prefixes, suffixes))| { + prefixes + .clone() + .map(|prefix| { + s.starts_with(*prefix) + && s.chars() + .nth(prefix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + || suffixes + .clone() + .map(|suffix| { + s.starts_with(*suffix) + && s.chars() + .nth(suffix.len() + 1) + .map(|c| c == '@' || c == ' ') + .unwrap_or(false) + }) + .any(|cond| cond) + }) + .map(|(ns, (_, _))| *ns) + .next() + } +} + +/// Sealed trait used solely for the generic bound on [`resolve_doc_link`]. +pub trait Resolvable { + fn resolver(&self, db: &D) -> Option; + fn try_into_module_def(self) -> Option; +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index ae2f1fd4d..d1f4d7813 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -27,7 +27,7 @@ pub mod diagnostics; mod from_id; mod code_model; -mod link_rewrite; +mod doc_links; mod has_source; @@ -38,8 +38,8 @@ pub use crate::{ Function, GenericDef, HasAttrs, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility, }, + doc_links::resolve_doc_link, has_source::HasSource, - link_rewrite::resolve_doc_link, semantics::{original_range, PathResolution, Semantics, SemanticsScope}, }; diff --git a/crates/hir/src/link_rewrite.rs b/crates/hir/src/link_rewrite.rs deleted file mode 100644 index dad3a39cf..000000000 --- a/crates/hir/src/link_rewrite.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! Resolves and rewrites links in markdown documentation for hovers/completion windows. - -use std::iter::once; - -use itertools::Itertools; -use url::Url; - -use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef}; -use hir_def::{db::DefDatabase, resolver::Resolver}; -use syntax::ast::Path; - -pub fn resolve_doc_link( - db: &D, - definition: &T, - link_text: &str, - link_target: &str, -) -> Option<(String, String)> { - try_resolve_intra(db, definition, link_text, &link_target).or_else(|| { - if let Some(definition) = definition.clone().try_into_module_def() { - try_resolve_path(db, &definition, &link_target) - .map(|target| (target, link_text.to_string())) - } else { - None - } - }) -} - -/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). -/// -/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). -fn try_resolve_intra( - db: &D, - definition: &T, - link_text: &str, - link_target: &str, -) -> Option<(String, String)> { - // Set link_target for implied shortlinks - let link_target = - if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; - - // Namespace disambiguation - let namespace = Namespace::from_intra_spec(link_target); - - // Strip prefixes/suffixes - let link_target = strip_prefixes_suffixes(link_target); - - // Parse link as a module path - let path = Path::parse(link_target).ok()?; - let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); - - // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) - let resolver = definition.resolver(db)?; - - let resolved = resolver.resolve_module_path_in_items(db, &modpath); - let (defid, namespace) = match namespace { - // FIXME: .or(resolved.macros) - None => resolved - .types - .map(|t| (t.0, Namespace::Types)) - .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, - Some(ns @ Namespace::Types) => (resolved.types?.0, ns), - Some(ns @ Namespace::Values) => (resolved.values?.0, ns), - // FIXME: - Some(Namespace::Macros) => None?, - }; - - // Get the filepath of the final symbol - let def: ModuleDef = defid.into(); - let module = def.module(db)?; - let krate = module.krate(); - let ns = match namespace { - Namespace::Types => ItemInNs::Types(defid), - Namespace::Values => ItemInNs::Values(defid), - // FIXME: - Namespace::Macros => None?, - }; - let import_map = db.import_map(krate.into()); - let path = import_map.path_of(ns)?; - - Some(( - get_doc_url(db, &krate)? - .join(&format!("{}/", krate.display_name(db)?)) - .ok()? - .join(&path.segments.iter().map(|name| name.to_string()).join("/")) - .ok()? - .join(&get_symbol_filename(db, &def)?) - .ok()? - .into_string(), - strip_prefixes_suffixes(link_text).to_string(), - )) -} - -/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). -fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str) -> Option { - if !link_target.contains("#") && !link_target.contains(".html") { - return None; - } - let ns = ItemInNs::Types(moddef.clone().into()); - - let module = moddef.module(db)?; - let krate = module.krate(); - let import_map = db.import_map(krate.into()); - let base = once(format!("{}", krate.display_name(db)?)) - .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name))) - .join("/"); - - get_doc_url(db, &krate) - .and_then(|url| url.join(&base).ok()) - .and_then(|url| { - get_symbol_filename(db, moddef).as_deref().map(|f| url.join(f).ok()).flatten() - }) - .and_then(|url| url.join(link_target).ok()) - .map(|url| url.into_string()) -} - -// Strip prefixes, suffixes, and inline code marks from the given string. -fn strip_prefixes_suffixes(mut s: &str) -> &str { - s = s.trim_matches('`'); - - [ - (TYPES.0.iter(), TYPES.1.iter()), - (VALUES.0.iter(), VALUES.1.iter()), - (MACROS.0.iter(), MACROS.1.iter()), - ] - .iter() - .for_each(|(prefixes, suffixes)| { - prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); - suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); - }); - let s = s.trim_start_matches("@").trim(); - s -} - -fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option { - krate - .get_doc_url(db) - .or_else(|| - // Fallback to docs.rs - // FIXME: Specify an exact version here. This may be difficult, as multiple versions of the same crate could exist. - Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?))) - .and_then(|s| Url::parse(&s).ok()) -} - -/// Get the filename and extension generated for a symbol by rustdoc. -/// -/// Example: `struct.Shard.html` -fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { - Some(match definition { - ModuleDef::Adt(adt) => match adt { - Adt::Struct(s) => format!("struct.{}.html", s.name(db)), - Adt::Enum(e) => format!("enum.{}.html", e.name(db)), - Adt::Union(u) => format!("union.{}.html", u.name(db)), - }, - ModuleDef::Module(_) => "index.html".to_string(), - ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), - ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), - ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), - ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), - ModuleDef::EnumVariant(ev) => { - format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) - } - ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), - ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), - }) -} - -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] -enum Namespace { - Types, - Values, - Macros, -} - -static TYPES: ([&str; 7], [&str; 0]) = - (["type", "struct", "enum", "mod", "trait", "union", "module"], []); -static VALUES: ([&str; 8], [&str; 1]) = - (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); -static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); - -impl Namespace { - /// Extract the specified namespace from an intra-doc-link if one exists. - /// - /// # Examples - /// - /// * `struct MyStruct` -> `Namespace::Types` - /// * `panic!` -> `Namespace::Macros` - /// * `fn@from_intra_spec` -> `Namespace::Values` - fn from_intra_spec(s: &str) -> Option { - [ - (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), - (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), - (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), - ] - .iter() - .filter(|(_ns, (prefixes, suffixes))| { - prefixes - .clone() - .map(|prefix| { - s.starts_with(*prefix) - && s.chars() - .nth(prefix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - || suffixes - .clone() - .map(|suffix| { - s.starts_with(*suffix) - && s.chars() - .nth(suffix.len() + 1) - .map(|c| c == '@' || c == ' ') - .unwrap_or(false) - }) - .any(|cond| cond) - }) - .map(|(ns, (_, _))| *ns) - .next() - } -} - -/// Sealed trait used solely for the generic bound on [`resolve_doc_link`]. -pub trait Resolvable { - fn resolver(&self, db: &D) -> Option; - fn try_into_module_def(self) -> Option; -} diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs index a826220e3..ff3200eef 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs @@ -1,4 +1,6 @@ -//! This is a wrapper around [`hir::link_rewrite`] connecting it to the markdown parser. +//! Resolves and rewrites links in markdown documentation. +//! +//! Most of the implementation can be found in [`hir::doc_links`]. use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; -- cgit v1.2.3 From 452afaebe188251cd4403e56999bf8b58de4fba9 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Tue, 25 Aug 2020 16:40:43 +1200 Subject: Changes from review --- crates/hir/src/code_model.rs | 18 ++++++++---------- crates/hir/src/doc_links.rs | 23 +++++++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) (limited to 'crates') diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index bd80102fa..94dd7f6f5 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -126,13 +126,16 @@ impl Crate { } /// Try to get the root URL of the documentation of a crate. - pub fn get_doc_url(self: &Crate, db: &dyn HirDatabase) -> Option { + pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDef::from(self.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().map(|tt| { + if !doc_attr_q.exists() { + return None; + } + + let doc_url = doc_attr_q.tt_values().map(|tt| { let name = tt.token_trees.iter() .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url")) .skip(2) @@ -142,14 +145,9 @@ impl Crate { Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), _ => None } - }).flat_map(|t| t).next().map(|s| s.to_string()) - } else { - None - }; + }).flat_map(|t| t).next(); - doc_url - .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") - .map(|s| s.to_string()) + doc_url.map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") } } diff --git a/crates/hir/src/doc_links.rs b/crates/hir/src/doc_links.rs index 45b26519e..dd2379bfc 100644 --- a/crates/hir/src/doc_links.rs +++ b/crates/hir/src/doc_links.rs @@ -38,21 +38,17 @@ fn try_resolve_intra( let link_target = if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; - // Namespace disambiguation - let namespace = Namespace::from_intra_spec(link_target); - - // Strip prefixes/suffixes - let link_target = strip_prefixes_suffixes(link_target); + let doclink = IntraDocLink::from(link_target); // Parse link as a module path - let path = Path::parse(link_target).ok()?; + let path = Path::parse(doclink.path).ok()?; let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap(); // Resolve it relative to symbol's location (according to the RFC this should consider small scopes) let resolver = definition.resolver(db)?; let resolved = resolver.resolve_module_path_in_items(db, &modpath); - let (defid, namespace) = match namespace { + let (defid, namespace) = match doclink.namespace { // FIXME: .or(resolved.macros) None => resolved .types @@ -133,7 +129,7 @@ fn strip_prefixes_suffixes(mut s: &str) -> &str { fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option { krate - .get_doc_url(db) + .get_html_root_url(db) .or_else(|| // Fallback to docs.rs // FIXME: Specify an exact version here. This may be difficult, as multiple versions of the same crate could exist. @@ -164,6 +160,17 @@ fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option { + path: &'s str, + namespace: Option, +} + +impl<'s> From<&'s str> for IntraDocLink<'s> { + fn from(s: &'s str) -> Self { + Self { path: strip_prefixes_suffixes(s), namespace: Namespace::from_intra_spec(s) } + } +} + #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] enum Namespace { Types, -- cgit v1.2.3