Skip to content
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

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -243,6 +243,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>,
Expand Down Expand Up @@ -767,6 +771,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");
Expand Down Expand Up @@ -867,6 +872,7 @@ impl Options {
id_map,
playground_url,
module_sorting,
crate_list_heading,
themes,
extension_css,
extern_html_root_urls,
Expand Down
31 changes: 27 additions & 4 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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)]
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/html/render/write_shared/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()]);
}
Expand Down
70 changes: 51 additions & 19 deletions src/librustdoc/html/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,36 +1003,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() {
Expand Down
14 changes: 14 additions & 0 deletions src/librustdoc/html/static/js/rustdoc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ declare global {
interface Window {
/** Make the current theme easy to find */
currentTheme: HTMLLinkElement|null;
/** 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. */
Expand Down Expand Up @@ -348,6 +350,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
Expand Down
8 changes: 8 additions & 0 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion tests/rustdoc-gui/sidebar.goml
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand Down
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"'
Loading