Don't strip affixes from path links
This commit is contained in:
@@ -17,7 +17,7 @@ use ra_ide_db::{
|
|||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use ra_syntax::{
|
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 ra_tt::{Ident, Leaf, Literal, TokenTree};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -394,35 +394,36 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
|||||||
// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
|
// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
|
||||||
fn map_links<'e>(
|
fn map_links<'e>(
|
||||||
events: impl Iterator<Item = Event<'e>>,
|
events: impl Iterator<Item = Event<'e>>,
|
||||||
callback: impl Fn(&str, &str) -> String,
|
callback: impl Fn(&str, &str) -> (String, String),
|
||||||
) -> impl Iterator<Item = Event<'e>> {
|
) -> impl Iterator<Item = Event<'e>> {
|
||||||
let mut in_link = false;
|
let mut in_link = false;
|
||||||
let mut link_text = CowStr::Borrowed("");
|
let mut link_target: Option<CowStr> = None;
|
||||||
|
|
||||||
events.map(move |evt| match evt {
|
events.map(move |evt| match evt {
|
||||||
Event::Start(Tag::Link(..)) => {
|
Event::Start(Tag::Link(_link_type, ref target, _)) => {
|
||||||
in_link = true;
|
in_link = true;
|
||||||
|
link_target = Some(target.clone());
|
||||||
evt
|
evt
|
||||||
}
|
}
|
||||||
Event::End(Tag::Link(link_type, target, _)) => {
|
Event::End(Tag::Link(link_type, _target, _)) => {
|
||||||
in_link = false;
|
in_link = false;
|
||||||
let target = callback(&target, &link_text);
|
Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
|
||||||
Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed("")))
|
|
||||||
}
|
}
|
||||||
Event::Text(s) if in_link => {
|
Event::Text(s) if in_link => {
|
||||||
link_text = s.clone();
|
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
|
||||||
// TODO: This can unintentionally strip words from path-based links.
|
link_target = Some(CowStr::Boxed(link_target_s.into()));
|
||||||
// See std::box::Box -> std::box link as an example.
|
Event::Text(CowStr::Boxed(link_name.into()))
|
||||||
Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into()))
|
|
||||||
}
|
}
|
||||||
Event::Code(s) if in_link => {
|
Event::Code(s) if in_link => {
|
||||||
link_text = s.clone();
|
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
|
||||||
Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into()))
|
link_target = Some(CowStr::Boxed(link_target_s.into()));
|
||||||
|
Event::Code(CowStr::Boxed(link_name.into()))
|
||||||
}
|
}
|
||||||
_ => evt,
|
_ => 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<String> {
|
fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> {
|
||||||
let doc = Parser::new_with_broken_link_callback(
|
let doc = Parser::new_with_broken_link_callback(
|
||||||
markdown,
|
markdown,
|
||||||
@@ -431,21 +432,22 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) ->
|
|||||||
);
|
);
|
||||||
|
|
||||||
let doc = map_links(doc, |target, title: &str| {
|
let doc = map_links(doc, |target, title: &str| {
|
||||||
match Url::parse(target) {
|
// This check is imperfect, there's some overlap between valid intra-doc links
|
||||||
// If this is a valid absolute URL don't touch it
|
// and valid URLs so we choose to be too eager to try to resolve what might be
|
||||||
Ok(_) => target.to_string(),
|
// a URL.
|
||||||
// Otherwise there are two main possibilities
|
if target.contains("://") {
|
||||||
// path-based links: `../../module/struct.MyStruct.html`
|
(target.to_string(), title.to_string())
|
||||||
// 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 {
|
} else {
|
||||||
target.to_string()
|
// 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,
|
definition: &Definition,
|
||||||
link_text: &str,
|
link_text: &str,
|
||||||
link_target: &str,
|
link_target: &str,
|
||||||
) -> Option<String> {
|
) -> Option<(String, String)> {
|
||||||
// Set link_target for implied shortlinks
|
// Set link_target for implied shortlinks
|
||||||
let link_target =
|
let link_target =
|
||||||
if link_target.is_empty() { link_text.trim_matches('`') } else { 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
|
// Parse link as a module path
|
||||||
// This expects a full document, which a single path isn't, but we can just ignore the errors.
|
// 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 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 = parsed.descendants().filter_map(Path::cast).next()?;
|
||||||
let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
|
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 import_map = db.import_map(krate.into());
|
||||||
let path = import_map.path_of(ns)?;
|
let path = import_map.path_of(ns)?;
|
||||||
|
|
||||||
Some(
|
Some((
|
||||||
get_doc_url(db, &krate)?
|
get_doc_url(db, &krate)?
|
||||||
.join(&format!("{}/", krate.display_name(db)?))
|
.join(&format!("{}/", krate.display_name(db)?))
|
||||||
.ok()?
|
.ok()?
|
||||||
@@ -575,7 +578,7 @@ fn try_resolve_intra(
|
|||||||
.join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
|
.join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
|
||||||
.ok()?
|
.ok()?
|
||||||
.into_string(),
|
.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`).
|
/// 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]
|
#[test]
|
||||||
fn test_hover_intra_link() {
|
fn test_hover_path_link_no_strip() {
|
||||||
check_hover_result(
|
check_hover_result(
|
||||||
r"
|
r"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
pub struct Foo;
|
pub struct Foo;
|
||||||
/// [Foo](Foo)
|
/// [struct Foo](struct.Foo.html)
|
||||||
pub struct B<|>ar
|
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)"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user