From 5f4351fbb6cf07582974e37845f4d30b81399b0a Mon Sep 17 00:00:00 2001 From: rainy-me Date: Thu, 5 May 2022 00:41:29 +0900 Subject: [PATCH 1/2] fix: doc url link type --- crates/hir-def/src/attr.rs | 17 +++++++++++++ crates/hir/src/lib.rs | 19 +-------------- crates/ide/src/doc_links.rs | 45 +++++++++++++++++++++++------------ crates/ide/src/hover/tests.rs | 34 ++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 11d3f48b94..f92de2d42d 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -853,6 +853,23 @@ impl<'attr> AttrQuery<'attr> { .iter() .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key)) } + + pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> { + if !self.exists() { + return None; + } + + self.tt_values().find_map(|tt| { + let name = tt.token_trees.iter() + .skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key)) + .nth(2); + + match name { + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ref text, ..}))) => Some(text), + _ => None + } + }) + } } fn attrs_from_item_tree(id: ItemTreeId, db: &dyn DefDatabase) -> RawAttrs { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2d73168727..54a04f0ba2 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -75,7 +75,6 @@ use syntax::{ ast::{self, HasAttrs as _, HasDocComments, HasName}, AstNode, AstPtr, SmolStr, SyntaxNodePtr, T, }; -use tt::{Ident, Leaf, Literal, TokenTree}; use crate::db::{DefDatabase, HirDatabase}; @@ -230,23 +229,7 @@ impl Crate { pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option { // Look for #![doc(html_root_url = "...")] let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into())); - let doc_attr_q = attrs.by_key("doc"); - - if !doc_attr_q.exists() { - return None; - } - - let doc_url = doc_attr_q.tt_values().filter_map(|tt| { - let name = tt.token_trees.iter() - .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident { text, ..} )) if text == "html_root_url")) - .nth(2); - - match name { - Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text), - _ => None - } - }).next(); - + let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url"); doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/") } diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index a1e170634b..2b788fb6cf 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -45,19 +45,19 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin // 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()) + (Some(LinkType::Inline), target.to_string(), title.to_string()) } else { // Two possibilities: // * path-based links: `../../module/struct.MyStruct.html` // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` - if let Some(rewritten) = rewrite_intra_doc_link(db, definition, target, title) { - return rewritten; + if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title) { + return (None, target, title); } if let Some(target) = rewrite_url_link(db, definition, target) { - return (target, title.to_string()); + return (Some(LinkType::Inline), target, title.to_string()); } - (target.to_string(), title.to_string()) + (None, target.to_string(), title.to_string()) } }); let mut out = String::new(); @@ -368,33 +368,42 @@ fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option { /// Rewrites a markdown document, applying 'callback' to each link. fn map_links<'e>( events: impl Iterator>, - callback: impl Fn(&str, &str) -> (String, String), + callback: impl Fn(&str, &str) -> (Option, String, String), ) -> impl Iterator> { let mut in_link = false; - let mut link_target: Option = None; + // holds the origin link target on start event and the rewritten one on end event + let mut end_link_target: Option = None; + // normally link's type is determined by the type of link tag in the end event, + // however in same cases we want to change the link type. + // For example, Shortcut type doesn't make sense for url links + let mut end_link_type: Option = None; events.map(move |evt| match evt { Event::Start(Tag::Link(_, ref target, _)) => { in_link = true; - link_target = Some(target.clone()); + end_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_or(target), + end_link_type.unwrap_or(link_type), + end_link_target.take().unwrap_or(target), 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())); + let (link_type, link_target_s, link_name) = + callback(&end_link_target.take().unwrap(), &s); + end_link_target = Some(CowStr::Boxed(link_target_s.into())); + end_link_type = link_type; 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())); + let (link_type, link_target_s, link_name) = + callback(&end_link_target.take().unwrap(), &s); + end_link_target = Some(CowStr::Boxed(link_target_s.into())); + end_link_type = link_type; Event::Code(CowStr::Boxed(link_name.into())) } _ => evt, @@ -468,7 +477,13 @@ fn filename_and_frag_for_def( Adt::Union(u) => format!("union.{}.html", u.name(db)), }, Definition::Module(m) => match m.name(db) { - Some(name) => format!("{}/index.html", name), + // `#[doc(keyword = "...")]` is internal used only by rust compiler + Some(name) => match m.attrs(db).by_key("doc").find_string_value_in_tt("keyword") { + Some(kw) => { + format!("keyword.{}.html", kw.trim_matches('"')) + } + None => format!("{}/index.html", name), + }, None => String::from("index.html"), }, Definition::Trait(t) => format!("trait.{}.html", t.name(db)), diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index d61b5af13a..95420f2ffe 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -3641,6 +3641,40 @@ mod return_keyword {} ); } +#[test] +fn hover_keyword_doc() { + check( + r#" +//- /main.rs crate:main deps:std +fn foo() { + let bar = mov$0e || {}; +} +//- /libstd.rs crate:std +#[doc(keyword = "move")] +/// [closure] +/// [closures][closure] +/// [threads] +/// +/// [closure]: ../book/ch13-01-closures.html +/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads +mod move_keyword {} +"#, + expect![[r##" + *move* + + ```rust + move + ``` + + --- + + [closure](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html) + [closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html) + [threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads) + "##]], + ); +} + #[test] fn hover_keyword_as_primitive() { check( From ddff1b22f957e98aa98acff87ce86c04c72db308 Mon Sep 17 00:00:00 2001 From: rainy-me Date: Thu, 5 May 2022 13:41:33 +0900 Subject: [PATCH 2/2] fix: add docs and remove unnecessary check --- crates/hir-def/src/attr.rs | 10 ++++++---- crates/ide/src/doc_links.rs | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index f92de2d42d..fd44493076 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -854,11 +854,13 @@ impl<'attr> AttrQuery<'attr> { .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_smol_str() == key)) } + /// Find string value for a specific key inside token tree + /// + /// ```ignore + /// #[doc(html_root_url = "url")] + /// ^^^^^^^^^^^^^ key + /// ``` pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> { - if !self.exists() { - return None; - } - self.tt_values().find_map(|tt| { let name = tt.token_trees.iter() .skip_while(|tt| !matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text, ..} )) if text == key)) diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 2b788fb6cf..9ad11b7a65 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -374,8 +374,8 @@ fn map_links<'e>( // holds the origin link target on start event and the rewritten one on end event let mut end_link_target: Option = None; // normally link's type is determined by the type of link tag in the end event, - // however in same cases we want to change the link type. - // For example, Shortcut type doesn't make sense for url links + // however in same cases we want to change the link type, for example, + // `Shortcut` type doesn't make sense for url links let mut end_link_type: Option = None; events.map(move |evt| match evt {