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 header map to the table of contents #120736

Merged
merged 8 commits into from
Sep 5, 2024

Conversation

notriddle
Copy link
Contributor

@notriddle notriddle commented Feb 7, 2024

Summary

Add header sections to the sidebar TOC.

Preview

image

Motivation

Some pages are very wordy, like these.

crate word count
std::option 2,138
derive_builder 2,403
tracing 3,912
regex 8,412

This kind of very long document is more navigable with a table of contents, like Wikipedia's or the one GitHub recently added for READMEs.

In fact, the use case is so compelling, that it's been requested multiple times and implemented in an extension:

(Some of these issues ask for more than this, so don’t close them.)

It's also been implemented by hand in some crates, because the author really thought it was needed. Protip: for a more exhaustive list, run site:docs.rs table of contents, though some of them are false positives.

Unfortunately for these hand-built ToCs, because they're just part of the docs, there's no consistent way to turn them off if the reader doesn't want them. It's also more complicated to ensure they stay in sync with the docs they're supposed to describe, and they don't stay with you when you scroll like Wikipedia's does now.

Guide-level explanation

When writing docs for a top-level item, the first and second level of headers will be shown in an outline in the sidebar. In this context, "top level" means "not associated".

This means, if you're writing very long guides or explanations, and you want it to have a table of contents in the sidebar for its headings, the ideal place to attach it is usually the module or crate, because this page has fewer other things on it (and is the ideal place to describe "cross-cutting concerns" for its child items).

If you're reading documentation, and want to get rid of the table of contents, open the image Settings panel and checkmark "Hide table of contents."

Reference-level explanation

Top-level items have an outline generated. This works for potentially-malformed header trees by pairing a header with the nearest header with a higher level. For example:

## A
# B
# C
## D
## E

A, B, and C are all siblings, and D and E are children of C.

Rustdoc only presents two layers of tree, but it tracks up to the full depth of 6 while preparing it.

That means that these two doc comment both generate the same outline:

/// # First
/// ## Second
struct One;
/// ## First
/// ### Second
struct Two;

Drawbacks

The biggest drawback is adding more stuff to the sidebar.

My crawl through docs.rs shows this to, surprisingly, be less of a problem than I thought. The manually-built tables of contents, and the pages with dozens of headers, usually seem to be modules or crates, not types (where extreme scrolling would become a problem, since they already have methods to deal with).

The best example of a type with many headers is vec::Vec, which still only has five headers, not dozens like axum::extract.

Rationale and alternatives

Why in the existing sidebar?

The method links and the top-doc header links have more in common with each other than either of them do with the "In [parent module]" links, and should go together.

Why limited to two levels?

The sidebar is pretty narrow, and I don't want too much space used by indentation. Making the sidebar wider, while it has some upsides, also takes up more space on middling-sized screens or tiled WMs.

Why not line wrap?

That behaves strangely when resizing.

Prior art

Doc generators that have TOC for headers

https://hexdocs.pm/phoenix/Phoenix.Controller.html is very close, in the sense that it also has header sections directly alongside functions and types.

Another example, referenced as part of the early sidebar discussion that added methods, Ruby will show a table of contents in the sidebar (for example, on the ARGF class). According to their changelog, they added it in 2013.

Haskell seems to mix text and functions even more freely than Elixir. For example, this Naming conventions is plain text, and is immediately followed by functions. And the Pandoc top level has items split up by function, rather than by kind. Their TOC matches exactly with the contents of the page.

Doc generators that don't have header TOC, but still have headers

Elm, interestingly enough, seems to have the same setup that Rust used to have: sibling navigation between modules, and no index within a single page. They keep Haskell's habit of named sections with machine-generated type signatures, though.

PHP, like elm, also has a right-hand sidebar with sibling navigation. However, PHP has a single page for a single method, unlike Rust's page for an entire "class." So even though these pages have headers, it's never more than ten at most. And when they have guides, those guides are also multi-page.

Unresolved questions

  • Writing recommendations for anyone who wants to take advantage of this.
  • Right now, it does not line wrap. That might be a bad idea: a lot of these are getting truncated.
  • Split sidebars, which I tried implementing, are not required. The TOC can be turned off, if it's really a problem. Implemented in rustdoc: add three-column layout for large desktops #120818, but needs more, separate, discussion.

Future possibilities

I would like to do a better job of distinguishing global navigation from local navigation. Rustdoc has a pretty reasonable information architecture, if only we did a better job of communicating it.

This PR aims, mostly, to help doc authors help their users by writing docs that can be more effectively skimmed. But it doesn't do anything to make it easier to tell the TOC and the Module Nav apart.

@rustbot
Copy link
Collaborator

rustbot commented Feb 7, 2024

r? @jsha

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Feb 7, 2024
@rustbot
Copy link
Collaborator

rustbot commented Feb 7, 2024

Some changes occurred in src/librustdoc/clean/types.rs

cc @camelid

Some changes occurred in GUI tests.

cc @GuillaumeGomez

Some changes occurred in HTML/CSS/JS.

cc @GuillaumeGomez, @jsha

@the8472
Copy link
Member

the8472 commented Feb 7, 2024

In fact, the use case is so compelling, that it's been requested multiple times and implemented in an extension:

At least some of the requests (e.g. #14475, #40273) are about method signature summary tables, not the headings for the prose.

Additionally - as a desktop user who frequently fullscreens browser windows - I would prefer if such navigation aids were placed in the vast empty space on the right side rather on the very narrow left bar.

@notriddle
Copy link
Contributor Author

notriddle commented Feb 7, 2024

At least some of the requests are about method signature summary tables, not the headings for the prose.

Some of the requests are a bit unclear about what they want. 80858 has a bunch of comments that talk about header outlines, but was closed as a duplicate of 14475.

Additionally - as a desktop user who frequently fullscreens browser windows - I would prefer if such navigation aids were placed in the vast empty space on the right side rather on the very narrow left bar.

I agree. I've tried a few different implementations of this, before deciding that the details were complicated enough to not block this on them. It's the same asymmetry that motivates poor mobile-first design: a small-screen layout on a big-screen is a lot better than a big-screen layout on a small-screen, so the three-column ("split sidebar") layout has to be in addition to the two-column layout, but can't replace it.

Is it a blocking concern for this PR? If not, I'll open another PR that implements it.

@the8472
Copy link
Member

the8472 commented Feb 7, 2024

Regarding the issues. I want to avoid issues getting closed when this isn't the thing some of them are asking for. And also make it clear that what is being asked for is more complicated.

Is it a blocking concern for this PR?

No.

@notriddle
Copy link
Contributor Author

Added a note to the PR

(Some of these issues ask for more than this, so don’t close them.)

@GuillaumeGomez
Copy link
Member

My only issue with the current version is it's actually not obvious what I'm reading at the top of the sidebar (also I'm a bit sad because I really liked the version with a new sidebar).

It's also a bit strange to have the table of contents followed directly by section links like "Structs" as there is no real visual marker to indicate it's not the table of content anymore.

@notriddle
Copy link
Contributor Author

My only issue with the current version is it's actually not obvious what I'm reading at the top of the sidebar (also I'm a bit sad because I really liked the version with a new sidebar).

Would an explicit label for the "Sections" be a good idea, like how Elixir does it?

image

Or Wikipedia, where the whole thing is the "Contents"?

image

I hesitated, originally, because it added another line of text, but maybe it's worth it?

It's also a bit strange to have the table of contents followed directly by section links like "Structs" as there is no real visual marker to indicate it's not the table of content anymore.

That's still the table of contents. They're programmatically generated, instead of coming from the Markdown text, but they're still part of the current page.

also I'm a bit sad because I really liked the version with a new sidebar

I agree. #120818 does that.

@GuillaumeGomez
Copy link
Member

Would an explicit label for the "Sections" be a good idea, like how Elixir does it?

I think I prefer the non-wikipedia approach, but both look much better than the current situation!

That's still the table of contents. They're programmatically generated, instead of coming from the Markdown text, but they're still part of the current page.

Then maybe add a bit more of spacing to make it more obvious it's not the same thing?

I agree. #120818 does that.

👍

@notriddle
Copy link
Contributor Author

Then maybe add a bit more of spacing to make it more obvious it's not the same thing?

Why is it important that they're not "the same thing?" If somebody saw the page for the first time, what kind of mistake would they make if we failed to get across to them the difference between the sections that list child items and the sections that contain hand-written docs?

@GuillaumeGomez
Copy link
Member

My issue here is that it takes me (not sure about others, so might be irrelevant) much more time to find the methods/items list when I need to go through the TOC first. There is no visual marker to help there too.

@Folyd
Copy link
Contributor

Folyd commented Feb 14, 2024

Good to know librustdoc support TOC in the doc page. Just a suggestion, why not put the TOC to the right? Rust Search Extension is an example :)
https://rust.extension.sh/#show-table-of-content

@bors
Copy link
Contributor

bors commented Jun 1, 2024

☔ The latest upstream changes (presumably #124577) made this pull request unmergeable. Please resolve the merge conflicts.

@GuillaumeGomez
Copy link
Member

Sorry it took me so long to come back to this PR. This is a nice improvement! Please start the FCP once merge conflicts are fixed.

@notriddle notriddle added T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output. and removed T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Jul 3, 2024
@bors
Copy link
Contributor

bors commented Jul 18, 2024

☔ The latest upstream changes (presumably #127865) made this pull request unmergeable. Please resolve the merge conflicts.

@notriddle
Copy link
Contributor Author

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Jul 18, 2024

Team member @notriddle has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jul 18, 2024
@GuillaumeGomez
Copy link
Member

I see in your demo that you have:

image

Is it coming from a change in this PR?

@GuillaumeGomez
Copy link
Member

Ah nevermind, it was a bug on current nightly. Fixed in #127963. Can you regen once this PR is merged please? Just want to do one last tour to ensure I didn't miss anything before approving.

@jsha
Copy link
Contributor

jsha commented Aug 25, 2024

@rustbot r? @GuillaumeGomez

@rustbot rustbot assigned GuillaumeGomez and unassigned jsha Aug 25, 2024
@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Aug 25, 2024
@rfcbot
Copy link

rfcbot commented Aug 25, 2024

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Sep 4, 2024
@rfcbot
Copy link

rfcbot commented Sep 4, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@GuillaumeGomez
Copy link
Member

Thanks everyone!

@bors r=t-rustdoc

@bors
Copy link
Contributor

bors commented Sep 4, 2024

📌 Commit bead042 has been approved by t-rustdoc

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 4, 2024
tgross35 added a commit to tgross35/rust that referenced this pull request Sep 5, 2024
rustdoc: add header map to the table of contents

## Summary

Add header sections to the sidebar TOC.

### Preview

![image](https://github.com/user-attachments/assets/eae4df02-86aa-4df4-8c61-a95685cd8829)

* http://notriddle.com/rustdoc-html-demo-9/toc/rust/std/index.html
* http://notriddle.com/rustdoc-html-demo-9/toc/rust-derive-builder/derive_builder/index.html

## Motivation

Some pages are very wordy, like these.

| crate | word count |
|--|--|
| [std::option](https://doc.rust-lang.org/stable/std/option/index.html) | 2,138
| [derive_builder](https://docs.rs/derive_builder/0.13.0/derive_builder/index.html) | 2,403
| [tracing](https://docs.rs/tracing/0.1.40/tracing/index.html) | 3,912
| [regex](https://docs.rs/regex/1.10.3/regex/index.html) | 8,412

This kind of very long document is more navigable with a table of contents, like Wikipedia's or the one [GitHub recently added](https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/) for READMEs.

In fact, the use case is so compelling, that it's been requested multiple times and implemented in an extension:

* rust-lang#80858
* rust-lang#28056
* rust-lang#14475
* https://rust.extension.sh/#show-table-of-content

(Some of these issues ask for more than this, so don’t close them.)

It's also been implemented by hand in some crates, because the author really thought it was needed. Protip: for a more exhaustive list, run [`site:docs.rs table of contents`](https://duckduckgo.com/?t=ffab&q=site%3Adocs.rs+table+of+contents&ia=web), though some of them are false positives.

* https://docs.rs/figment/0.10.14/figment/index.html#table-of-contents
* https://docs.rs/csv/1.3.0/csv/tutorial/index.html#table-of-contents
* https://docs.rs/axum/0.7.4/axum/response/index.html#table-of-contents
* https://docs.rs/regex-automata/0.4.5/regex_automata/index.html#table-of-contents

Unfortunately for these hand-built ToCs, because they're just part of the docs, there's no consistent way to turn them off if the reader doesn't want them. It's also more complicated to ensure they stay in sync with the docs they're supposed to describe, and they don't stay with you when you scroll like Wikipedia's [does now](https://uxdesign.cc/design-notes-on-the-2023-wikipedia-redesign-d6573b9af28d).

## Guide-level explanation

When writing docs for a top-level item, the first and second level of headers will be shown in an outline in the sidebar. In this context, "top level" means "not associated".

This means, if you're writing very long guides or explanations, and you want it to have a table of contents in the sidebar for its headings, the ideal place to attach it is usually the *module* or *crate*, because this page has fewer other things on it (and is the ideal place to describe "cross-cutting concerns" for its child items).

If you're reading documentation, and want to get rid of the table of contents, open the ![image](https://github.com/rust-lang/rust/assets/1593513/2ad82466-5fe3-4684-b1c2-6be4c99a8666) Settings panel and checkmark "Hide table of contents."

## Reference-level explanation

Top-level items have an outline generated. This works for potentially-malformed header trees by pairing a header with the nearest header with a higher level. For example:

```markdown
## A
# B
# C
## D
## E
```

A, B, and C are all siblings, and D and E are children of C.

Rustdoc only presents two layers of tree, but it tracks up to the full depth of 6 while preparing it.

That means that these two doc comment both generate the same outline:

```rust
/// # First
/// ## Second
struct One;
/// ## First
/// ### Second
struct Two;
```

## Drawbacks

The biggest drawback is adding more stuff to the sidebar.

My crawl through docs.rs shows this to, surprisingly, be less of a problem than I thought. The manually-built tables of contents, and the pages with dozens of headers, usually seem to be modules or crates, not types (where extreme scrolling would become a problem, since they already have methods to deal with).

The best example of a type with many headers is [vec::Vec](https://doc.rust-lang.org/1.75.0/std/vec/struct.Vec.html), which still only has five headers, not dozens like [axum::extract](https://docs.rs/axum/0.7.4/axum/extract/index.html).

## Rationale and alternatives

### Why in the existing sidebar?

The method links and the top-doc header links have more in common with each other than either of them do with the "In [parent module]" links, and should go together.

### Why limited to two levels?

The sidebar is pretty narrow, and I don't want too much space used by indentation. Making the sidebar wider, while it has some upsides, also takes up more space on middling-sized screens or tiled WMs.

### Why not line wrap?

That behaves strangely when resizing.

## Prior art

### Doc generators that have TOC for headers

https://hexdocs.pm/phoenix/Phoenix.Controller.html is very close, in the sense that it also has header sections directly alongside functions and types.

Another example, referenced as part of the [early sidebar discussion](rust-lang#37856) that added methods, Ruby will show a table of contents in the sidebar (for example, on the [ARGF](https://docs.ruby-lang.org/en/master/ARGF.html) class). According to their changelog, [they added it in 2013](https://github.com/ruby/rdoc/blob/06137bde8ccc48cd502bc28178bcd8f2dfe37624/History.rdoc#400--2013-02-24-).

Haskell seems to mix text and functions even more freely than Elixir. For example, this [Naming conventions](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#g:3) is plain text, and is immediately followed by functions. And the [Pandoc top level](https://hackage.haskell.org/package/pandoc-3.1.11.1/docs/Text-Pandoc.html) has items split up by function, rather than by kind. Their TOC matches exactly with the contents of the page.

### Doc generators that don't have header TOC, but still have headers

Elm, interestingly enough, seems to have the same setup that Rust used to have: sibling navigation between modules, and no index within a single page. [They keep Haskell's habit of named sections with machine-generated type signatures](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Dom), though.

[PHP](https://www.php.net/manual/en/book.datetime.php), like elm, also has a right-hand sidebar with sibling navigation. However, PHP has a single page for a single method, unlike Rust's page for an entire "class." So even though these pages have headers, it's never more than ten at most. And when they have guides, those guides are also multi-page.

## Unresolved questions

* Writing recommendations for anyone who wants to take advantage of this.
* Right now, it does not line wrap. That might be a bad idea: a lot of these are getting truncated.
* Split sidebars, which I [tried implementing](https://rust-lang.zulipchat.com/#narrow/stream/266220-t-rustdoc/topic/Table.20of.20contents), are not required. The TOC can be turned off, if it's really a problem. Implemented in rust-lang#120818, but needs more, separate, discussion.

## Future possibilities

I would like to do a better job of distinguishing global navigation from local navigation. Rustdoc has a pretty reasonable information architecture, if only we did a better job of communicating it.

This PR aims, mostly, to help doc authors help their users by writing docs that can be more effectively skimmed. But it doesn't do anything to make it easier to tell the TOC and the Module Nav apart.
bors added a commit to rust-lang-ci/rust that referenced this pull request Sep 5, 2024
…iaskrgr

Rollup of 10 pull requests

Successful merges:

 - rust-lang#101339 (enable -Zrandomize-layout in debug CI builds )
 - rust-lang#120736 (rustdoc: add header map to the table of contents)
 - rust-lang#127021 (Add target support for RTEMS Arm)
 - rust-lang#128928 (CI: rfl: add more tools and steps)
 - rust-lang#129584 (warn the user if the upstream master branch is old)
 - rust-lang#129664 (Arbitrary self types v2: pointers feature gate.)
 - rust-lang#129752 (Make supertrait and implied predicates queries defaulted)
 - rust-lang#129918 (Update docs of `missing_abi` lint)
 - rust-lang#129919 (Stabilize `waker_getters`)
 - rust-lang#129925 (remove deprecated option `rust.split-debuginfo`)

Failed merges:

 - rust-lang#129789 (rustdoc: use strategic boxing to shrink `clean::Item`)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Sep 5, 2024
…iaskrgr

Rollup of 10 pull requests

Successful merges:

 - rust-lang#101339 (enable -Zrandomize-layout in debug CI builds )
 - rust-lang#120736 (rustdoc: add header map to the table of contents)
 - rust-lang#127021 (Add target support for RTEMS Arm)
 - rust-lang#128928 (CI: rfl: add more tools and steps)
 - rust-lang#129584 (warn the user if the upstream master branch is old)
 - rust-lang#129664 (Arbitrary self types v2: pointers feature gate.)
 - rust-lang#129752 (Make supertrait and implied predicates queries defaulted)
 - rust-lang#129918 (Update docs of `missing_abi` lint)
 - rust-lang#129919 (Stabilize `waker_getters`)
 - rust-lang#129925 (remove deprecated option `rust.split-debuginfo`)

Failed merges:

 - rust-lang#129789 (rustdoc: use strategic boxing to shrink `clean::Item`)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit e1da72c into rust-lang:master Sep 5, 2024
6 checks passed
@rustbot rustbot added this to the 1.83.0 milestone Sep 5, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Sep 5, 2024
Rollup merge of rust-lang#120736 - notriddle:notriddle/toc, r=t-rustdoc

rustdoc: add header map to the table of contents

## Summary

Add header sections to the sidebar TOC.

### Preview

![image](https://github.com/user-attachments/assets/eae4df02-86aa-4df4-8c61-a95685cd8829)

* http://notriddle.com/rustdoc-html-demo-9/toc/rust/std/index.html
* http://notriddle.com/rustdoc-html-demo-9/toc/rust-derive-builder/derive_builder/index.html

## Motivation

Some pages are very wordy, like these.

| crate | word count |
|--|--|
| [std::option](https://doc.rust-lang.org/stable/std/option/index.html) | 2,138
| [derive_builder](https://docs.rs/derive_builder/0.13.0/derive_builder/index.html) | 2,403
| [tracing](https://docs.rs/tracing/0.1.40/tracing/index.html) | 3,912
| [regex](https://docs.rs/regex/1.10.3/regex/index.html) | 8,412

This kind of very long document is more navigable with a table of contents, like Wikipedia's or the one [GitHub recently added](https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/) for READMEs.

In fact, the use case is so compelling, that it's been requested multiple times and implemented in an extension:

* rust-lang#80858
* rust-lang#28056
* rust-lang#14475
* https://rust.extension.sh/#show-table-of-content

(Some of these issues ask for more than this, so don’t close them.)

It's also been implemented by hand in some crates, because the author really thought it was needed. Protip: for a more exhaustive list, run [`site:docs.rs table of contents`](https://duckduckgo.com/?t=ffab&q=site%3Adocs.rs+table+of+contents&ia=web), though some of them are false positives.

* https://docs.rs/figment/0.10.14/figment/index.html#table-of-contents
* https://docs.rs/csv/1.3.0/csv/tutorial/index.html#table-of-contents
* https://docs.rs/axum/0.7.4/axum/response/index.html#table-of-contents
* https://docs.rs/regex-automata/0.4.5/regex_automata/index.html#table-of-contents

Unfortunately for these hand-built ToCs, because they're just part of the docs, there's no consistent way to turn them off if the reader doesn't want them. It's also more complicated to ensure they stay in sync with the docs they're supposed to describe, and they don't stay with you when you scroll like Wikipedia's [does now](https://uxdesign.cc/design-notes-on-the-2023-wikipedia-redesign-d6573b9af28d).

## Guide-level explanation

When writing docs for a top-level item, the first and second level of headers will be shown in an outline in the sidebar. In this context, "top level" means "not associated".

This means, if you're writing very long guides or explanations, and you want it to have a table of contents in the sidebar for its headings, the ideal place to attach it is usually the *module* or *crate*, because this page has fewer other things on it (and is the ideal place to describe "cross-cutting concerns" for its child items).

If you're reading documentation, and want to get rid of the table of contents, open the ![image](https://github.com/rust-lang/rust/assets/1593513/2ad82466-5fe3-4684-b1c2-6be4c99a8666) Settings panel and checkmark "Hide table of contents."

## Reference-level explanation

Top-level items have an outline generated. This works for potentially-malformed header trees by pairing a header with the nearest header with a higher level. For example:

```markdown
## A
# B
# C
## D
## E
```

A, B, and C are all siblings, and D and E are children of C.

Rustdoc only presents two layers of tree, but it tracks up to the full depth of 6 while preparing it.

That means that these two doc comment both generate the same outline:

```rust
/// # First
/// ## Second
struct One;
/// ## First
/// ### Second
struct Two;
```

## Drawbacks

The biggest drawback is adding more stuff to the sidebar.

My crawl through docs.rs shows this to, surprisingly, be less of a problem than I thought. The manually-built tables of contents, and the pages with dozens of headers, usually seem to be modules or crates, not types (where extreme scrolling would become a problem, since they already have methods to deal with).

The best example of a type with many headers is [vec::Vec](https://doc.rust-lang.org/1.75.0/std/vec/struct.Vec.html), which still only has five headers, not dozens like [axum::extract](https://docs.rs/axum/0.7.4/axum/extract/index.html).

## Rationale and alternatives

### Why in the existing sidebar?

The method links and the top-doc header links have more in common with each other than either of them do with the "In [parent module]" links, and should go together.

### Why limited to two levels?

The sidebar is pretty narrow, and I don't want too much space used by indentation. Making the sidebar wider, while it has some upsides, also takes up more space on middling-sized screens or tiled WMs.

### Why not line wrap?

That behaves strangely when resizing.

## Prior art

### Doc generators that have TOC for headers

https://hexdocs.pm/phoenix/Phoenix.Controller.html is very close, in the sense that it also has header sections directly alongside functions and types.

Another example, referenced as part of the [early sidebar discussion](rust-lang#37856) that added methods, Ruby will show a table of contents in the sidebar (for example, on the [ARGF](https://docs.ruby-lang.org/en/master/ARGF.html) class). According to their changelog, [they added it in 2013](https://github.com/ruby/rdoc/blob/06137bde8ccc48cd502bc28178bcd8f2dfe37624/History.rdoc#400--2013-02-24-).

Haskell seems to mix text and functions even more freely than Elixir. For example, this [Naming conventions](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#g:3) is plain text, and is immediately followed by functions. And the [Pandoc top level](https://hackage.haskell.org/package/pandoc-3.1.11.1/docs/Text-Pandoc.html) has items split up by function, rather than by kind. Their TOC matches exactly with the contents of the page.

### Doc generators that don't have header TOC, but still have headers

Elm, interestingly enough, seems to have the same setup that Rust used to have: sibling navigation between modules, and no index within a single page. [They keep Haskell's habit of named sections with machine-generated type signatures](https://package.elm-lang.org/packages/elm/browser/latest/Browser-Dom), though.

[PHP](https://www.php.net/manual/en/book.datetime.php), like elm, also has a right-hand sidebar with sibling navigation. However, PHP has a single page for a single method, unlike Rust's page for an entire "class." So even though these pages have headers, it's never more than ten at most. And when they have guides, those guides are also multi-page.

## Unresolved questions

* Writing recommendations for anyone who wants to take advantage of this.
* Right now, it does not line wrap. That might be a bad idea: a lot of these are getting truncated.
* Split sidebars, which I [tried implementing](https://rust-lang.zulipchat.com/#narrow/stream/266220-t-rustdoc/topic/Table.20of.20contents), are not required. The TOC can be turned off, if it's really a problem. Implemented in rust-lang#120818, but needs more, separate, discussion.

## Future possibilities

I would like to do a better job of distinguishing global navigation from local navigation. Rustdoc has a pretty reasonable information architecture, if only we did a better job of communicating it.

This PR aims, mostly, to help doc authors help their users by writing docs that can be more effectively skimmed. But it doesn't do anything to make it easier to tell the TOC and the Module Nav apart.
@notriddle notriddle deleted the notriddle/toc branch September 5, 2024 13:32
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Sep 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants