Commit graph

2453 commits

Author SHA1 Message Date
Lukas Markeffsky
d796ad4209 rustdoc ui: adjust tooltip z-index to be above sidebar 2023-12-31 20:31:56 +01:00
bors
67b6975051 Auto merge of #119066 - notriddle:notriddle/sidebar-source-redesign, r=GuillaumeGomez
rustdoc: clean up source sidebar hide button

This is a redesign of the feature, with parts pulled from https://github.com/rust-lang/rust/pull/119049 but with a button that looks more like a button and matches the one used on other sidebar pages.

Preview:

* http://notriddle.com/rustdoc-html-demo-8/source-sidebar-resize/src/std/lib.rs.html
* http://notriddle.com/rustdoc-html-demo-8/source-sidebar-resize/std/index.html

| | Before | After |
|--|--|--|
| Closed | ![image](https://github.com/rust-lang/rust/assets/1593513/092bed75-79c3-412f-8e7b-557f30dfb1e3) | ![image](https://github.com/rust-lang/rust/assets/1593513/b68e1ee9-9aef-484d-a5b1-2fd29c9d72ea)
| Open | ![image](https://github.com/rust-lang/rust/assets/1593513/95cf9545-25b1-48ec-820b-02e1aec99839) | ![image](https://github.com/rust-lang/rust/assets/1593513/923532f6-59e0-4d7c-9976-21699c30d42e)
| Mobile Closed | ![image](https://github.com/rust-lang/rust/assets/1593513/9bc00cc5-937c-4120-94be-94c7cb6d5297) | ![image](https://github.com/rust-lang/rust/assets/1593513/76a744d8-aac2-46fe-abb9-3b34e2d3ccaa)
| Mobile Open | ![image](https://github.com/rust-lang/rust/assets/1593513/d19a94fe-47b1-462d-a280-44fc215b9b72) | ![image](https://github.com/rust-lang/rust/assets/1593513/2b2e3dec-b610-4b12-8a72-35b86359ba45)
2023-12-31 15:32:15 +00:00
Matthias Krüger
2f51bad66b
Rollup merge of #119331 - notriddle:notriddle/maxpatheditdistance, r=GuillaumeGomez
rustdoc-search: count path edits with separate edit limit

Avoids strange-looking results like this one, where the path component seems to be ignored:

![image](https://github.com/rust-lang/rust/assets/1593513/f0ef077a-6e09-4d67-a29d-8cabc1495f66)

Since the two are counted separately elsewhere, they should get their own limits, too. The biggest problem with combining them is that paths are loosely checked by not requiring every component to match, which means that if they are short and matched loosely, they can easily find "drunk typist" matches that make no sense, like this old result:

    std::collections::btree_map::itermut matching slice::itermut
    maxEditDistance = ("slice::itermut".length) / 3 = 14 / 3 = 4
    editDistance("std", "slice") = 4
    editDistance("itermut", "itermut") = 0
        4 + 0 <= 4 PASS

Of course, `slice::itermut` should not match stuff from btreemap. `slice` should not match `std`.

The new result counts them separately:

    maxPathEditDistance = "slice".length / 3 = 5 / 3 = 1
    maxEditDistance = "itermut".length / 3 = 7 / 3 = 2
    editDistance("std", "slice") = 4
        4 <= 1 FAIL

Effectively, this makes path queries less "typo-resistant". It's not zero, but it means `vec` won't match the `v1` prelude.

This commit also adds substring matching to paths. It's stricter than the substring matching in the main part, but loose enough that what I expect to match does.

Queries without parent paths are unchanged.
2023-12-28 18:48:00 +01:00
Michael Howell
0ea58e2346 rustdoc-search: count path edits with separate edit limit
Since the two are counted separately elsewhere, they should get
their own limits, too. The biggest problem with combining them
is that paths are loosely checked by not requiring every component
to match, which means that if they are short and matched loosely,
they can easily find "drunk typist" matches that make no sense,
like this old result:

    std::collections::btree_map::itermut matching slice::itermut
    maxEditDistance = ("slice::itermut".length) / 3 = 14 / 3 = 4
    editDistance("std", "slice") = 4
    editDistance("itermut", "itermut") = 0
        4 + 0 <= 4 PASS

Of course, `slice::itermut` should not match stuff from btreemap.
`slice` should not match `std`.

The new result counts them separately:

    maxPathEditDistance = "slice".length / 3 = 5 / 3 = 1
    maxEditDistance = "itermut".length / 3 = 7 / 3 = 2
    editDistance("std", "slice") = 4
        4 <= 1 FAIL

Effectively, this makes path queries less "typo-resistant".
It's not zero, but it means `vec` won't match the `v1` prelude.

Queries without parent paths are unchanged.
2023-12-26 18:46:17 -07:00
Michael Howell
624885d242 rustdoc: treat query string + as space
Fixes #119219
2023-12-26 14:14:28 -07:00
Guillaume Gomez
279b11c4b5 Fix display of warning block if it is first element of the top doc block 2023-12-24 19:23:42 +01:00
Michael Howell
9566db1e49 Fix corner cases when dealing with mobile mode 2023-12-19 19:50:53 -07:00
Michael Howell
34984a6830 Use folder icon instead of hamburger and sidebar 2023-12-18 18:22:35 -07:00
Michael Howell
66779ff606 Add border bottom for source sidebar title area 2023-12-18 16:00:57 -07:00
Michael Howell
c3e29ea5e9 Stop using the trigram of heaven as a hamburger button
It doesn't look quite right, because the lines are too far apart,
and it's not going to be announced by screenreaders as a menu button,
since that's not what the symbol means.

This adds a real tooltip and uses a better drawing of the icon.
2023-12-18 13:56:55 -07:00
Michael Howell
bd14fb68da Add back the column 2023-12-18 12:42:24 -07:00
Michael Howell
859bbc5def rustdoc: clean up source sidebar hide button
This is a redesign of the feature, with parts pulled from
https://github.com/rust-lang/rust/pull/119049
but with a button that looks more like a button and matches the
one used on other sidebar pages.
2023-12-17 23:06:31 -07:00
bors
2f19122f73 Auto merge of #119001 - notriddle:notriddle/searchwords, r=GuillaumeGomez
rustdoc-search: remove parallel searchWords array

This might have made sense if the algorithm could use `searchWords` to skip having to look at `searchIndex`, but since it always does a substring check on both the stock word and the normalizedName, it doesn't seem to help performance anyway.

Profile: http://notriddle.com/rustdoc-html-demo-8/searchwords/index.html
2023-12-17 06:20:49 +00:00
Michael Howell
6b69ebcae0 rustdoc-search: remove parallel searchWords array
This might have made sense if the algorithm could use `searchWords`
to skip having to look at `searchIndex`, but since it always
does a substring check on both the stock word and the normalizedName,
it doesn't seem to help performance anyway.
2023-12-15 16:26:35 -07:00
Matthias Krüger
ae9e08e65e
Rollup merge of #118977 - GuillaumeGomez:simplifysrc-script, r=notriddle
Simplify `src-script.js` code

Instead of keeping this value in the global scope and still use it in the function in case it wasn't used outside, let's just use it inside the function.

r? ``@notriddle``
2023-12-15 20:19:54 +01:00
Guillaume Gomez
552143c875 Simplify src-script.js code 2023-12-15 12:26:09 +01:00
Guillaume Gomez
f8b92697a1
Rollup merge of #115660 - notriddle:notriddle/sidebar-resize, r=GuillaumeGomez
rustdoc: allow resizing the sidebar / hiding the top bar

Fixes #97306

Preview: http://notriddle.com/rustdoc-html-demo-4/sidebar-resize/std/index.html

![image](https://github.com/rust-lang/rust/assets/1593513/a2f40ea2-0436-4e44-99e8-d160dab2a680)

## Summary

This feature adds:

1. A checkbox to the Settings popover to hide the persistent navigation bar (the sidebar on large viewports and the top bar on small ones).
2. On large viewports, it adds a resize handle to the persistent sidebar. Resizing it into nothing is equivalent to turning off the persistent navigation bar checkbox in Settings.
3. If the navigation bar is hidden, a toolbar button to the left of the search appears. Clicking it brings the navigation bar back.

## Motivation

While "mobile mode" is definitely a good default, it's not the only reason people have wanted to hide the sidebar:

* Some people use tiling window managers, and don't like rustdoc's current breakpoints. Changing the breakpoints might help with that, but there's no perfect solution, because there's a gap between "huge screen" and "smartphone" where reasonable people can disagree about whether it makes sense for the sidebar to be on-screen. https://github.com/rust-lang/rust/issues/97306

* Some people ask for ways to reduce on-screen clutter because it makes it easier to focus. There's not a media query for that (and if there was, privacy-conscious users would turn it off). https://github.com/rust-lang/rust/issues/59829

This feature is designed to avoid these problems. Resizing the sidebar especially helps, because it provides a way to hide the sidebar without adding a new top-level button (which would add clutter), and it provides a way to make rustdoc play nicer in complex, custom screen layouts.

## Guide and Reference-level explanation

On a desktop or laptop with a mouse, resize the sidebar by dragging its right edge.

On any browser, including mobile phones, the sticky top bar or side bar can be hidden from the Settings area (the button with the cog wheel, next to the search bar). When it's hidden, a convenient button will appear on the search bar's left.

## Drawbacks

This adds more JavaScript code to the render blocking area.

## Rationale and alternatives

The most obvious way to allow people to hide the sidebar would have been to let them "manually enter mobile mode." The upside is that it's a feature we already have. The downside is that it's actually really hard to come up with a terse description. Is it:

* A Setting that forces desktop viewers to always have the mobile-style top bar? If so, how do we label it? Should it be visible on mobile, and, if so, does it just not do anything?
* A persistent hide/show sidebar button, present on desktop, just like on mobile? That's clutter that I'd like to avoid.

## Prior art

* The new file browser in GitHub uses a similar divider with a mouse-over indicator
* mdBook and macOS Finder both allow you to resize the sidebar to nothing as a gesture to hide it
* https://www.nngroup.com/articles/drag-drop/

## Future possibilities

https://rust-lang.zulipchat.com/#narrow/stream/266220-rustdoc/topic/Table.20of.20contents proposes a new, second sidebar (a table of contents). How should it fit in with this feature? Should it be resizeable? Hideable? Can it be accessed on mobile?
2023-12-15 11:51:23 +01:00
Guillaume Gomez
fc7221689e Use Map instead of Object for source files and search index 2023-12-14 13:33:26 +01:00
Michael Howell
bec6672984 rustdoc-search: clean up handleSingleArg type handling 2023-12-13 10:37:52 -07:00
Michael Howell
9dfcf131b3 rustdoc-search: better hashing, faster unification
The hash changes are based on some tests with `arti` and various
specific queries, aimed at reducing the false positive rate.

Sorting the query elements so that generics always come first is
instead aimed at reducing the number of Map operations on mgens,
assuming if the bloom filter does find a false positive, it'll
be able to reject the row without having to track a mapping.

- https://hur.st/bloomfilter/?n=3&p=&m=96&k=6

  Different functions have different amounts of inputs, and
  unification isn't very slow anyway, so figuring out a single
  ideal number of hash functions is nasty, but 6 keeps things
  low even up to 10 inputs.

- https://web.archive.org/web/20210927123933/https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.2442&rep=rep1&type=pdf

  This is the `h1` and `h2`, both derived from `h0`.
2023-12-13 10:37:51 -07:00
Michael Howell
9a9695a052 rustdoc-search: use set ops for ranking and filtering
This commit adds ranking and quick filtering to type-based search,
improving performance and having it order results based on their
type signatures.

Motivation
----------

If I write a query like `str -> String`, a lot of functions come up.
That's to be expected, but `String::from_str` should come up on top, and
it doesn't right now. This is because the sorting algorithm is based
on the functions name, and doesn't consider the type signature at all.
`slice::join` even comes up above it!

To fix this, the sorting should take into account the function's
signature, and the closer match should come up on top.

Guide-level description
-----------------------

When searching by type signature, types with a "closer" match will
show up above types that match less precisely.

Reference-level explanation
---------------------------

Functions signature search works in three major phases:

* A compact "fingerprint," based on the [bloom filter] technique, is used to
  check for matches and to estimate the distance. It sometimes has false
  positive matches, but it also operates on 128 bit contiguous memory and
  requires no backtracking, so it performs a lot better than real
  unification.

  The fingerprint represents the set of items in the type signature, but it
  does not represent nesting, and it ignores when the same item appears more
  than once.

  The result is rejected if any query bits are absent in the function, or
  if the distance is higher than the current maximum and 200
  results have already been found.

* The second step performs unification. This is where nesting and true bag
  semantics are taken into account, and it has no false positives. It uses a
  recursive, backtracking algorithm.

  The result is rejected if any query elements are absent in the function.

[bloom filter]: https://en.wikipedia.org/wiki/Bloom_filter

Drawbacks
---------

This makes the code bigger.

More than that, this design is a subtle trade-off. It makes the cases I've
tested against measurably faster, but it's not clear how well this extends
to other crates with potentially more functions and fewer types.

The more complex things get, the more important it is to gather a good set
of data to test with (this is arguably more important than the actual
benchmarking ifrastructure right now).

Rationale and alternatives
--------------------------

Throwing a bloom filter in front makes it faster.

More than that, it tries to take a tactic where the system can not only check
for potential matches, but also gets an accurate distance function without
needing to do unification. That way it can skip unification even on items
that have the needed elems, as long as they have more items than the
currently found maximum.

If I didn't want to be able to cheaply do set operations on the fingerprint,
a [cuckoo filter] is supposed to have better performance.
But the nice bit-banging set intersection doesn't work AFAIK.

I also looked into [minhashing], but since it's actually an unbiased
estimate of the similarity coefficient, I'm not sure how it could be used
to skip unification (I wouldn't know if the estimate was too low or
too high).

This function actually uses the number of distinct items as its
"distance function."
This should give the same results that it would have gotten from a Jaccard
Distance $1-\frac{|F\cap{}Q|}{|F\cup{}Q|}$, while being cheaper to compute.
This is because:

* The function $F$ must be a superset of the query $Q$, so their union is
  just $F$ and the intersection is $Q$ and it can be reduced to
  $1-\frac{|Q|}{|F|}.

* There are no magic thresholds. These values are only being used to
  compare against each other while sorting (and, if 200 results are found,
  to compare with the maximum match). This means we only care if one value
  is bigger than the other, not what it's actual value is, and since $Q$ is
  the same for everything, it can be safely left out, reducing the formula
  to $1-\frac{1}{|F|} = \frac{|F|}{|F|}-\frac{1}{|F|} = |F|-1$. And, since
  the values are only being compared with each other, $|F|$ is fine.

Prior art
---------

This is significantly different from how Hoogle does it.
It doesn't account for order, and it has no special account for nesting,
though `Box<t>` is still two items, while `t` is only one.

This should give the same results that it would have gotten from a Jaccard
Distance $1-\frac{|A\cap{}B|}{|A\cup{}B|}$, while being cheaper to compute.

Unresolved questions
--------------------

`[]` and `()`, the slice/array and tuple/union operators, are ignored while
building the signature for the query. This is because they match more than
one thing, making them ambiguous. Unfortunately, this also makes them
a performance cliff. Is this likely to be a problem?

Right now, the system just stashes the type distance into the
same field that levenshtein distance normally goes in. This means exact
query matches show up on top (for example, if you have a function like
`fn nothing(a: Nothing, b: i32)`, then searching for `nothing` will show it
on top even if there's another function with `fn bar(x: Nothing)` that's
technically a closer match in type signature.

Future possibilities
--------------------

It should be possible to adopt more sorting criteria to act as a tie breaker,
which could be determined during unification.

[cuckoo filter]: https://en.wikipedia.org/wiki/Cuckoo_filter
[minhashing]: https://en.wikipedia.org/wiki/MinHash
2023-12-13 10:37:15 -07:00
Michael Howell
fd1d256d61 rustdoc-search: remove the now-redundant validateResult
This function dates back to 9a45c9d7c6 and
seems to have been made obsolete when `addIntoResult` grew the ability to
check the levenshtein distance matching with commit
ba824ec52b.
2023-12-13 10:35:36 -07:00
Jubilee
2f937c720d
Rollup merge of #118886 - GuillaumeGomez:clean-up-search-vars, r=notriddle
Clean up variables in `search.js`

While reviewing https://github.com/rust-lang/rust/pull/118402, I saw a few small clean ups that were needed, mostly about variables creation.

r? ```@notriddle```
2023-12-12 18:48:53 -08:00
Guillaume Gomez
f1342f30a5 Clean up variables in search.js 2023-12-12 19:31:43 +01:00
Michael Howell
4f8083374d rustdoc-search: clean up parser
The `c === "="` was redundant when `isSeparatorCharacter` already
checks that.

The function `isStopCharacter` and `isEndCharacter` functions
did exactly the same thing and have synonymous names.
There doesn't seem much point in having both.
2023-12-11 22:24:44 -07:00
Michael Howell
7162cb9550 rustdoc-search: fix fast path unboxing bindings 2023-12-10 20:53:53 -07:00
Michael Howell
92b84f849a rustdoc-search: do not treat associated type names as types
Before: http://notriddle.com/rustdoc-html-demo-6/tor-before/tor_config/

After: http://notriddle.com/rustdoc-html-demo-6/tor-after/tor_config/

Profile: http://notriddle.com/rustdoc-html-demo-6/tor-profile/

As a bit of background information: in type-based queries, a type
name that does not exist gets treated as a generic type variable.

This causes a counterintuitive behavior in the `tor_config` crate,
which has a trait with an associated type variable called `T`.

This isn't a searchable concrete type, but its name still gets stored
in the typeNameIdMap, as a convenient way to intern its name.
2023-12-10 16:52:21 -07:00
Matthias Krüger
9dd34d5945
Rollup merge of #118722 - notriddle:notriddle/dom-opt-3, r=GuillaumeGomez
rustdoc: remove unused parameter `reversed` from onEach(Lazy)

This feature was added in edec5807ac to support JavaScript-based toggles that were later replaced with HTML `<details>`.
2023-12-08 06:44:44 +01:00
Michael Howell
6a0a89af80 rustdoc: remove unused parameter reversed from onEach(Lazy)
This feature was added in edec5807ac
to support JavaScript-based toggles that were later replaced with
HTML `<details>`.
2023-12-07 13:02:50 -07:00
Guillaume Gomez
bf681dcfb5 Fix display of features in rustdoc 2023-12-07 10:44:55 +01:00
Takayuki Maeda
a030add411
Rollup merge of #118483 - notriddle:notriddle/fmt-newline, r=GuillaumeGomez
rustdoc: `div.where` instead of fmt-newline class

This is about equally readable, a lot more terse, and stops special-casing functions and methods.

```console
$ du -hs doc-old/ doc-new/
671M    doc-old/
670M    doc-new/
```
2023-12-01 13:47:42 +09:00
Michael Howell
7230f6c5c5 rustdoc: div.where instead of fmt-newline class
This is about equally readable, a lot more terse, and stops
special-casing functions and methods.

```console
$ du -hs doc-old/ doc-new/
671M    doc-old/
670M    doc-new/
```
2023-11-30 10:43:40 -07:00
Matthias Krüger
49fadeef59
Rollup merge of #118458 - notriddle:notriddle/small-section-header, r=GuillaumeGomez
rustdoc: remove small from  `small-section-header`

There's no such thing as a big section header, so I don't know why the name was used.
2023-11-30 09:28:26 +01:00
Michael Howell
c910a49b05 rustdoc: remove small from small-section-header
There's no such thing as a big section header, so I don't know why the
name was used.
2023-11-29 13:40:07 -07:00
Michael Howell
930cba8061 rustdoc-search: replace TAB/NL/LF with SP first
This way, most of the parsing code doesn't need to be designed to handle
it, since they should always be treated exactly the same anyhow.
2023-11-29 11:02:56 -07:00
Michael Howell
93f17117ed rustdoc-search: removed dead parser code
This is already covered by the normal unexpected char path.
2023-11-29 11:02:56 -07:00
Michael Howell
c28de27a73 rustdoc-search: allow :: and ::
This restriction made sense back when spaces separated function
parameters, but now that they separate path components, there's
no real ambiguity any more.

Additionally, the Rust language allows it.
2023-11-29 11:02:50 -07:00
Matthias Krüger
828db2860f
Rollup merge of #118325 - clubby789:rustdoc-search-link, r=fmease
Fix Rustdoc search docs link

This link has been outdated since #112725 moved the search docs to their own page
2023-11-27 08:21:19 +01:00
clubby789
4d9344d26e Fix Rustdoc search docs link 2023-11-26 16:22:43 +00:00
Guillaume Gomez
bb5bbbf4ea
Rollup merge of #118296 - notriddle:notriddle/main-dom, r=GuillaumeGomez
rustdoc: replace `elemIsInParent` with `Node.contains`

According to [MDN], this function is compatible with:

* Chrome 16 and Edge 12
* Firefox 9
* Safari 1.1 and iOS Safari 1

These browsers are well within our [support matrix], which requires compatibility with Chrome 118, Firefox 115, Safari 17, and Edge 119.

[MDN]: https://developer.mozilla.org/en-US/docs/Web/API/Node/contains#browser_compatibility
[support matrix]: https://browsersl.ist/#q=last+2+Chrome+versions%2C+last+1+Firefox+version%2C+Firefox+ESR%2C+last+1+Safari+version%2C+last+1+iOS+version%2C+last+1+Edge+version%2C+last+1+UCAndroid+version
2023-11-26 15:44:53 +01:00
Michael Howell
bdcf91605c rustdoc: replace elemIsInParent with Node.contains
According to [MDN], this function is compatible with:

* Chrome 16 and Edge 12
* Firefox 9
* Safari 1.1 and iOS Safari 1

These browsers are well within our [support matrix], which requires
compatibility with Chrome 118, Firefox 115, Safari 17, and Edge 119.

[MDN]: https://developer.mozilla.org/en-US/docs/Web/API/Node/contains#browser_compatibility
[support matrix]: https://browsersl.ist/#q=last+2+Chrome+versions%2C+last+1+Firefox+version%2C+Firefox+ESR%2C+last+1+Safari+version%2C+last+1+iOS+version%2C+last+1+Edge+version%2C+last+1+UCAndroid+version
2023-11-25 12:33:04 -07:00
Michael Howell
884679ff63 rustdoc-search: clean up some DOM code 2023-11-25 10:39:45 -07:00
Michael Howell
1b7b9540fe rustdoc-search: avoid infinite where clause unbox
Fixes #118242
2023-11-24 10:42:11 -07:00
Michael Howell
28f17d97a9 rustdoc-search: make primitives and keywords less special
The search sorting code already sorts by item type discriminant,
putting things with smaller discriminants first. There was
also a special case for sorting keywords and primitives earlier,
and this commit removes it by giving them lower discriminants.

The sorting code has another criteria where items with descriptions
appear earlier than items without, and that criteria has higher
priority than the item type. This shouldn't matter, though,
because primitives and keywords normally only appear in the
standard library, and it always gives them descriptions.
2023-11-21 13:59:26 -07:00
Michael Howell
d82a08537a rustdoc-search: clean up checkPath
This computes the same result with less code by computing many of
the old checks at once:

* It won't enter the loop if clength > length, because then the
  result of length - clength will be negative and the
  loop conditional will fail.
* i + clength will never be greater than length, because it
  starts out as i = length - clength, implying that i + clength
  equals length, and it only goes down from there.
* The aborted variable is replaced with control flow.
2023-11-21 13:09:53 -07:00
Michael Howell
63c50712f4 rustdoc-search: add support for associated types 2023-11-19 18:54:36 -07:00
Michael Howell
d82b3748d5 rustdoc-search: switch to recursive backtracking
This is significantly faster, because

- It allows the one-element fast path to kick in on multi-
  element queries.
- It constructs intermediate data structures more lazily
  than the old system did.

It's measurably faster than the old algo even without the fast path, but
that fast path still helps significantly.
2023-11-18 16:12:43 -07:00
Michael Howell
a66972d551 rustdoc-search: fix accidental shared, mutable map 2023-11-17 18:22:31 -07:00
Michael Howell
c76c2e71f0 rustdoc-search: fast path for 1-query unification
Short queries, in addition to being common, are also the base
case for a lot of more complicated queries. We can avoid
most of the backtracking data structures, and use simple
recursive matching instead, by special casing them.

Profile output:
https://notriddle.com/rustdoc-html-demo-5/profile-3/index.html
2023-11-17 18:22:30 -07:00
Michael Howell
6d59452841 rustdoc-search: less new Maps in unifyFunctionType
This is a major source of expense on generic queries,
and this commit reduces them.

Profile output:
https://notriddle.com/rustdoc-html-demo-5/profile-2/index.html
2023-11-17 18:22:09 -07:00