Skip to content

[red-knot] Add basic on-hover to playground and LSP #17057

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

Merged
merged 4 commits into from
Apr 4, 2025
Merged

Conversation

MichaReiser
Copy link
Member

@MichaReiser MichaReiser commented Mar 29, 2025

Summary

Implement a very basic hover in the playground and LSP.

It's basic, because it only shows the type on-hover. Most other LSPs also show:

I decided to defer these features for now because it requires new semantic APIs (similar to goto definition), and investing in fancy rendering only makes sense once we have the relevant data.

Closes #16826

Test Plan

Screen.Recording.2025-04-03.at.11.28.59.mov
Screen.Recording.2025-04-03.at.11.43.50.mov

@MichaReiser MichaReiser added server Related to the LSP server ty Multi-file analysis & type inference labels Mar 29, 2025
Copy link
Contributor

github-actions bot commented Mar 29, 2025

mypy_primer results

No ecosystem changes detected ✅

@MichaReiser MichaReiser force-pushed the micha/playground-goto-type-definition branch from a950f23 to debd0c8 Compare April 1, 2025 11:39
@MichaReiser MichaReiser force-pushed the micha/hover branch 2 times, most recently from ca67635 to ce5a01f Compare April 1, 2025 16:19
@MichaReiser MichaReiser force-pushed the micha/playground-goto-type-definition branch 4 times, most recently from b9b659c to 68d506e Compare April 2, 2025 14:25
Base automatically changed from micha/playground-goto-type-definition to main April 2, 2025 14:35
@MichaReiser MichaReiser changed the title [red-knot] LSP and playground hover [red-knot] Add basic on-hover to playground and LSP Apr 3, 2025
@MichaReiser MichaReiser removed the server Related to the LSP server label Apr 3, 2025
Copy link
Contributor

github-actions bot commented Apr 3, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@MichaReiser MichaReiser marked this pull request as ready for review April 3, 2025 10:03
@dhruvmanila
Copy link
Member

dhruvmanila commented Apr 3, 2025

  • Do more fancy markdown rendering

I'm not sure if we need to do anything here as I think it's the client that will do the rendering, the server just need to make sure to use the MarkupKind::Markdown.

Edit: I think I know what you mean - it's more related to how we generate the markdown content similar to how currently we're using horizontal line to split between signature and (in the future) documentation?

Copy link
Member

@dhruvmanila dhruvmanila left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty straightforward to me!

I haven't looked at the playground code in detail, mainly glanced through it.

);

// TODO: Add documentation of the symbol (not the type's definition).
// TODO: Render the symbol's signature instead of just its type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like what Pyright is doing here as it's format is (<node type>) <name>: type or (<node type>) <signature>. I also like what rust-analyzer does which provides a hierarchy of where the symbol is coming from:

<import location>

<impl block>
<signature>
---
<documentation>

These are just something that I've noticed and there are no action items here except to take inspiration and iterate on the format of the hover content.

Comment on lines +52 to +68
impl<'db> IntoIterator for Hover<'db> {
type Item = HoverContent<'db>;
type IntoIter = std::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
self.contents.into_iter()
}
}

impl<'a, 'db> IntoIterator for &'a Hover<'db> {
type Item = &'a HoverContent<'db>;
type IntoIter = std::slice::Iter<'a, HoverContent<'db>>;

fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two looks the same except for only the IntoIter type, what's the reason for having these two implementations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is that one allows iterating when you have self and the other when you only have a &self

match self.content {
HoverContent::Type(ty) => self
.kind
.fenced_code_block(ty.display(self.db.upcast()), "text")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use "python" as the code block language here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first used python but the notation used by our type rendering isn't valid Python. For example, we have some types of the form <some text here>. This resulted in text here to be highlighted in red because it isn't valid python.

I think the long term solution here is to implement a type renderer that more closely matches valid Python syntax (or even renders a full signature).

Comment on lines -177 to +188
let token = parsed.tokens().at_offset(offset).find(|token| {
matches!(
token.kind(),
let token = parsed
.tokens()
.at_offset(offset)
.max_by_key(|token| match token.kind() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change to get the last matched token instead of the first matched token?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is to get the token with the highest priority and it was necessary to support hovering over the operator in a binary expression. Before, the logic filtered out any non Name or literal token, meaning that hovering + showed no type.

The logic now ranks the tokens, preferring names and literals. The "preferring" is important for if you have call<CURSOR>(; in this case, we want to get the call name and not ( (or .<CURSOR>arg) because it's more likely what the user wanted to hover / goto.

Copy link
Member

@dhruvmanila dhruvmanila Apr 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is to get the token with the highest priority and it was necessary to support hovering over the operator in a binary expression. Before, the logic filtered out any non Name or literal token, meaning that hovering + showed no type.

Interesting, so I think this would affect goto implementations as well. For example, in an addition expression like a + b, would we go to the type of a or b? Regardless, it's not a major issue.

@carljm carljm removed their request for review April 3, 2025 23:22
@MichaReiser MichaReiser merged commit a4ba10f into main Apr 4, 2025
23 checks passed
@MichaReiser MichaReiser deleted the micha/hover branch April 4, 2025 06:13
dcreager added a commit that referenced this pull request Apr 4, 2025
* main:
  [red-knot] Empty tuple is always-falsy (#17213)
  Run fuzzer with `--preview` (#17210)
  Bump 0.11.4 (#17212)
  [syntax-errors] Allow `yield` in base classes and annotations (#17206)
  Don't skip visiting non-tuple slice in `typing.Annotated` subscripts (#17201)
  [red-knot] mypy_primer: do not specify Python version (#17200)
  [red-knot] Add `Type.definition` method (#17153)
  Implement `Invalid rule provided` as rule RUF102 with `--fix` (#17138)
  [red-knot] Add basic on-hover to playground and LSP (#17057)
  [red-knot] don't remove negations when simplifying constrained typevars (#17189)
  [minor] Fix extra semicolon for clippy (#17188)
  [syntax-errors] Invalid syntax in annotations (#17101)
  [syntax-errors] Duplicate attributes in match class pattern (#17186)
  [syntax-errors] Fix multiple assignment for class keyword argument (#17184)
  use astral-sh/cargo-dist instead (#17187)
  Enable overindented docs lint (#17182)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[red-knot] Show type on hover
2 participants