-
-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Put negative implementors first and apply same ordering logic to foreign implementors #142380
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
base: main
Are you sure you want to change the base?
Conversation
|
Some changes occurred in HTML/CSS/JS. |
This comment has been minimized.
This comment has been minimized.
27e69ca to
2b0bda6
Compare
This comment has been minimized.
This comment has been minimized.
2b0bda6 to
2dd2db9
Compare
This comment has been minimized.
This comment has been minimized.
2dd2db9 to
cc15898
Compare
|
Took me a while to figure out where the typescript definition was stored to fix the error. ^^' |
| let part = OrderedJson::array_sorted( | ||
| implementors.sort_unstable(); | ||
|
|
||
| let part = OrderedJson::array_unsorted( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely an improvement, as it eliminates an allocation, but we're still allocating an intermediate string for each element.
OrderedJson could be much more efficient if it had a method that appended using serde_json::to_writer.
I can open an issue for improving the OrderedJson interface, if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An issue would be welcome indeed.
cc15898 to
80a06df
Compare
This comment has been minimized.
This comment has been minimized.
80a06df to
3028afb
Compare
alternate interface for OrderedJson to reduce allocations inspired by #142380 (comment) r? `@GuillaumeGomez` <!-- homu-ignore:start --> <!-- If this PR is related to an unstable feature or an otherwise tracked effort, please link to the relevant tracking issue here. If you don't know of a related tracking issue or there are none, feel free to ignore this. This PR will get automatically assigned to a reviewer. In case you would like a specific user to review your work, you can assign it to them by using r? <reviewer name> --> <!-- homu-ignore:end -->
|
☔ The latest upstream changes (presumably #142667) made this pull request unmergeable. Please resolve the merge conflicts. |
3028afb to
bc014e0
Compare
|
Fixed merge conflicts. |
|
Either JavaScript-loaded content needs to go on the bottom, or it needs to be loaded in a blocking manner. Otherwise, you get layout shift and potential misclicks. output.mp4 |
notriddle
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need a solution to the layout shift problem.
|
Fair enough. I can make the implementors content to be hidden by default (when JS is enabled) and display it when the updated is done. |
|
So now, the implementors lists are only displayed once they have been filled by the JS. I still display the headings though. I also opened the hosted example. Does that sound good to you @notriddle or do you have something else in mind? |
|
How does this work on a page like rust/tests/rustdoc-gui/trait-with-bounds.goml Lines 3 to 4 in e76a5eb
|
I wanted to put downstream foreign trait impls on the bottom, so we end up equivalent to But, in order of preference, if I have to pick between longer load time and more layout shift, I'll usually pick longer load time. But I'd rather go with an option that avoids both. |
But if we are hiding all the impls, then there would almost always be something that shows up no? |
|
The section are always present, whether they're empty or not. Currently, content appear (almost) instantly. If we add a visual marker that content is loading and once done, it just disappears because there was nothing to add (ie, no foreign implementors), then it would seem like something failed. |
It seems like you still don't understand what I'm saying. As stated before, one way to prevent layout shifting is to hide all implementations until the foreign impls are loaded. IF we do that, we should also add a loading icon, otherwise every page would initially look like they have no impls. If a loading indicator into nothing is too confusing, we could add a simple bit of text saying "no implementors" when none are found. (nojs users could even be shown "no local implementors" if we want to be technically correct). |
|
I got what you said. The current implementation of this PR hides all implementations until foreign ones are loaded. You suggest to add a visual indicator that it is loading and I answered to that. I don't think adding text to indicate the section is empty is very useful either. |
You're saying that the indicator would disappear with no changes if there are no foreign implementors, but if we are hiding all implementors, that is incorrect. Such a situation would only happen if there are no implementors at all. |
|
Yep that's my point. |
|
I think that "not done loading" and "no results" being indistinguishable is bad ui design, and is likely to cause frustration and/or confusion if the loading time is ever significant. |
|
☔ The latest upstream changes (presumably #138907) made this pull request unmergeable. Please resolve the merge conflicts. |
e76a5eb to
42134ec
Compare
This comment has been minimized.
This comment has been minimized.
|
So back to this: what do you think about going forward with the current implementation as it improves the current situation and discuss further improvements later on? |
|
☔ The latest upstream changes (presumably #148692) made this pull request unmergeable. Please resolve the merge conflicts. |
|
|
||
| /// Struct used to handle insertion of "negative impl" marker in the generated DOM. | ||
| /// | ||
| /// This marker appears once in all trait impl lists to divide negative impls from positive impls. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
| /// This marker appears once in all trait impl lists to divide negative impls from positive impls. | |
| /// This marker may appear at most once in every trait impl lists to divide negative impls from positive impls. |
It might not appear, if item has only negative impls, I think?
And "in all trailt impl lists" makes it sound like it's only gonna appear once on a single HTML page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, we put it all all the time so JS knows where the separation is.
| /// | ||
| /// This marker appears once in all trait impl lists to divide negative impls from positive impls. | ||
| struct NegativeMarker { | ||
| inserted_negative_marker: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
| inserted_negative_marker: bool, | |
| inserted: bool, |
no need to repeat what's being inserted :)
|
|
||
| fn insert_if_needed(&mut self, w: &mut fmt::Formatter<'_>, implementor: &Impl) -> fmt::Result { | ||
| if !self.inserted_negative_marker && !implementor.is_negative_trait_impl() { | ||
| write!(w, "<div class=\"negative-marker\"></div>")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| write!(w, "<div class=\"negative-marker\"></div>")?; | |
| w.write_str("<div class=\"negative-marker\"></div>")?; |
| #synthetic-implementors-list, #implementors-list { | ||
| /* To prevent layout shift when loading the page with extra implementors being loaded | ||
| from JS, we hide the list until it's complete. */ | ||
| display: none; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nicer to not use el.style.display = 'block' to override this, but rather use classes somehow.
Maybe:
| #synthetic-implementors-list, #implementors-list { | |
| /* To prevent layout shift when loading the page with extra implementors being loaded | |
| from JS, we hide the list until it's complete. */ | |
| display: none; | |
| } | |
| #synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) { | |
| /* To prevent layout shift when loading the page with extra implementors being loaded | |
| from JS, we hide the list until it's complete. */ | |
| display: none; | |
| } |
and then the "reset" to display: block in noscript.css can be removed (IIUC), and the JS will be something like:
if (implementors[0]) {
implementors[0].classList.add('loaded');
}
if (syntheticImplementors[0]) {
syntheticImplementors[0].classList.add('loaded');
}Your call, but I think it's slightly nicer this way. Also means we don't have to get the default value for display right :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can even add a loaded class on some ancestor element, and then do something like:
.some-parent:not(.loaded) #synthetic-implementors-list, .some-parent:not(.loaded) #implementors-list {
...
}it might come in handy for other "loaded" states we might add later on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather keep the "loaded" class close to the content which is actually loaded because I imagine we could have other async data load and I'd rather not have to handle that in the JS (checking if all data is loaded).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to precise: I think the loaded class is a good idea. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: we need to keep the noscript part because it will not have the .loaded class (thank you past-me for adding a test for this case T_T).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: we need to keep the
noscriptpart because it will not have the.loadedclass (thank you past-me for adding a test for this case T_T).
That's confusing.
Without the .loaded class, the display should default to block.
With noscript, there would be no JS to add that .loaded class in the first place, so I don't understand why we would need to override the display: none with display: block.
I'm probably missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e., this part of the noscript stylesheet is an "oxymoron":
https://github.com/rust-lang/rust/pull/142380/files#diff-6298483d735c3524d041be8e87a755ab53e75cf10dee016939929d3a2e620af5R32-R34
I don't see how it will ever be applicable, noscript and .loaded are two things that should never co-exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVM
understood it now
was completely confused there for a second
haha
sorry!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mind blown 😆
Took me a few seconds as well to understand what was going on when test failed because it seemed logical that noscript wasn't needed... until I realized. I feel less alone in this "brain misery" now haha
| } | ||
| } | ||
|
|
||
| impl PartialEq for Implementor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not great, at all, that the Eq and PartialEq impls don't behave the same.
I understand that all these impls are just so you're able to sort the implementors, but maybe it would be better to just use sort_unstable_by. Will also be less boilerplate-y, but most importantly more correct. IMHO, at least.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you're right. It was bugging me even when I wrote it so I'm glad it's a shared feeling. Using sort_unstable_by instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(it also allowed to remove a lot of code, so big 👍 for the idea)
42134ec to
01f8a22
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Implemented suggestions. |
This comment has been minimized.
This comment has been minimized.
… to show/hide implementors
01f8a22 to
fb9d3a5
Compare
|
Looks really good! All that's left is |
|
I'll leave it as is if you don't mind. No energy to squash any additional commit today. ^^' |
|
@bors r+ |
…=yotamofek Put negative implementors first and apply same ordering logic to foreign implementors Fixes rust-lang#51129. This PR changeda surprisingly small amount of things to put negative trait impls before the others: basically just adding a new information in the generated JS file for foreign implementors and a "negative marker" DOM element to know where to insert negative impls. I also used this occasion to make the foreign implementors sort the same as the local ones by using `compare_names`. You can test it [here](https://rustdoc.crud.net/imperio/neg-implementors/core/marker/trait.Sync.html#implementors). r? `@notriddle`
Fixes #51129.
This PR changeda surprisingly small amount of things to put negative trait impls before the others: basically just adding a new information in the generated JS file for foreign implementors and a "negative marker" DOM element to know where to insert negative impls.
I also used this occasion to make the foreign implementors sort the same as the local ones by using
compare_names.You can test it here.
r? @notriddle