Skip to content

Commit 8804e7f

Browse files
authored
Rollup merge of #108025 - notriddle:notriddle/intra-doc-link-tooltips, r=GuillaumeGomez
rustdoc: add more tooltips to intra-doc links This commit makes intra-doc link tooltips consistent with generated links in function signatures and item tables, with the format `itemtype foo::bar::baz`. This way, you can tell if a link points at a trait or a type (for example) by mousing over it. See also #39697 Partially solves https://internals.rust-lang.org/t/rustdoc-suggestion-highlight-links-fn-s-mod-s-type-s-etc-appropriately-within-and-documentation/17931 (though the Internals thread asks for color-coding, while this PR adds a tooltip instead, it's accomplishing the same thing). Before: <img width="950" alt="image" src="https://user-images.githubusercontent.com/1593513/218653059-911cea01-7231-438a-ad98-be98ab73783f.png"> After: <img width="432" alt="image" src="https://user-images.githubusercontent.com/1593513/218653201-34ca3aa7-18f1-4cb1-be68-a1411bbe797e.png">
2 parents 43b42c5 + ba4b026 commit 8804e7f

File tree

4 files changed

+52
-9
lines changed

4 files changed

+52
-9
lines changed

src/librustdoc/clean/types.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -482,23 +482,24 @@ impl Item {
482482
}
483483

484484
pub(crate) fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> {
485-
use crate::html::format::href;
485+
use crate::html::format::{href, link_tooltip};
486486

487487
cx.cache()
488488
.intra_doc_links
489489
.get(&self.item_id)
490490
.map_or(&[][..], |v| v.as_slice())
491491
.iter()
492-
.filter_map(|ItemLink { link: s, link_text, page_id: did, ref fragment }| {
493-
debug!(?did);
494-
if let Ok((mut href, ..)) = href(*did, cx) {
492+
.filter_map(|ItemLink { link: s, link_text, page_id: id, ref fragment }| {
493+
debug!(?id);
494+
if let Ok((mut href, ..)) = href(*id, cx) {
495495
debug!(?href);
496496
if let Some(ref fragment) = *fragment {
497497
fragment.render(&mut href, cx.tcx())
498498
}
499499
Some(RenderedLink {
500500
original_text: s.clone(),
501501
new_text: link_text.clone(),
502+
tooltip: link_tooltip(*id, fragment, cx),
502503
href,
503504
})
504505
} else {
@@ -523,6 +524,7 @@ impl Item {
523524
original_text: s.clone(),
524525
new_text: link_text.clone(),
525526
href: String::new(),
527+
tooltip: String::new(),
526528
})
527529
.collect()
528530
}
@@ -1040,6 +1042,8 @@ pub struct RenderedLink {
10401042
pub(crate) new_text: String,
10411043
/// The URL to put in the `href`
10421044
pub(crate) href: String,
1045+
/// The tooltip.
1046+
pub(crate) tooltip: String,
10431047
}
10441048

10451049
/// The attributes on an [`Item`], including attributes like `#[derive(...)]` and `#[inline]`,

src/librustdoc/html/format.rs

+16
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::clean::{
3434
use crate::formats::item_type::ItemType;
3535
use crate::html::escape::Escape;
3636
use crate::html::render::Context;
37+
use crate::passes::collect_intra_doc_links::UrlFragment;
3738

3839
use super::url_parts_builder::estimate_item_path_byte_length;
3940
use super::url_parts_builder::UrlPartsBuilder;
@@ -768,6 +769,21 @@ pub(crate) fn href_relative_parts<'fqp>(
768769
}
769770
}
770771

772+
pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Context<'_>) -> String {
773+
let cache = cx.cache();
774+
let Some((fqp, shortty)) = cache.paths.get(&did)
775+
.or_else(|| cache.external_paths.get(&did))
776+
else { return String::new() };
777+
let fqp = fqp.iter().map(|sym| sym.as_str()).join("::");
778+
if let &Some(UrlFragment::Item(id)) = fragment {
779+
let name = cx.tcx().item_name(id);
780+
let descr = cx.tcx().def_kind(id).descr(id);
781+
format!("{descr} {fqp}::{name}")
782+
} else {
783+
format!("{shortty} {fqp}")
784+
}
785+
}
786+
771787
/// Used to render a [`clean::Path`].
772788
fn resolved_path<'cx>(
773789
w: &mut fmt::Formatter<'_>,

src/librustdoc/html/markdown.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
360360
trace!("it matched");
361361
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
362362
self.shortcut_link = Some(link);
363+
if title.is_empty() && !link.tooltip.is_empty() {
364+
*title = CowStr::Borrowed(link.tooltip.as_ref());
365+
}
363366
}
364367
}
365368
// Now that we're done with the shortcut link, don't replace any more text.
@@ -410,9 +413,12 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
410413
}
411414
// If this is a link, but not a shortcut link,
412415
// replace the URL, since the broken_link_callback was not called.
413-
Some(Event::Start(Tag::Link(_, dest, _))) => {
416+
Some(Event::Start(Tag::Link(_, dest, title))) => {
414417
if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) {
415418
*dest = CowStr::Borrowed(link.href.as_ref());
419+
if title.is_empty() && !link.tooltip.is_empty() {
420+
*title = CowStr::Borrowed(link.tooltip.as_ref());
421+
}
416422
}
417423
}
418424
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
@@ -976,7 +982,7 @@ impl Markdown<'_> {
976982
links
977983
.iter()
978984
.find(|link| link.original_text.as_str() == &*broken_link.reference)
979-
.map(|link| (link.href.as_str().into(), link.new_text.as_str().into()))
985+
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
980986
};
981987

982988
let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer));
@@ -1059,7 +1065,7 @@ impl MarkdownSummaryLine<'_> {
10591065
links
10601066
.iter()
10611067
.find(|link| link.original_text.as_str() == &*broken_link.reference)
1062-
.map(|link| (link.href.as_str().into(), link.new_text.as_str().into()))
1068+
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
10631069
};
10641070

10651071
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer))
@@ -1106,7 +1112,7 @@ fn markdown_summary_with_limit(
11061112
link_names
11071113
.iter()
11081114
.find(|link| link.original_text.as_str() == &*broken_link.reference)
1109-
.map(|link| (link.href.as_str().into(), link.new_text.as_str().into()))
1115+
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
11101116
};
11111117

11121118
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
@@ -1187,7 +1193,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
11871193
link_names
11881194
.iter()
11891195
.find(|link| link.original_text.as_str() == &*broken_link.reference)
1190-
.map(|link| (link.href.as_str().into(), link.new_text.as_str().into()))
1196+
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
11911197
};
11921198

11931199
let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));

tests/rustdoc/intra-doc/basic.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
// @has basic/index.html
22
// @has - '//a/@href' 'struct.ThisType.html'
3+
// @has - '//a/@title' 'struct basic::ThisType'
34
// @has - '//a/@href' 'struct.ThisType.html#method.this_method'
5+
// @has - '//a/@title' 'associated function basic::ThisType::this_method'
46
// @has - '//a/@href' 'enum.ThisEnum.html'
7+
// @has - '//a/@title' 'enum basic::ThisEnum'
58
// @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant'
9+
// @has - '//a/@title' 'variant basic::ThisEnum::ThisVariant'
610
// @has - '//a/@href' 'trait.ThisTrait.html'
11+
// @has - '//a/@title' 'trait basic::ThisTrait'
712
// @has - '//a/@href' 'trait.ThisTrait.html#tymethod.this_associated_method'
13+
// @has - '//a/@title' 'associated function basic::ThisTrait::this_associated_method'
814
// @has - '//a/@href' 'trait.ThisTrait.html#associatedtype.ThisAssociatedType'
15+
// @has - '//a/@title' 'associated type basic::ThisTrait::ThisAssociatedType'
916
// @has - '//a/@href' 'trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST'
17+
// @has - '//a/@title' 'associated constant basic::ThisTrait::THIS_ASSOCIATED_CONST'
1018
// @has - '//a/@href' 'trait.ThisTrait.html'
19+
// @has - '//a/@title' 'trait basic::ThisTrait'
1120
// @has - '//a/@href' 'type.ThisAlias.html'
21+
// @has - '//a/@title' 'type basic::ThisAlias'
1222
// @has - '//a/@href' 'union.ThisUnion.html'
23+
// @has - '//a/@title' 'union basic::ThisUnion'
1324
// @has - '//a/@href' 'fn.this_function.html'
25+
// @has - '//a/@title' 'fn basic::this_function'
1426
// @has - '//a/@href' 'constant.THIS_CONST.html'
27+
// @has - '//a/@title' 'constant basic::THIS_CONST'
1528
// @has - '//a/@href' 'static.THIS_STATIC.html'
29+
// @has - '//a/@title' 'static basic::THIS_STATIC'
1630
// @has - '//a/@href' 'macro.this_macro.html'
31+
// @has - '//a/@title' 'macro basic::this_macro'
1732
// @has - '//a/@href' 'trait.SoAmbiguous.html'
33+
// @has - '//a/@title' 'trait basic::SoAmbiguous'
1834
// @has - '//a/@href' 'fn.SoAmbiguous.html'
35+
// @has - '//a/@title' 'fn basic::SoAmbiguous'
1936
//! In this crate we would like to link to:
2037
//!
2138
//! * [`ThisType`](ThisType)

0 commit comments

Comments
 (0)