Skip to content

Commit 48d858f

Browse files
committed
Don't strip affixes from path links
1 parent 1833036 commit 48d858f

File tree

1 file changed

+53
-35
lines changed

1 file changed

+53
-35
lines changed

crates/ra_ide/src/hover.rs

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use ra_ide_db::{
1717
RootDatabase,
1818
};
1919
use ra_syntax::{
20-
ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset,
20+
ast, ast::{Path, MacroCall}, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset,
2121
};
2222
use ra_tt::{Ident, Leaf, Literal, TokenTree};
2323
use url::Url;
@@ -394,35 +394,36 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
394394
// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
395395
fn map_links<'e>(
396396
events: impl Iterator<Item = Event<'e>>,
397-
callback: impl Fn(&str, &str) -> String,
397+
callback: impl Fn(&str, &str) -> (String, String),
398398
) -> impl Iterator<Item = Event<'e>> {
399399
let mut in_link = false;
400-
let mut link_text = CowStr::Borrowed("");
400+
let mut link_target: Option<CowStr> = None;
401+
401402
events.map(move |evt| match evt {
402-
Event::Start(Tag::Link(..)) => {
403+
Event::Start(Tag::Link(_link_type, ref target, _)) => {
403404
in_link = true;
405+
link_target = Some(target.clone());
404406
evt
405407
}
406-
Event::End(Tag::Link(link_type, target, _)) => {
408+
Event::End(Tag::Link(link_type, _target, _)) => {
407409
in_link = false;
408-
let target = callback(&target, &link_text);
409-
Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed("")))
410+
Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
410411
}
411412
Event::Text(s) if in_link => {
412-
link_text = s.clone();
413-
// TODO: This can unintentionally strip words from path-based links.
414-
// See std::box::Box -> std::box link as an example.
415-
Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into()))
413+
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
414+
link_target = Some(CowStr::Boxed(link_target_s.into()));
415+
Event::Text(CowStr::Boxed(link_name.into()))
416416
}
417417
Event::Code(s) if in_link => {
418-
link_text = s.clone();
419-
Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into()))
418+
let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
419+
link_target = Some(CowStr::Boxed(link_target_s.into()));
420+
Event::Code(CowStr::Boxed(link_name.into()))
420421
}
421422
_ => evt,
422423
})
423424
}
424425

425-
/// Rewrite documentation links in markdown to point to local documentation/docs.rs
426+
/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
426427
fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> {
427428
let doc = Parser::new_with_broken_link_callback(
428429
markdown,
@@ -431,21 +432,22 @@ fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) ->
431432
);
432433

433434
let doc = map_links(doc, |target, title: &str| {
434-
match Url::parse(target) {
435-
// If this is a valid absolute URL don't touch it
436-
Ok(_) => target.to_string(),
437-
// Otherwise there are two main possibilities
438-
// path-based links: `../../module/struct.MyStruct.html`
439-
// module-based links (AKA intra-doc links): `super::super::module::MyStruct`
440-
Err(_) => {
441-
let resolved = try_resolve_intra(db, definition, title, &target)
442-
.or_else(|| try_resolve_path(db, definition, &target));
443-
444-
if let Some(resolved) = resolved {
445-
resolved
446-
} else {
447-
target.to_string()
448-
}
435+
// This check is imperfect, there's some overlap between valid intra-doc links
436+
// and valid URLs so we choose to be too eager to try to resolve what might be
437+
// a URL.
438+
if target.contains("://") {
439+
(target.to_string(), title.to_string())
440+
} else {
441+
// Two posibilities:
442+
// * path-based links: `../../module/struct.MyStruct.html`
443+
// * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
444+
let resolved = try_resolve_intra(db, definition, title, &target)
445+
.or_else(|| try_resolve_path(db, definition, &target).map(|target| (target, title.to_string())));
446+
447+
if let Some((target, title)) = resolved {
448+
(target, title)
449+
} else {
450+
(target.to_string(), title.to_string())
449451
}
450452
}
451453
});
@@ -520,7 +522,7 @@ fn try_resolve_intra(
520522
definition: &Definition,
521523
link_text: &str,
522524
link_target: &str,
523-
) -> Option<String> {
525+
) -> Option<(String, String)> {
524526
// Set link_target for implied shortlinks
525527
let link_target =
526528
if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };
@@ -534,6 +536,7 @@ fn try_resolve_intra(
534536
// Parse link as a module path
535537
// This expects a full document, which a single path isn't, but we can just ignore the errors.
536538
let parsed = SyntaxNode::new_root(ra_syntax::parse_text(link_target).0);
539+
// TODO: Proper parsing
537540
let path = parsed.descendants().filter_map(Path::cast).next()?;
538541
let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
539542

@@ -566,7 +569,7 @@ fn try_resolve_intra(
566569
let import_map = db.import_map(krate.into());
567570
let path = import_map.path_of(ns)?;
568571

569-
Some(
572+
Some((
570573
get_doc_url(db, &krate)?
571574
.join(&format!("{}/", krate.display_name(db)?))
572575
.ok()?
@@ -575,7 +578,7 @@ fn try_resolve_intra(
575578
.join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
576579
.ok()?
577580
.into_string(),
578-
)
581+
strip_prefixes_suffixes(link_text).to_string()))
579582
}
580583

581584
/// 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; }; }
14851488
}
14861489

14871490
#[test]
1488-
fn test_hover_intra_link() {
1491+
fn test_hover_path_link_no_strip() {
14891492
check_hover_result(
14901493
r"
14911494
//- /lib.rs
14921495
pub struct Foo;
1493-
/// [Foo](Foo)
1496+
/// [struct Foo](struct.Foo.html)
14941497
pub struct B<|>ar
14951498
",
1496-
&["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1499+
&["pub struct Bar\n```\n___\n\n[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)"],
1500+
);
1501+
}
1502+
1503+
#[test]
1504+
fn test_hover_intra_link() {
1505+
check_hover_result(
1506+
r"
1507+
//- /lib.rs
1508+
pub mod foo {
1509+
pub struct Foo;
1510+
}
1511+
/// [Foo](foo::Foo)
1512+
pub struct B<|>ar
1513+
",
1514+
&["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)"],
14971515
);
14981516
}
14991517

0 commit comments

Comments
 (0)