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

Add basic support for LSP snippets #5864

Merged
merged 12 commits into from Mar 8, 2023

Conversation

Urgau
Copy link
Contributor

@Urgau Urgau commented Feb 7, 2023

This PR adds a basic support for LSP completion snippet based on https://github.com/helix-editor/helix/tree/snippets.

A snippet is a bit different from a normal completion in that it contains tab stops/placeholders (ie $1, $2 or ${3:foo} and $0 who is always the last one). A complete definition of the syntax can be found [here(https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax). So when creating the transaction we parse the snippet input, create the output text with the correct indentation and create the selections for the placeholders.

This PR brings the support for snippet in a good state where only variable, placeholder removal and Tab/S-Tab support isn't implemented for the moment, those should be feasible but I didn't wanted to do them in this PR.

I've tested multiple snippet with rust-analyzer, I recommend the macro_rules snippet for testing as it uses multiple tab stops and is multi line.

Copy link
Contributor

@filipdutescu filipdutescu left a comment

Choose a reason for hiding this comment

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

Hey there, thanks a lot for submitting this PR, I really miss snippets at the moment, but it always seemed like a hard thing to implement. I hope you don't mind me leaving a comment, thought it might be of interest.

helix-lsp/src/lib.rs Outdated Show resolved Hide resolved
@archseer
Copy link
Member

archseer commented Feb 7, 2023

Note: we have a full snippet parser here: https://github.com/helix-editor/helix/tree/snippets It's just not tied into the LSP stack yet.

@Urgau
Copy link
Contributor Author

Urgau commented Feb 7, 2023

Note: we have a full snippet parser here: https://github.com/helix-editor/helix/tree/snippets It's just not tied into the LSP stack yet.

Do you want me to close the PR and wait or the full snippet parser to land ?

@archseer
Copy link
Member

archseer commented Feb 7, 2023

You could base your PR on top of that branch and use the snippet parser rather than a custom parser. It would be a good enough stopgap until full snippet support is developed

@archseer
Copy link
Member

archseer commented Feb 7, 2023

Actually, @the-mikedavis looks like your branch already matches this PR's behaviour? (apart from jumping to the first tabstop and correctly indenting)

@Urgau
Copy link
Contributor Author

Urgau commented Feb 7, 2023

Actually, @the-mikedavis looks like your branch already matches this PR's behaviour? (apart from jumping to the first tabstop and correctly indenting)

I think it already jumps to the first tab stop here. As for indentation, I can add it.

@Urgau
Copy link
Contributor Author

Urgau commented Feb 8, 2023

I have now rebased this PR with the changes from @the-mikedavis. 🥳

On top of the rebase I added the auto-indentation, sorting and merging of tabstops, removed the need for regex when parsing the snippets and added the deletion of the placeholder when inserting a character (edit: removed for now as it wasn't right).

The support for snippet is now in a good state where only variable and Tab/S-Tab support isn't implemented.

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.

The changes in 1c548d8 heavily regress many core editing operating in helix like appendmode (and also handle other details incorrectly like using byte indexing instead of char indexing). You should not be changing the core editing operations. Instead the placeholders should be stored on the Document and specical cased in apply_impl.

helix-lsp/src/snippet.rs Outdated Show resolved Hide resolved
@pascalkuthe pascalkuthe added C-enhancement Category: Improvements A-language-server Area: Language server client E-hard Call for participation: Experience needed to fix: Hard / a lot S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 8, 2023
@Urgau Urgau force-pushed the lsp-snippet-support branch 2 times, most recently from b2cef33 to d885ca7 Compare February 8, 2023 11:25
@Urgau
Copy link
Contributor Author

Urgau commented Feb 8, 2023

The changes in 1c548d8 heavily regress many core editing operating in helix like appendmode (and also handle other details incorrectly like using byte indexing instead of char indexing). You should not be changing the core editing operations. Instead the placeholders should be stored on the Document and specical cased in apply_impl.

My bad, I didn't test it enough. I'm still new to the code base or even helix. I will try to do the special cased as said this week. For the moment, I've removed the offending commit.

@archseer
Copy link
Member

archseer commented Feb 9, 2023

I'd implement this the simplest way possible for now (jump to first tabstop and drop all the snippet state). The idea is to use marks in the future (#3720) to keep track of each tabstop locations even as text edits occur.

@Urgau
Copy link
Contributor Author

Urgau commented Feb 9, 2023

The pull request already jumps the cursor to the first tabstop. So I think it's ready for review.

The idea is to use marks in the future (#3720) to keep track of each tabstop locations even as text edits occur.

That seems exactly what tabstops should be map to. Looking forward to see this being merged.

@pascalkuthe
Copy link
Member

The pull request already jumps the cursor to the first tabstop. So I think it's ready for review.

The idea is to use marks in the future (#3720) to keep track of each tabstop locations even as text edits occur.

That seems exactly what tabstops should be map to. Looking forward to see this being merged.

I think removing the placeholder text for the first tabstop would be required for a MVP, deleting that would be quite annoying. Maybe if we want to keep this PR really simple you could simply remove the placeholder for the first tabstop by generating a second transaction that deletes it and apply it immedietly. That would probably be the easiest workaround and just take 5 lines of code.

@David-Else
Copy link
Contributor

David-Else commented Feb 9, 2023

I tested this and it unlocks all the basic features of the HTML language server that didn't work before. You now get auto suggestions and auto tag completion :) I remember it was an issue in Neovim that you needed a special snippet plugin to solve. This PR adds a lot of value as it is.

helix-lsp/src/snippet.rs Outdated Show resolved Hide resolved
Cargo.toml Show resolved Hide resolved
helix-lsp/src/snippet.rs Outdated Show resolved Hide resolved
@H4ckint0sh
Copy link

I tested this and it unlocks all the basic features of the HTML language server that didn't work before. You now get auto suggestions and auto tag completion :) I remember it was an issue in Neovim that you needed a special snippet plugin to solve. This PR adds a lot of value as it is.

@David-Else Do I need to do some additional steps beside merging this PR to get auto suggestion and auto completion working? Cuz I have build from src merging this PR but it seems that those are not present.

@David-Else
Copy link
Contributor

David-Else commented Feb 10, 2023

@H4ckint0sh I assume you have:

Configured language server: vscode-html-language-server
Binary for language server: /usr/local/bin/vscode-html-language-server

I just switched to this PR using GitHub CLI, rebuilt Helix and it worked right away. You need to be in a valid HTML file for it to work, here is what I was using to test:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>Make another div, you should get suggestions for span, div etc, then start to close it and it suggests the right closing tag</div>
  </body>
</html>

@Urgau
Copy link
Contributor Author

Urgau commented Feb 10, 2023

@pascalkuthe Here's what I hacked for deleting the first tabstop, I had to do a bit of range manipulation since the selection was too big at the right and was eating the next character. Do you have a better things in mind or should I push this ?

@archseer
Copy link
Member

Seems a bit odd that you would have to modify the ranges after the fact, it's probably because the returned ranges are end inclusive?

@archseer archseer added this to the next milestone Mar 3, 2023
@archseer
Copy link
Member

archseer commented Mar 3, 2023

Hmm that branch is also affected by #5728

Should I merge that first and we can rebase this PR on top?

@archseer
Copy link
Member

archseer commented Mar 3, 2023

Or we could do it the other way around (\cc @pascalkuthe)

@pascalkuthe
Copy link
Member

@archseer I think it's probably better to merge this PR first since some of the fixes from #5728 need to be applied to the logic from this PR (see #5864 (comment)). It's probably easier if I just do that in #5728 myself

Copy link
Member

@the-mikedavis the-mikedavis left a comment

Choose a reason for hiding this comment

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

This looks good 🚀

I left some minor comments

helix-lsp/src/lib.rs Outdated Show resolved Hide resolved
helix-lsp/src/lib.rs Outdated Show resolved Hide resolved
helix-lsp/src/snippet.rs Outdated Show resolved Hide resolved
helix-lsp/src/snippet.rs Outdated Show resolved Hide resolved
Urgau and others added 6 commits March 3, 2023 17:33
When accepting a snippet completion we automatically delete the
placeholders for now as doing so manual is quite cumbersome. In the
future we should keep these as a mark + virtual text that is
automatically removed once the cursor moves there.
This refactors the snippet logic to be largely unaware of the rest of
the document. The completion application logic is moved into
generate_transaction_from_snippet which is extended to support
dynamically computing replacement text.
@the-mikedavis the-mikedavis added S-waiting-on-review Status: Awaiting review from a maintainer. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 3, 2023
@leonqadirie
Copy link
Contributor

leonqadirie commented Mar 6, 2023

@leonqadirie: the fix is in Urgau#6

Thank you very much for the fix/improvement!
Indentation for ElixirLS' do completion is still a bit off on my end but this might be unrelated.

@andriigrynenko
Copy link
Contributor

Thank you very much for the fix/improvement! Indentation for ElixirLS' do completion is still a bit off on my end but this might be unrelated.

Thank you for flagging this! At first it seemed like an easy issue to fix, but I ended up uncovering some more subtle bugs in the current implementation. Urgau#7 is the fix.

Copy link
Member

@archseer archseer left a comment

Choose a reason for hiding this comment

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

We'll postpone Urgau#7 until this PR + Pascal's PR land.

I wanted to say, thank you @Urgau and @andriigrynenko for working on this! It's great to see basic snippet expansion finally land :)

@AddictArts
Copy link

Apologies, I've read this through more than once and my queston isn't answered, thus I'll ask.

How do you test macro-rules if Tab and S-Tab are not implemented? It is hard to see the feature actually working. The news video doesn't highlight or show what actually is the snippet when completing std::fs::create_dir_all(.

I think the idea is it hits the first tab stop path. Then that is it; However @Urgau mentions using macro_rules, I'm guessing it has multiple tab stops, but you can't actually more through them. I think the issue here is from the video I can't tell what is different.

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-hard Call for participation: Experience needed to fix: Hard / a lot S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Auto completing trait functions do not generate closing curly brace.