Skip to content

rustdoc: add unstable option --crate-list-heading to customize the sidebar crate list. #138143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
@@ -241,6 +241,10 @@ pub(crate) struct RenderOptions {
/// What sorting mode to use for module pages.
/// `ModuleSorting::Alphabetical` by default.
pub(crate) module_sorting: ModuleSorting,
/// In the sidebar list of all crates, put the current crate(s) under this heading.
/// To put a crate under the unnamed primary heading, which is always listed first,
/// make this the empty string.
pub(crate) crate_list_heading: String,
/// List of themes to extend the docs with. Original argument name is included to assist in
/// displaying errors if it fails a theme check.
pub(crate) themes: Vec<StylePath>,
@@ -760,6 +764,7 @@ impl Options {
} else {
ModuleSorting::Alphabetical
};
let crate_list_heading = matches.opt_str("crate-list-heading").unwrap_or_default();
let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default();
let markdown_no_toc = matches.opt_present("markdown-no-toc");
let markdown_css = matches.opt_strs("markdown-css");
@@ -859,6 +864,7 @@ impl Options {
id_map,
playground_url,
module_sorting,
crate_list_heading,
themes,
extension_css,
extern_html_root_urls,
31 changes: 27 additions & 4 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
//! --resource-suffix flag and are emitted when --emit-type is empty (default)
//! or contains "invocation-specific".
use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::OsString;
use std::fs::File;
@@ -69,8 +70,13 @@ pub(crate) fn write_shared(
write_search_desc(cx, krate, &desc)?; // does not need to be merged

let crate_name = krate.name(cx.tcx());
let crate_name = crate_name.as_str(); // rand
let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
let crate_name = crate_name.as_str(); // e.g. rand
let crate_name_json = OrderedJson::serialize(AllCratesEntry {
heading: Cow::Borrowed(&opt.crate_list_heading),
crate_name: Cow::Borrowed(crate_name),
})
.unwrap(); // e.g. {"c":"rand","h":""}

let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
let info = CrateInfo {
version: CrateInfoVersion::V1,
@@ -387,6 +393,19 @@ impl AllCratesPart {
}
}

/// Type of a serialized entry in the [`AllCratesPart`] JSON.
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct AllCratesEntry<'a> {
#[serde(rename = "c")]
crate_name: Cow<'a, str>,

/// If non-absent and non-empty, is the name of a section heading in the crate sidebar.
#[serde(rename = "h")]
#[serde(default)]
#[serde(skip_serializing_if = "str::is_empty")]
heading: Cow<'a, str>,
}

/// Reads `crates.js`, which seems like the best
/// place to obtain the list of externally documented crates if the index
/// page was disabled when documenting the deps.
@@ -408,8 +427,12 @@ fn hack_get_external_crate_names(
let Some(content) = regex.find(&content) else {
return Err(Error::new("could not find crates list in crates.js", path));
};
let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
Ok(content)
let crate_names: Vec<String> =
try_err!(serde_json::from_str::<Vec<AllCratesEntry<'static>>>(content.as_str()), &path)
.into_iter()
.map(|entry| entry.crate_name.into_owned())
.collect();
Ok(crate_names)
}

#[derive(Serialize, Deserialize, Clone, Default, Debug)]
3 changes: 2 additions & 1 deletion src/librustdoc/html/render/write_shared/tests.rs
Original file line number Diff line number Diff line change
@@ -9,7 +9,8 @@ fn hack_external_crate_names() {
let path = path.path();
let crates = hack_get_external_crate_names(&path, "").unwrap();
assert!(crates.is_empty());
fs::write(path.join("crates.js"), r#"window.ALL_CRATES = ["a","b","c"];"#).unwrap();
fs::write(path.join("crates.js"), r#"window.ALL_CRATES = [{"c":"a"},{"c":"b"},{"c":"c"}];"#)
.unwrap();
let crates = hack_get_external_crate_names(&path, "").unwrap();
assert_eq!(crates, ["a".to_string(), "b".to_string(), "c".to_string()]);
}
70 changes: 51 additions & 19 deletions src/librustdoc/html/static/js/main.js
Original file line number Diff line number Diff line change
@@ -994,36 +994,68 @@ function preLoadCss(cssUrl) {
window.register_type_impls(window.pending_type_impls);
}

// Draw a convenient sidebar of known crates if we have a listing
function addSidebarCrates() {
// @ts-expect-error
if (!window.ALL_CRATES) {
return;
}
const sidebarElems = document.getElementById("rustdoc-modnav");
if (!sidebarElems) {
return;
}
// Draw a convenient sidebar of known crates if we have a listing
const h3 = document.createElement("h3");
h3.innerHTML = "Crates";
const ul = document.createElement("ul");
ul.className = "block crate";

// @ts-expect-error
for (const crate of window.ALL_CRATES) {
const link = document.createElement("a");
link.href = window.rootPath + crate + "/index.html";
link.textContent = crate;

const li = document.createElement("li");
if (window.rootPath !== "./" && crate === window.currentCrate) {
li.className = "current";
// h2 puts this on the same level as the current crate name heading at the top
const allCratesHeading = document.createElement("h2");
allCratesHeading.textContent = "Crates";

const allCratesSection = document.createElement("section");

// window.ALL_CRATES is in unsorted array-of-structs format; reorganize so crates with the
// same heading are grouped.
const cratesGroupedByHeading = new Map();
for (const entry of window.ALL_CRATES) {
const heading = entry.h || "";
const crateName = entry.c;
let group = cratesGroupedByHeading.get(heading);
if (group === undefined) {
group = [];
cratesGroupedByHeading.set(heading, group);
}
li.appendChild(link);
ul.appendChild(li);
group.push(crateName);
}
const headings = Array.from(cratesGroupedByHeading.keys());
headings.sort();

// Generate HTML for grouped crates.
for (const headingText of headings) {
// Empty string denotes a group with no named heading.
if (headingText !== "") {
const crateCategoryHeading = document.createElement("h3");
crateCategoryHeading.textContent = headingText;
allCratesSection.appendChild(crateCategoryHeading);
}

const cratesUl = document.createElement("ul");
cratesUl.className = "block crate";

for (const crateName of cratesGroupedByHeading.get(headingText)) {
const link = document.createElement("a");
link.href = window.rootPath + crateName + "/index.html";
link.textContent = crateName;

const li = document.createElement("li");
if (window.rootPath !== "./" && crateName === window.currentCrate) {
li.className = "current";
}
li.appendChild(link);
cratesUl.appendChild(li);
}

allCratesSection.appendChild(cratesUl);
}
sidebarElems.appendChild(h3);
sidebarElems.appendChild(ul);

sidebarElems.appendChild(allCratesHeading);
sidebarElems.appendChild(allCratesSection);
}

function expandAllDocs() {
14 changes: 14 additions & 0 deletions src/librustdoc/html/static/js/rustdoc.d.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,8 @@ declare global {
currentTheme: HTMLLinkElement|null;
/** Generated in `render/context.rs` */
SIDEBAR_ITEMS?: { [key: string]: string[] };
/** List of all documented crates. */
ALL_CRATES: rustdoc.AllCratesEntry[]|undefined;
/** Used by the popover tooltip code. */
RUSTDOC_TOOLTIP_HOVER_MS: number;
/** Used by the popover tooltip code. */
@@ -396,6 +398,18 @@ declare namespace rustdoc {
bindings: Map<number, FingerprintableType[]>;
};

/** Member of ALL_CRATES. */
interface AllCratesEntry {
/**
* Heading under which the crate should be listed.
*
* May be empty to specify the first, primary heading.
*/
h?: string,
/** Crate name. */
c: string,
}

/**
* The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
* are arrays with the same length. `q`, `a`, and `c` use a sparse
8 changes: 8 additions & 0 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
@@ -383,6 +383,14 @@ fn opts() -> Vec<RustcOptGroup> {
"sort modules by where they appear in the program, rather than alphabetically",
"",
),
opt(
Unstable,
Opt,
"",
"crate-list-heading",
"heading under which to list this crate in the sidebar crate list",
"TEXT",
),
opt(
Stable,
Opt,
4 changes: 3 additions & 1 deletion tests/rustdoc-gui/sidebar.goml
Original file line number Diff line number Diff line change
@@ -48,7 +48,9 @@ call-function: ("switch-theme", {"theme": "light"})
assert-text: (".sidebar > .sidebar-crate > h2 > a", "test_docs")
// Crate root has no "location" element
assert-count: (".sidebar .location", 0)
assert-count: (".sidebar h2", 1)
// Crate root has two h2s, the second of which is the the crate list
assert-count: (".sidebar h2", 2)
assert-text: (".sidebar #rustdoc-modnav h2", "Crates")
assert-text: ("#all-types", "All Items")
assert-css: ("#all-types", {"color": "#356da4"})
// We check that we have the crates list and that the "current" on is "test_docs".
5 changes: 5 additions & 0 deletions tests/rustdoc/crate-list-heading.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@ compile-flags: -Zunstable-options --crate-list-heading=helloworld
#![crate_name = "foo"]

//@ hasraw crates.js '"h":"helloworld"'
//@ hasraw crates.js '"c":"foo"'