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

Feat: LSP Type Hints #5934

Merged
merged 21 commits into from
Mar 11, 2023
Merged

Conversation

poliorcetics
Copy link
Contributor

@poliorcetics poliorcetics commented Feb 12, 2023

Add support for LSP type hints (merged in version 3.17 of the spec, a few month old now).

For now, I only added it in the main editor views, pickers do not have type hints.
Even if we decide they need to, I would prefer doing it in another PR since this one is already big
enough as it is.

I'll join a screen in a comment below, and if people could try it out with their LSPs and configs, it
would be very useful, since I pretty much only use Rust-Analyzer so testing may be somewhat limited still.

Closes #2070

EDIT: instructions to test

@poliorcetics
Copy link
Contributor Author

poliorcetics commented Feb 12, 2023

Screenshot of helix with LSP inlay hints active in two different views of the same file

@Proful
Copy link

Proful commented Feb 12, 2023

Amazing!

image

@pascalkuthe pascalkuthe self-requested a review February 12, 2023 12:30
Copy link
Member

@pascalkuthe pascalkuthe left a comment

Choose a reason for hiding this comment

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

Nice to finally see the virtual text API put to use 🎉

Sadly I think you missunderstood the API somewhat and therefore had to do a bunch of unnecesassary changes. I designed the virtual text API very carefully (including with this usecase in mind) and the way you changed the API here makes it less efficent and less elegant.

The API is a bit abstract without an example and you are not the first person to be initially confused. I think the core of your misunderstanding is that you created a single layer (call to add_inline_annotations) per inlay a hint

That are a lot of layers (and not efficent since every layers must be checked at every singel char when transvcersing the document) and I therefore you probably thaught that you needed to store these somewhere.

Instead the idea is that one layer corresponds to a single TYPE of virtual text.
Therefore it is possible to construct these layers on the fly in the text_annotations functions and only store the contents of the layers. In your case there should only be the need for 4 layers. Constructing a Vec of 4 elements on the fly is basically free and is how the API was supposed to be used.

I left a bunch of comments with more details.

Once you apply my suggestion the diff should reduce significantly and this should turn into a smaller PR.

helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-core/src/text_annotations.rs Outdated Show resolved Hide resolved
helix-view/src/view.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Show resolved Hide resolved
helix-view/src/document.rs Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
helix-term/src/commands/lsp.rs Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
@pascalkuthe pascalkuthe added S-waiting-on-review Status: Awaiting review from a maintainer. A-language-server Area: Language server client E-medium Call for participation: Experience needed to fix: Medium / intermediate S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. C-enhancement Category: Improvements and removed S-waiting-on-review Status: Awaiting review from a maintainer. labels Feb 12, 2023
@poliorcetics
Copy link
Contributor Author

poliorcetics commented Feb 12, 2023

Sadly I think you missunderstood the API somewhat and therefore had to do a bunch of unnecesassary changes. I designed the virtual text API very carefully (including with this usecase in mind) and the way you changed the API here makes it less efficent and less elegant.

The API is a bit abstract without an example and you are not the first person to be initially confused. I think the core of your misunderstanding is that you created a single layer (call to add_inline_annotations) per inlay a hint

That are a lot of layers (and not efficent since every layers must be checked at every singel char when transvcersing the document) and I therefore you probably thaught that you needed to store these somewhere.

Yes, this is really confusing at first because there is pretty much zero doc in Helix about virtual text and annotations (or, if there is, it's absolutely not where I expect it to be, on the types and methods) so I was really confused, thanks for clarifying !

Instead the idea is that one layer corresponds to a single TYPE of virtual text.
Therefore it is possible to construct these layers on the fly in the text_annotations functions and only store the contents of the layers. In your case there should only be the need for 4 layers. Constructing a Vec of 4 elements on the fly is basically free and is how the API was supposed to be used.

Yes, that approach is much better, I used it instead. I can't separate padding from the hints though, else it breaks in all kinds of ways: everything is squished together and unreadable.

Once you apply my suggestion the diff should reduce significantly and this should turn into a smaller PR.

Not really 😅 I added quite some docs for inlay hints in documents that re-inflated the count after reducing it, but still the MR is better than it was before thanks to you


EDIT: I left a few comments open voluntarily, close them or answer as you want, for the others I either fixed the problem or changed the code too much to leave them as-is

helix-view/src/view.rs Outdated Show resolved Hide resolved
@pascalkuthe
Copy link
Member

Yes, that approach is much better, I used it instead. I can't separate padding from the hints though, else it breaks in all kinds of ways: everything is squished together and unreadable.

Huh that is odd, it shouldn't let me check.

Not really sweat_smile I added quite some docs for inlay hints in documents that re-inflated the count after reducing it, but still the MR is better than it was before thanks to you

Well you added quite a bit of docs and its still almost 25% smaller so I would call that a win :D

@pascalkuthe
Copy link
Member

Yes, that approach is much better, I used it instead. I can't separate padding from the hints though, else it breaks in all kinds of ways: everything is squished together and unreadable.

Huh that is odd, it shouldn't let me check.

Not really sweat_smile I added quite some docs for inlay hints in documents that re-inflated the count after reducing it, but still the MR is better than it was before thanks to you

Well you added quite a bit of docs and its still almost 25% smaller so I would call that a win :D

I just implemented this myself and it worked fine for me.
However for this to work the order of layer is important, since all these annotations will occur at the same position:

  • first add the left padding layer
  • the the actual label layers
  • then the right padding layer

let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
if let Some(data) = Rc::get_mut(annotations) {
for inline in data.iter_mut() {
inline.char_idx = changes.map_pos(inline.char_idx, Assoc::After);
Copy link
Member

Choose a reason for hiding this comment

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

Neither Assoc::After or Assoc::Before works well here that is because both of these were designed for ranges where the start is mapped with Assoc::Before and Assoc::After. However for inlay hints we want something different. Inlay hints are bound to a single point so when that point is removed when also want to delete the inlay hint. For that you need to add a new variant Assoc::Point which returns None in case the position which contains char_idx was removed/replaced.

For that to worth tough we need a way to remove InlineAnnotations so the equivalent for Vec::retain but for Rc<[T]> which is not possible without a custom reference counted type (which we probably don't want to add here).

I am considering just switching TextAnnotations back to using slices instead of Rc which it originally did. I changed it to make the API easier to use/remove lifetime issues that occured in #5340 but I think those issues were actually resolved by something else so maybe a slice is a better choice afterall.

I don't have more time to look into this right now but you could start with implementing Assoc::Delete and I will try to comeup with something in the next couple of days.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I want to do this in this PR. Nor that I am qualified to do it.

ChangeSet::map_pos() currently unconditionnally returns a usize, adding Assoc::Delete or Assoc::Point would need to change the return type to account for the deletion cleanly.

Instead I think this PR should either be blocked until map_pos and Assoc::... are better equipped or, if we don't want to block too long, make an issue for it and fix it later on since for now Assoc::After works well enough (it probably has strange edge cases at the end of files but apart from that it should be okay for a v1)

Copy link
Member

Choose a reason for hiding this comment

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

If you don't feel confident working on this that's fine. I will create a PR myself in the next couple days that that you can then depend on. It's not that big of a change so I dlunt it will block this PR for long.

I think it is a requirement for this because without properly handeling this some odd effects do occur. For example if you delete two lines 2xd then all inlay hints within those lines are moved to the start of the next line

Copy link
Member

Choose a reason for hiding this comment

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

I actually changed my mind on this. Mapping points doesn't work well in general. The right fix here would be to convert these points to ranges by expanding to the word at the specific position and then tracking that range (only removing it when empty just like we do for diagnostics).

I have a pretty good idea how to implement this but it's a bit more effort and I don't have the necessary time right now. In practice this not as large of a problem as I orginally tough because the type hints get refreshed relatively quickly (on idle timeout) so the artificats usually don't stick around for that long. So I think I can do the necessary refactors in a followup PR.

Copy link
Contributor Author

@poliorcetics poliorcetics left a comment

Choose a reason for hiding this comment

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

Fixed some of the comments, I'm gonna work on the bigger ones now

helix-view/src/view.rs Outdated Show resolved Hide resolved
helix-view/src/document.rs Outdated Show resolved Hide resolved
helix-term/src/ui/editor.rs Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
helix-term/src/commands/lsp.rs Outdated Show resolved Hide resolved
@lazytanuki
Copy link
Contributor

lazytanuki commented Feb 14, 2023

Hi and thanks for working on this!
Been testing for a couple hours and it's really nice.
I find that having the hints pop up in the function arguments while typing them can be a bit unnerving, as it shifts the cursor back and forth.
I think it would be nice to have a setting to disable inlay hints for the current function parameters, in insert mode a least. Or maybe in insert mode altogether ? Only on the current line ? What do others think ?

@poliorcetics
Copy link
Contributor Author

Those should probably be options. I agree that some of that sound nice, though I think implementing them in this MR would delay it too much: I would prefer having something working merged then refine it as user reports pop up :), that way we can both keep this MR relatively small (my time is sadly limited, the maintainers' time too) and account for lots of reports at once instead of just the people following this MR

Does that sound good to you ?

@univerz
Copy link

univerz commented Feb 15, 2023

I find that having the hints pop up in the function arguments while typing them can be a bit unnerving

+1. I didn't notice this problem while using vscode, so there is a way to do it "right".

@marcbull
Copy link

Thanks for the work on this part of Helix, this is one of the features I really want to use.

I find that having the hints pop up in the function arguments while typing them can be a bit unnerving, as it shifts the cursor back and forth.

If I compare it with VS code, the experience there is more pleasant because there is a delay after the last change until the LSP type hint is adjusted.

I don't think it would be good to disable the LSP type hint in the current line or completely in insert mode, because I personally like to check the result value as I type and it's a welcome hint to see if the result is an option or already a T, especially if you're new to a project or using a new API.

@archseer
Copy link
Member

archseer commented Mar 9, 2023

Hmm this isn't blocking but I've noticed that if I make a selection the code is selected but the hints aren't highlighted as a part of the selection. It's probably better this way, but do we get the same behaviour in VSCode and elsewhere?

@pascalkuthe
Copy link
Member

pascalkuthe commented Mar 9, 2023

Yeah vscode behaves the same and it's very tricky to change because we do selectionswith syntax highlights. If we applied those to virtual text than virtual text would have the full style of the text (so syntax highlight color, dagnowtic underlines...)

@KittyBorgX
Copy link

+1 This behaves similar to VSCode

@erasin
Copy link
Contributor

erasin commented Mar 9, 2023

I've been using this pr for a week or two, and I didn't realize the problem before.
There has a problem that the cursor position in insert mode is after the virtual text.

helix:

hx

vscode:

vs

@poliorcetics
Copy link
Contributor Author

I can’t do anything about that, all inlay hints in Helix will behave like that due to how they’re rendered, though I agree it isn’t the most agreable.

Writing a « dumb » fix here (offsetting by one) for type hints would break for arguments and hints at the end of a line

@pascalkuthe
Copy link
Member

I've been using this pr for a week or two, and I didn't realize the problem before. There has a problem that the cursor position in insert mode is after the virtual text.

helix:

hx

vscode:

vs

It's not perfect but yeah as @poliorcetics said that's kind of just how the cursor works in helix. In insertmode we increase the selection size by one and insert before the cursor block. If you use block cursors its much more clear why it works the way it does: The selection extends one character after the virtual text. Maybe if we switch to zero width cursors oneday that could be solved but i am not sure about that. I could also do some evil trickery here to sepcical case bar cursors but I would rather not do that.

@Lordxan
Copy link

Lordxan commented Mar 9, 2023

I've been using this pr for a week or two, and I didn't realize the problem before. There has a problem that the cursor position in insert mode is after the virtual text.
helix:
hx
vscode:
vs

It's not perfect but yeah as @poliorcetics said that's kind of just how the cursor works in helix. In insertmode we increase the selection size by one and insert before the cursor block. If you use block cursors its much more clear why it works the way it does: The selection extends one character after the virtual text. Maybe if we switch to zero width cursors oneday that could be solved but i am not sure about that. I could also do some evil trickery here to sepcical case bar cursors but I would rather not do that.

I actually would like to see an optional patch for that (sorry can't do it myself just started learning rust)

@erasin
Copy link
Contributor

erasin commented Mar 9, 2023

Since I am a non-ascii characters user, the block cursor is so unfriendly to Chinese characters that I hardly ever use it.

The fact that this problem was not realized before means that it does not affect me much. Hope to become a better experience in the future.

thanks

@poliorcetics
Copy link
Contributor Author

poliorcetics commented Mar 9, 2023

(I'm won't do it in this MR but here is a potential solution):

  • If the cursor is next to an inlay hint where the format is text<cursor><space> or <space><cursor><text>, we could display the cursor closer next to the text, adapting the position of the hint locally to obtain: text<cursor><hint><space> and <space><hint><cursor>text
  • In the case of a conflict (text<hint>text), we would just defaut to the current behaviour.

I'm not certain how much work it would be to do that, or if it's even possible, but if it works it would probably solve most of the problematic cases ?

EDIT: <space> means non-text, it can be a line-ending, a space, a tab ...

@archseer
Copy link
Member

I've noticed this issue too, but given that it's a new feature that's also off by default we can resolve this in a follow up.

A quick solution we could use: only display hints outside of insert mode. That would also address issues with the hints shifting the code around while you type. Not sure how desirable this would be, the hints are helpful when typing out method parameters. Though that's also resolved by signature help. It just seems to me that hints are more beneficial during reading the code (normal mode) than writing (insert mode).

@poliorcetics
Copy link
Contributor Author

only display hints outside of insert mode.

I don't know, things disappearing just before I start typing seems very jarring too.

It just seems to me that hints are more beneficial during reading the code (normal mode) than writing (insert mode).

I'm also reading code when I type some more, adding code is not done in a vacuum

If we do this it should be an option, because it seems to me to be very much useless to only have type hints outside of insert mode

@jquesada2016
Copy link

only display hints outside of insert mode.

I personally think this is a really bad idea. For example, when you're chaining method calls, it's often hard to remember what types the method will return, and being able to see it while in insert mode is very helpful.

@erasin
Copy link
Contributor

erasin commented Mar 10, 2023

Unlike #6059, type hints is very useful in insert mode. We do not need additional option.

@archseer
Copy link
Member

Agreed, it was just an idea.

@archseer archseer merged commit bdcd4d9 into helix-editor:master Mar 11, 2023
@evanrichter
Copy link
Contributor

so hyped for all the fantastic virtual text features coming down the pipeline. the next release is going to be off the charts! thanks to the maintainers and everyone involved <3

@Hywan
Copy link

Hywan commented Mar 11, 2023

Awesome!

@eulerdisk
Copy link

I think inlay-hints theme text style should fallback to the comment style.
Now a lot of theme are "broken", inlay hints for types are like user written types.

sagnibak pushed a commit to sagnibak/helix that referenced this pull request Mar 21, 2023
* misc: missing inline, outdated link

* doc: Add new theme keys and config option to book

* fix: don't panic in Tree::try_get(view_id)

Necessary for later, where we could be receiving an LSP response
for a closed window, in which case we don't want to crash while
checking for its existence

* fix: reset idle timer on all mouse events

* refacto: Introduce Overlay::new and InlineAnnotation::new

* refacto: extract make_job_callback from Context::callback

* feat: add LSP display_inlay_hint option to config

* feat: communicate inlay hints support capabilities of helix to LSP server

* feat: Add function to request range of inlay hint from LSP

* feat: Save inlay hints in document, per view

* feat: Update inlay hints on document changes

* feat: Compute inlay hints on idle timeout

* nit: Add todo's about inlay hints for later

* fix: compute text annotations for current view in view.rs, not document.rs

* doc: Improve Document::text_annotations() description

* nit: getters don't use 'get_' in front

* fix: Drop inlay hints annotations on config refresh if necessary

* fix: padding theming for LSP inlay hints

* fix: tracking of outdated inlay hints should not be dependant on document revision (because of undos and such)

* fix: follow LSP spec and don't highlight padding as virtual text

* config: add some LSP inlay hint configs
@yuyidegit yuyidegit mentioned this pull request Mar 24, 2023
wes-adams pushed a commit to wes-adams/helix that referenced this pull request Jul 4, 2023
* misc: missing inline, outdated link

* doc: Add new theme keys and config option to book

* fix: don't panic in Tree::try_get(view_id)

Necessary for later, where we could be receiving an LSP response
for a closed window, in which case we don't want to crash while
checking for its existence

* fix: reset idle timer on all mouse events

* refacto: Introduce Overlay::new and InlineAnnotation::new

* refacto: extract make_job_callback from Context::callback

* feat: add LSP display_inlay_hint option to config

* feat: communicate inlay hints support capabilities of helix to LSP server

* feat: Add function to request range of inlay hint from LSP

* feat: Save inlay hints in document, per view

* feat: Update inlay hints on document changes

* feat: Compute inlay hints on idle timeout

* nit: Add todo's about inlay hints for later

* fix: compute text annotations for current view in view.rs, not document.rs

* doc: Improve Document::text_annotations() description

* nit: getters don't use 'get_' in front

* fix: Drop inlay hints annotations on config refresh if necessary

* fix: padding theming for LSP inlay hints

* fix: tracking of outdated inlay hints should not be dependant on document revision (because of undos and such)

* fix: follow LSP spec and don't highlight padding as virtual text

* config: add some LSP inlay hint configs
@cidit
Copy link

cidit commented Dec 25, 2023

do i need to use helix manually compiled on main to use this feature ?

@gabydd
Copy link
Member

gabydd commented Dec 25, 2023

No this was released in 23.05 I believe, you just need to enable it in your config. And for some languages you might need to enable it on the actual language server.

@poliorcetics poliorcetics deleted the 2070-lsp-type-hints branch February 12, 2024 22:11
jbtrystram added a commit to jbtrystram/helix-themes that referenced this pull request Jul 10, 2024
Since helix-editor/helix#5934 landed
in 23.05 helix supports hints from the LSP.

This PR change the hints colors to be the same as comments
because otherwise they show the same colour as code which is
confusing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-language-server Area: Language server client C-enhancement Category: Improvements E-medium Call for participation: Experience needed to fix: Medium / intermediate S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LSP Type Hints