LuaSnip icon indicating copy to clipboard operation
LuaSnip copied to clipboard

Support for rust-analyzer's SnippetTextEdits

Open augustocdias opened this issue 4 years ago • 40 comments

As requested for vim-snip (hrsh7th/vim-vsnip#225), I think it should be nice to be supported in LuaSnip as well...

Reference: https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#snippet-textedit Reference: hrsh7th/nvim-cmp#353 (comment) Reference: simrat39/rust-tools.nvim#74

augustocdias avatar Dec 02 '21 12:12 augustocdias

What exactly would have to be added to luasnip to enable snippet-textedits? We have the capability to expand lsp-style snippets at the current cursor position via luasnip.lsp_expand(snippet_body), is that enough?

L3MON4D3 avatar Dec 02 '21 13:12 L3MON4D3

@simrat39 would that be enough to add this to rust-tools?

augustocdias avatar Dec 02 '21 13:12 augustocdias

umm not really, rust analyzer's snippet text edits are more like normal lsp text edits, they send a location and stuff as well, but I afaik most snippet plugins only support snippet expansion at the current cursor position, so it wouldn't work. It would need a completely separate implementation of TextEdits.

simrat39 avatar Dec 02 '21 22:12 simrat39

Expanding at any position shouldn't be a problem, I could add that. I wouldn't implement lsp-communication in luasnip, but that part could be handled by rust-tools?

L3MON4D3 avatar Dec 03 '21 07:12 L3MON4D3

I believe that would be the end goal. Providing the API to call from lsp handlers.

augustocdias avatar Dec 03 '21 15:12 augustocdias

@L3MON4D3 yes the lsp communication will be handled by rust-tools. We override vim.lsp.utils.apply_text_edit, not ideal but it works so it's whatever, just need the snippet plugin to apply the snippets

simrat39 avatar Dec 03 '21 16:12 simrat39

I added functionality to expand lsp-snippets at any position, check here. Does that work for you?

L3MON4D3 avatar Dec 04 '21 22:12 L3MON4D3

Just realized that link doesn't actually get you anywhere, just scroll all the way down and look for lsp_expand

L3MON4D3 avatar Dec 04 '21 23:12 L3MON4D3

i'll play around with it, thanks for the support

simrat39 avatar Dec 04 '21 23:12 simrat39

This works mostly, but one thing missing is that TextEdits give back a range, which can be used for example replacing multiple lines of text with something else, idk if it's in the scope of this plugin to support that but we would need that.

This is how neovim does it https://github.com/neovim/neovim/blob/master/runtime/lua/vim/lsp/util.lua#L329

simrat39 avatar Dec 04 '21 23:12 simrat39

Ah, no I wouldn't like to implement that in Luasnip, srry.

L3MON4D3 avatar Dec 05 '21 07:12 L3MON4D3

that's fair, i'll try to see what I can do on rust-tools' side then

simrat39 avatar Dec 05 '21 18:12 simrat39

Hi @simrat39, is there a follow-up to this? I found simrat39/rust-tools.nvim#74 which is closed, does it mean that you won't be able to support the feature?

P.S. I love the amount of quality profile pics in this issue

anstadnik avatar Sep 04 '22 07:09 anstadnik

Ah, no I wouldn't like to implement that in Luasnip, srry.

I'm going to backtrack on my stance here, it doesn't make sense to require each plugin that wants to utilize luasnip for snippetTextEdits to write their own conversion from snippetTextEdit to luasnip-expand-call.

I'll add an implementation for applying a single snippetTextEdit, would that be enough @simrat39? (Applying textEdits interleaved with snippetTextEdits seems pretty complicated, is that required?)

L3MON4D3 avatar Sep 04 '22 09:09 L3MON4D3

(Taking a closer look, the main problem with applying multiple textEdits is that applying them in any order may cause the lines in the textEdit to no longer correspond to the correct position, but I think I know how to make that work properly)

L3MON4D3 avatar Sep 04 '22 09:09 L3MON4D3

Implemented a first version in branch snippet_text_edits, call as require("luasnip.extras.lsp").apply_text_edits(edits, bufnr, offset_encoding, apply_text_edit_func) (with apply_text_edit_func probably vim.lsp.util.apply_text_edits)

L3MON4D3 avatar Sep 04 '22 11:09 L3MON4D3

Thanks for this, I'll check it out and point out issues if any! Would be nice if we finally get this into luasnip and rust-tools

simrat39 avatar Sep 05 '22 00:09 simrat39

Ok so this works pretty good, thanks for the work. One small issue though, for some reason rust-analyzer sends multiple snippet text edits in one call, even though their docs say that they won't do that, but the extra ones they sent all had empty newText, which is also weird. I think I'll bring this issue upstream and see what the rust-analyzer people say.

simrat39 avatar Sep 09 '22 00:09 simrat39

Btw i pushed a separate branch if anyone wants to test it out. https://github.com/simrat39/rust-tools.nvim/commit/95a6f90b4ba290fa07662a660286cde60cc35831

Use it like this:

rt.setup({
  tools = {
    snippet_func = function(edits, bufnr, offset_encoding, old_func)
      require("luasnip.extras.lsp").apply_text_edits(
        edits,
        bufnr,
        offset_encoding,
        old_func
      )
    end,
...

simrat39 avatar Sep 09 '22 00:09 simrat39

Ok so this works pretty good, thanks for the work.

Nice, you're welcome :D

One small issue though, for some reason rust-analyzer sends multiple snippet text edits in one call, even though their docs say that they won't do that, but the extra ones they sent all had empty newText, which is also weird.

Mhmmm that's annoying, I guess we could filter it here, but would be nicer to accommodate for languageserver-quirks in their respective plugins imo. Wdyt?

I think I'll bring this issue upstream and see what the rust-analyzer people say.

Sounds good👍

L3MON4D3 avatar Sep 09 '22 06:09 L3MON4D3

Oh we could also just handle multiple snippetTextEdits by inserting the into the jump list one after the other, and then jumping back into the first placeholder of the first snippet. The main problem then becomes determining the order of the snippets

L3MON4D3 avatar Sep 09 '22 06:09 L3MON4D3

Mhmmm that's annoying, I guess we could filter it here, but would be nicer to accommodate for languageserver-quirks in their respective plugins imo. Wdyt?

Yeah for sure, but I still think it should be clarified upstream first, their docs and their implementation should match.

simrat39 avatar Sep 09 '22 21:09 simrat39

Oh yeah, no question there :+1:

L3MON4D3 avatar Sep 10 '22 06:09 L3MON4D3

For the reference, the rust-analyzer's VSCode client implements applySnippetTextEdit.

https://github.com/rust-lang/rust-analyzer/blob/master/editors/code/src/snippets.ts#L38

hrsh7th avatar Oct 03 '22 13:10 hrsh7th

Oh, thank you for sharing! Looks like they expect snippets to only contain $0, so I'll assume langugage-servers will limit themselves to just that :/

But at least they'll allow multiple snippetTextEdits, I'll look into accomodating that here. Chaining multiple snippets together sounds cool+is definitely feasible :D

L3MON4D3 avatar Oct 03 '22 21:10 L3MON4D3

The VSCode seems to now support SnippetTextEdit in the SnippetSession.

https://github.com/microsoft/vscode/blob/main/src/vs/editor/contrib/snippet/browser/snippetController2.ts#L91 https://github.com/microsoft/vscode/blob/main/src/vs/editor/contrib/snippet/browser/snippetSession.ts#L526

I think the time has come to support these functionality in the snippet engine side.

hrsh7th avatar Oct 12 '22 06:10 hrsh7th

True! Are you aware of a formal specification of the snippetTextEdits vscode expects, and how it handles them? I'm not at all familiar with vscode's way of handling snippets, so I can't infer much from just the code, unfortunately (also, might still be a while until I can really get into this :/)

L3MON4D3 avatar Oct 12 '22 07:10 L3MON4D3

@simrat39 Would you test #577 again? Multiple snippetTextEdits in one should now work. To test, try

                                                                       
                                                                       
                                                                       
                                                                       
                                                                       
                                                                       
                                                                       
                                                                       
                                                                       

require("luasnip.extras.lsp").apply_text_edits(
{ {
    insertTextFormat = 2,
    newText = "${1: lolo} adsffff ${2: lele}",
    range = {
      ["end"] = {
        character = 16,
        line = 2
      },
      start = {
        character = 0,
        line = 2
      }
    }
  }, {
    insertTextFormat = 2,
    newText = "${1: lolo} adsffff ${2: lele}",
    range = {
      ["end"] = {
        character = 0,
        line = 3
      },
      start = {
        character = 16,
        line = 2
      }
    }
  }, {
    insertTextFormat = 2,
    newText = "\n\nimpl std::fmt::Debug for Bruh {\n    $0fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Bruh\").field(\"a\", &self.a).finish()\n    }\n}",
    range = {
      ["end"] = {
        character = 1,
        line = 5
      },
      start = {
        character = 1,
        line = 5
      }
    }
  } }, 1, "utf-8", vim.lsp.util.apply_text_edits)

(stolen from that issue you opened :D)

One problem is: if the server sends a snippetTextEdit with newText="", we will just insert a i(0), which causes one jump to end up there, which does not seem expected. We probably need to do some special handling of those :(

L3MON4D3 avatar Feb 02 '23 11:02 L3MON4D3

To test, try

Oh, or the call you have in your plugin, that should still work

L3MON4D3 avatar Feb 02 '23 11:02 L3MON4D3

So now 3.18 has brought SnippetTextEdit into the core lsp protocol. I wonder what is the best way to make sure LuaSnip is always handling them?

IndianBoy42 avatar May 28 '24 16:05 IndianBoy42