diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 1e6cab8fcd3b7..8922bf377858e 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -29,6 +29,8 @@ pub(crate) struct HrefContext<'a, 'b, 'c> { /// This field is used to know "how far" from the top of the directory we are to link to either /// documentation pages or other source pages. pub(crate) root_path: &'c str, + /// This field is used to calculate precise local URLs. + pub(crate) current_href: &'c str, } /// Decorations are represented as a map from CSS class to vector of character ranges. @@ -977,9 +979,9 @@ fn string_without_closing_tag( // a link to their definition can be generated using this: // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338 match href { - LinkFromSrc::Local(span) => context - .href_from_span(*span, true) - .map(|s| format!("{}{}", href_context.root_path, s)), + LinkFromSrc::Local(span) => { + context.href_from_span_relative(*span, href_context.current_href) + } LinkFromSrc::External(def_id) => { format::href_with_root_path(*def_id, context, Some(href_context.root_path)) .ok() diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 01b96dc721558..62def4a94e8dc 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -31,6 +31,7 @@ use crate::formats::FormatRenderer; use crate::html::escape::Escape; use crate::html::format::{join_with_double_colon, Buffer}; use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; +use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{layout, sources}; use crate::scrape_examples::AllCallLocations; use crate::try_err; @@ -370,6 +371,35 @@ impl<'tcx> Context<'tcx> { anchor = anchor )) } + + pub(crate) fn href_from_span_relative( + &self, + span: clean::Span, + relative_to: &str, + ) -> Option { + self.href_from_span(span, false).map(|s| { + let mut url = UrlPartsBuilder::new(); + let mut dest_href_parts = s.split('/'); + let mut cur_href_parts = relative_to.split('/'); + for (cur_href_part, dest_href_part) in (&mut cur_href_parts).zip(&mut dest_href_parts) { + if cur_href_part != dest_href_part { + url.push(dest_href_part); + break; + } + } + for dest_href_part in dest_href_parts { + url.push(dest_href_part); + } + let loline = span.lo(self.sess()).line; + let hiline = span.hi(self.sess()).line; + format!( + "{}{}#{}", + "../".repeat(cur_href_parts.count()), + url.finish(), + if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") } + ) + }) + } } /// Generates the documentation for `crate` into the directory `dst` diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index f37c54e42983f..2e2bee78b95f9 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -288,11 +288,14 @@ pub(crate) fn print_src( } } line_numbers.write_str(""); + let current_href = &context + .href_from_span(clean::Span::new(file_span), false) + .expect("only local crates should have sources emitted"); highlight::render_source_with_highlighting( s, buf, line_numbers, - highlight::HrefContext { context, file_span, root_path }, + highlight::HrefContext { context, file_span, root_path, current_href }, decoration_info, ); } diff --git a/src/test/rustdoc/check-source-code-urls-to-def-std.rs b/src/test/rustdoc/check-source-code-urls-to-def-std.rs index 3396b234a77b1..e12d8445f4fa3 100644 --- a/src/test/rustdoc/check-source-code-urls-to-def-std.rs +++ b/src/test/rustdoc/check-source-code-urls-to-def-std.rs @@ -9,7 +9,7 @@ fn babar() {} // @has - '//a[@href="{{channel}}/std/primitive.u32.html"]' 'u32' // @has - '//a[@href="{{channel}}/std/primitive.str.html"]' 'str' // @has - '//a[@href="{{channel}}/std/primitive.bool.html"]' 'bool' -// @has - '//a[@href="../../src/foo/check-source-code-urls-to-def-std.rs.html#7"]' 'babar' +// @has - '//a[@href="#7"]' 'babar' pub fn foo(a: u32, b: &str, c: String) { let x = 12; let y: bool = true; @@ -31,12 +31,12 @@ macro_rules! data { pub fn another_foo() { // This is known limitation: if the macro doesn't generate anything, the visitor // can't find any item or anything that could tell us that it comes from expansion. - // @!has - '//a[@href="../../src/foo/check-source-code-urls-to-def-std.rs.html#19"]' 'yolo!' + // @!has - '//a[@href="#19"]' 'yolo!' yolo!(); // @has - '//a[@href="{{channel}}/std/macro.eprintln.html"]' 'eprintln!' eprintln!(); - // @has - '//a[@href="../../src/foo/check-source-code-urls-to-def-std.rs.html#27-29"]' 'data!' + // @has - '//a[@href="#27-29"]' 'data!' let x = data!(4); - // @has - '//a[@href="../../src/foo/check-source-code-urls-to-def-std.rs.html#23-25"]' 'bar!' + // @has - '//a[@href="#23-25"]' 'bar!' bar!(x); } diff --git a/src/test/rustdoc/check-source-code-urls-to-def.rs b/src/test/rustdoc/check-source-code-urls-to-def.rs index ec44e1bc65c90..d00a3e3551991 100644 --- a/src/test/rustdoc/check-source-code-urls-to-def.rs +++ b/src/test/rustdoc/check-source-code-urls-to-def.rs @@ -10,14 +10,14 @@ extern crate source_code; // @has 'src/foo/check-source-code-urls-to-def.rs.html' -// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#1-17"]' 'bar' +// @has - '//a[@href="auxiliary/source-code-bar.rs.html#1-17"]' 'bar' #[path = "auxiliary/source-code-bar.rs"] pub mod bar; -// @count - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#5"]' 4 +// @count - '//a[@href="auxiliary/source-code-bar.rs.html#5"]' 4 use bar::Bar; -// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#13"]' 'self' -// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14"]' 'Trait' +// @has - '//a[@href="auxiliary/source-code-bar.rs.html#13"]' 'self' +// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait' use bar::sub::{self, Trait}; pub struct Foo; @@ -31,26 +31,26 @@ fn babar() {} // @has - '//a/@href' '/struct.String.html' // @has - '//a/@href' '/primitive.u32.html' // @has - '//a/@href' '/primitive.str.html' -// @count - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#23"]' 5 +// @count - '//a[@href="#23"]' 5 // @has - '//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode' pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) { let x = 12; let y: Foo = Foo; let z: Bar = bar::Bar { field: Foo }; babar(); - // @has - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#26"]' 'hello' + // @has - '//a[@href="#26"]' 'hello' y.hello(); } -// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait' -// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14"]' 'Trait' +// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait' +// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait' pub fn foo2(t: &T, v: &V, b: bool) {} pub trait AnotherTrait {} pub trait WhyNot {} -// @has - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#49"]' 'AnotherTrait' -// @has - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#50"]' 'WhyNot' +// @has - '//a[@href="#49"]' 'AnotherTrait' +// @has - '//a[@href="#50"]' 'WhyNot' pub fn foo3(t: &T, v: &V) where T: AnotherTrait, @@ -59,7 +59,7 @@ where pub trait AnotherTrait2 {} -// @has - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#60"]' 'AnotherTrait2' +// @has - '//a[@href="#60"]' 'AnotherTrait2' pub fn foo4() { let x: Vec = Vec::new(); }