diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 7e835585b73e8..a59738ce42a66 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -589,6 +589,109 @@ impl<'a, I: Iterator- >> Iterator for HeadingLinks<'a, '_,
}
}
+/// Type used to merge `
` tags. It also takes into accounts links if the link only contains
+/// `` tag(s).
+///
+/// So if you have:
+///
+/// `<`[`Stream`](crate::Foo)`>`
+///
+/// Instead of getting:
+///
+/// ```html
+/// <
Stream
>
+/// ```
+///
+/// You will get:
+///
+/// ```html
+/// <Stream>
+/// ```
+struct CodeMerger<'a, I> {
+ inner: I,
+ queue: VecDeque>,
+}
+
+impl<'a, I> CodeMerger<'a, I> {
+ fn new(iter: I) -> Self {
+ Self { inner: iter, queue: VecDeque::new() }
+ }
+
+ fn merge_codes(&mut self, mut codes: VecDeque>) {
+ if codes.len() < 2 {
+ for item in codes.drain(..) {
+ self.queue.push_back(item);
+ }
+ return;
+ }
+ self.queue.push_back((Event::InlineHtml(CowStr::Borrowed("")), 0..0));
+ for item in codes.drain(..) {
+ self.queue.push_back(match item {
+ (Event::Code(code), range) => (Event::Text(code), range),
+ _ => item,
+ });
+ }
+ self.queue.push_back((Event::InlineHtml(CowStr::Borrowed("
")), 0..0));
+ }
+}
+
+impl<'a, I: Iterator- >> Iterator for CodeMerger<'a, I> {
+ type Item = SpannedEvent<'a>;
+
+ fn next(&mut self) -> Option {
+ if let Some(item) = self.queue.pop_front() {
+ return Some(item);
+ }
+
+ let mut buffed_link_items = VecDeque::new();
+ let mut codes = VecDeque::new();
+ let mut is_inside_link = false;
+ let mut extra = None;
+
+ while let Some(event) = self.inner.next() {
+ match event.0 {
+ Event::Code(_) => {
+ if is_inside_link {
+ buffed_link_items.push_back(event);
+ } else {
+ codes.push_back(event);
+ }
+ }
+ Event::Start(Tag::Link { .. }) => {
+ buffed_link_items.push_back(event);
+ is_inside_link = true;
+ }
+ Event::End(TagEnd::Link { .. }) if is_inside_link => {
+ // Seems like it's all good!
+ for item in buffed_link_items.drain(..) {
+ codes.push_back(item);
+ }
+ codes.push_back(event);
+ is_inside_link = false;
+ }
+ _ => {
+ extra = Some(event);
+ break;
+ }
+ }
+ }
+ // First we merge the code items if any.
+ self.merge_codes(codes);
+ // The link doesn't only contain codes so we first merge the codes into 1,
+ // then we put the link elements into the queue as well.
+ if is_inside_link {
+ for item in buffed_link_items.drain(..) {
+ self.queue.push_back(item);
+ }
+ }
+ if let Some(item) = extra {
+ self.queue.push_back(item);
+ }
+
+ self.queue.pop_front()
+ }
+}
+
/// Extracts just the first paragraph.
struct SummaryLine<'a, I: Iterator
- >> {
inner: I,
@@ -1374,6 +1477,7 @@ impl<'a> Markdown<'a> {
ids.handle_footnotes(|ids, existing_footnotes| {
let p = HeadingLinks::new(p, None, ids, heading_offset);
let p = SpannedLinkReplacer::new(p, links);
+ let p = CodeMerger::new(p);
let p = footnotes::Footnotes::new(p, existing_footnotes);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
CodeBlocks::new(p, codes, edition, playground)
@@ -1455,6 +1559,7 @@ impl MarkdownWithToc<'_> {
ids.handle_footnotes(|ids, existing_footnotes| {
let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1);
+ let p = CodeMerger::new(p);
let p = footnotes::Footnotes::new(p, existing_footnotes);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = CodeBlocks::new(p, codes, edition, playground);
@@ -1489,6 +1594,7 @@ impl MarkdownItemInfo<'_> {
ids.handle_footnotes(|ids, existing_footnotes| {
let p = HeadingLinks::new(p, None, ids, HeadingOffset::H1);
+ let p = CodeMerger::new(p);
let p = footnotes::Footnotes::new(p, existing_footnotes);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = p.filter(|event| {
@@ -1516,9 +1622,9 @@ impl MarkdownSummaryLine<'_> {
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
};
- let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer))
- .peekable();
- let mut summary = SummaryLine::new(p);
+ let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
+ let p = CodeMerger::new(p.map(|ev| (ev, 0..0)));
+ let mut summary = SummaryLine::new(p.map(|(ev, _)| ev).peekable());
let mut s = String::new();
diff --git a/tests/rustdoc-gui/docblock-big-code-mobile.goml b/tests/rustdoc-gui/docblock-big-code-mobile.goml
index 71e08e2c7e260..abaa00486637b 100644
--- a/tests/rustdoc-gui/docblock-big-code-mobile.goml
+++ b/tests/rustdoc-gui/docblock-big-code-mobile.goml
@@ -12,4 +12,5 @@ assert-size: (".docblock p > code", {"height": 48})
// Same check, but where the long code block is also a link
go-to: "file://" + |DOC_PATH| + "/test_docs/long_code_block_link/index.html"
-assert-size: (".docblock p > a > code", {"height": 48})
+assert-size: (".docblock p > code", {"height": 48})
+assert-size: (".docblock p > code > a", {"height": 44})
diff --git a/tests/rustdoc/code-tags-merge.rs b/tests/rustdoc/code-tags-merge.rs
new file mode 100644
index 0000000000000..6c6a6daea2fff
--- /dev/null
+++ b/tests/rustdoc/code-tags-merge.rs
@@ -0,0 +1,28 @@
+// This test checks that rustdoc is combining `
` tags as expected.
+
+#![crate_name = "foo"]
+
+//@ has 'foo/index.html'
+
+// First we check that summary lines also get the `` merge.
+//@ has - '//dd/code' 'Stream- >'
+//@ !has - '//dd/code/code' 'Stream
- >'
+// Then we check that the docblocks have it too.
+//@ has 'foo/struct.Foo.html'
+//@ has - '//*[@class="docblock"]//code' 'Stream
- >'
+//@ has - '//*[@class="docblock"]//code' 'Stream
- >'
+/// [`Stream`](crate::Foo)`
- >`
+pub struct Foo;
+
+impl Foo {
+ //@ has - '//*[@class="impl-items"]//*[@class="docblock"]//code' ''
+ /// A `<`[`Stream`](crate::Foo)`>` stuff.
+ pub fn bar() {}
+
+ //@ has - '//*[@class="impl-items"]//*[@class="docblock"]//code' '<'
+ //@ has - '//*[@class="impl-items"]//*[@class="docblock"]//a' 'Stream a'
+ //@ has - '//*[@class="impl-items"]//*[@class="docblock"]//code' 'Stream'
+ //@ has - '//*[@class="impl-items"]//*[@class="docblock"]//code' '>'
+ /// A `<`[`Stream` a](crate::Foo)`>` stuff.
+ pub fn foo() {}
+}
diff --git a/tests/rustdoc/intra-doc/disambiguators-removed.rs b/tests/rustdoc/intra-doc/disambiguators-removed.rs
index 613156222ed0e..1b3290d1c3768 100644
--- a/tests/rustdoc/intra-doc/disambiguators-removed.rs
+++ b/tests/rustdoc/intra-doc/disambiguators-removed.rs
@@ -2,15 +2,15 @@
// first try backticks
/// Trait: [`trait@Name`], fn: [`fn@Name`], [`Name`][`macro@Name`]
//@ has disambiguators_removed/struct.AtDisambiguator.html
-//@ has - '//a[@href="trait.Name.html"][code]' "Name"
-//@ has - '//a[@href="fn.Name.html"][code]' "Name"
-//@ has - '//a[@href="macro.Name.html"][code]' "Name"
+//@ has - '//code/a[@href="trait.Name.html"]' "Name"
+//@ has - '//code/a[@href="fn.Name.html"]' "Name"
+//@ has - '//code/a[@href="macro.Name.html"]' "Name"
pub struct AtDisambiguator;
/// fn: [`Name()`], macro: [`Name!`]
//@ has disambiguators_removed/struct.SymbolDisambiguator.html
-//@ has - '//a[@href="fn.Name.html"][code]' "Name()"
-//@ has - '//a[@href="macro.Name.html"][code]' "Name!"
+//@ has - '//code/a[@href="fn.Name.html"]' "Name()"
+//@ has - '//code/a[@href="macro.Name.html"]' "Name!"
pub struct SymbolDisambiguator;
// Now make sure that backticks aren't added if they weren't already there
diff --git a/tests/rustdoc/primitive-link.rs b/tests/rustdoc/primitive-link.rs
index 3fe9cdc3ca702..6d620e63199a1 100644
--- a/tests/rustdoc/primitive-link.rs
+++ b/tests/rustdoc/primitive-link.rs
@@ -1,12 +1,11 @@
#![crate_name = "foo"]
-
-//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/a[@href="{{channel}}/std/primitive.u32.html"]' 'u32'
+//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/code/a[@href="{{channel}}/std/primitive.u32.html"]' 'u32'
//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/a[@href="{{channel}}/std/primitive.i64.html"]' 'i64'
//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/a[@href="{{channel}}/std/primitive.i32.html"]' 'std::primitive::i32'
//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/a[@href="{{channel}}/std/primitive.str.html"]' 'std::primitive::str'
-//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/a[@href="{{channel}}/std/primitive.i32.html#associatedconstant.MAX"]' 'std::primitive::i32::MAX'
+//@ has foo/struct.Foo.html '//*[@class="docblock"]/p/code/a[@href="{{channel}}/std/primitive.i32.html#associatedconstant.MAX"]' 'std::primitive::i32::MAX'
/// It contains [`u32`] and [i64].
/// It also links to [std::primitive::i32], [std::primitive::str],
diff --git a/tests/rustdoc/short-docblock.rs b/tests/rustdoc/short-docblock.rs
index fa0af85696ac4..ee8d7ce43d5e2 100644
--- a/tests/rustdoc/short-docblock.rs
+++ b/tests/rustdoc/short-docblock.rs
@@ -20,7 +20,7 @@ pub fn foo() {}
/// foo mod
pub mod foo {}
-//@ has foo/index.html '//dd/a[@href="https://nougat.world"]/code' 'nougat'
+//@ has foo/index.html '//dd/code/a[@href="https://nougat.world"]' 'nougat'
/// [`nougat`](https://nougat.world)
pub struct Bar;