mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Auto merge of #12150 - rainy-me:feat/fix-doc-url-links, r=rainy-me
fix: doc url link type
fix: #12033
I did some debugging and found the cause looks like to be some doc links' `LinkType` are kept as `Shortcut` which don't make sense for url links.
This PR should resolve both problems in the origin issue, but aside this PR, more work are needed for doc_links.
about `LinkType`: f29bd1e228/src/lib.rs (L191-L210)
This commit is contained in:
commit
0218aeba7a
4 changed files with 84 additions and 33 deletions
|
@ -853,6 +853,25 @@ impl<'attr> AttrQuery<'attr> {
|
|||
.iter()
|
||||
.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> {
|
||||
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<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
|
||||
|
|
|
@ -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<String> {
|
||||
// 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() + "/")
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> {
|
|||
/// Rewrites a markdown document, applying 'callback' to each link.
|
||||
fn map_links<'e>(
|
||||
events: impl Iterator<Item = Event<'e>>,
|
||||
callback: impl Fn(&str, &str) -> (String, String),
|
||||
callback: impl Fn(&str, &str) -> (Option<LinkType>, String, String),
|
||||
) -> impl Iterator<Item = Event<'e>> {
|
||||
let mut in_link = false;
|
||||
let mut link_target: Option<CowStr> = None;
|
||||
// holds the origin link target on start event and the rewritten one on end event
|
||||
let mut end_link_target: Option<CowStr> = 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<LinkType> = 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)),
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue