helix icon indicating copy to clipboard operation
helix copied to clipboard

allow LSP insert text to replace non-matching prefixes (#5469)

Open Taywee opened this issue 1 year ago • 11 comments

closes #5469

Most LSPs will complete case-insensitive matches, particularly from lowercase to uppercase. In some cases, notably Pyright, this is given as a simple insert text instead of TextEdit. When this happens, the prefix text was left unedited.

This change seems to fix it for me. I'm not sure if there are other side effects to doing it this way, though. If another LSP gave an insert text that was just the rest to be inserted, I imagine this would still cause problems. But the previous method would also fail in that case with repeated text. If you typed su and the suggestion was surration, it would be ambiguous in that case whether that was to be inserted (making susurration) or replaced entirely (making surration).

Taywee avatar Jan 10 '23 19:01 Taywee

In my brief testing this seems to work really well, It think it would be good to test this on a wide range of lsp's. Also it should fix #4131 and maybe some of the related pr's, the issues that have the completion inserted in between text like #4851 probably isn't fixed by this.

gabydd avatar Jan 10 '23 20:01 gabydd

In a quick test, this does not seem to fix #4131. It looks a bit to me like gopls has bad completion in the first place.

Given this input:

input

2023-01-10T13:43:53.835 helix_lsp::transport [INFO] -> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"range":{"end":{"character":8,"line":3},"start":{"character":8,"line":3}},"text":"["}],"textDocument":{"uri":"file:///tmp/tmp.APmRKN78A3/hxtest.go","version":14}}}
2023-01-10T13:43:54.283 helix_lsp::transport [INFO] -> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"range":{"end":{"character":9,"line":3},"start":{"character":9,"line":3}},"text":"s"}],"textDocument":{"uri":"file:///tmp/tmp.APmRKN78A3/hxtest.go","version":15}}}
2023-01-10T13:43:54.459 helix_lsp::transport [INFO] -> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"contentChanges":[{"range":{"end":{"character":10,"line":3},"start":{"character":10,"line":3}},"text":"t"}],"textDocument":{"uri":"file:///tmp/tmp.APmRKN78A3/hxtest.go","version":16}}}
2023-01-10T13:43:54.471 helix_lsp::transport [INFO] -> {"jsonrpc":"2.0","method":"textDocument/completion","params":{"position":{"character":11,"line":3},"textDocument":{"uri":"file:///tmp/tmp.APmRKN78A3/hxtest.go"}},"id":6}

The response contains this:

response

      {
        "label": "string",
        "labelDetails": {},
        "kind": 7,
        "sortText": "00029",
        "filterText": "string",
        "insertTextFormat": 1,
        "textEdit": {
          "range": {
            "start": {
              "line": 3,
              "character": 11
            },
            "end": {
              "line": 3,
              "character": 11
            }
          },
          "newText": "string"
        }
      }

I'm not sure at the moment how other client LSPs handle it. I'll give it a shot with nvim real quick.

Taywee avatar Jan 10 '23 20:01 Taywee

In nvim, the gopls suggested response is:

{ filterText = "string", insertTextFormat = 2, kind = 7, label = "string", labelDetails = vim.empty_dict(), sortText = "00029", textEdit = { newText = "string", range = { ["end"] = { character = 11, line = 3 }, start = { character = 11, line = 3 } } } }

So nvim handles the textEdit differently, too. The gopls issue seems unrelated to what this PR addresses.

Taywee avatar Jan 10 '23 21:01 Taywee

The golps issue is a similar but unrelated issue as they do send an text edit

pascalkuthe avatar Jan 10 '23 22:01 pascalkuthe

Thanks for the spec reference. I'm still curious how nvim is handling the gopls case, because the textEdit range appears to not be correct, and nvim still gets the right result. According to the next section in the spec, a TextEdit with the same start and endpoints should be a plain insert. I might be simply missing something, but I guess that's not totally relevant to this PR or issue anyway.

Taywee avatar Jan 11 '23 04:01 Taywee

Thanks for the spec reference. I'm still curious how nvim is handling the gopls case, because the textEdit range appears to not be correct, and nvim still gets the right result. According to the next section in the spec, a TextEdit with the same start and endpoints should be a plain insert. I might be simply missing something, but I guess that's not totally relevant to this PR or issue anyway.

I have been digging intot his and at this point I am pretty sure its actually an upstream issue with the language server. Clearly sending and empty edit range is supposed to be an insertion and they have been receiving and (sometimes) fixing issues for that too. In particular I found the following commdnt: https://github.com/golang/go/issues/57384#issuecomment-1359455698

I think the response here is odd. They are quoting the lsp standard and say that what they do.is not standard compliant and that is the reason for the bug but then they go on to state that vscode handels this fine and they don't see a reason it shouldn't work so it's ok? Why even have a standard then?

We could add some kind of workaround (we should port it from vscode if anybody can find where it's happening) although I am in favor of reporting it upstream instead of.bending.over backwards for non standard compliant severs.

I also looked at nvim-cmp and Laplace (gave up with vscode):

  • Laplace always extends the removed text to the full word no matter what the edit range says (not spec compliant)
  • nvim-cmp used to accidently do the same but got.patched, after that people complained and somebody posted a weird fix thathaopens to work but it seem like it directly.violates the spec and they just didn't notice because the result is fine most of.the time.regardless. There is also no explanation od how/why this wokrs. During PR review the maintainer asked his/why it works, the author said he doesn't know and it got merged anyway? :D

pascalkuthe avatar Jan 11 '23 04:01 pascalkuthe

Wow. So no major LSP client is actually spec compliant on TextEdit completions, including VSCode. That's surprising. I'd rather be correct and complain upstream than violate the spec and paper over the difference.

Based on the linked issue, it does seem like they might have fixed the issue in gopls in the main branch as of a few weeks ago: https://github.com/golang/go/issues/57384

Taywee avatar Jan 11 '23 14:01 Taywee

Emacs LSP actually also has an open issue about it. They handle it the same way helix does and there are others too (for nvim alone there are like 4? LSP completions clients or more and some of those gopls issue were filed by nvim people so some of them probably handle this correctly). I didn't bother to look for more implementations tough.

I do agree that we should stick to the standard. I hope gpls fixes this upstream. It sounded like that the issue I linked was only about some completions for tests tough. I am not sure if they solved it in general or just for that specific case as that issue you mentioned about the general case was closed as duplicate

pascalkuthe avatar Jan 11 '23 14:01 pascalkuthe

Right, I'm mostly just shocked about VSCode. If one implementation should be fully spec compliant, it should be the editor that the standard was created for.

Taywee avatar Jan 11 '23 14:01 Taywee

This probably also solves https://github.com/helix-editor/helix/pull/1819 since we replace everything from the starting offset onwards

archseer avatar Jan 17 '23 02:01 archseer

This probably also solves #1819 since we replace everything from the starting offset onwards

This change only affects LSP servers that do not send a TextEdit with their completions and leave it up to the editor what text to replace. Most LSPs do send an edit (according to the spec the option of not sending an exit only exists to allow quick imolemtmstion of basic servers) and are unaffectd by this change. So at least to those servers the problem persists.

pascalkuthe avatar Jan 17 '23 03:01 pascalkuthe

I missed something while reviewing this PR: It removes the ability to complete at multiple cursors at once. I included this PR in #5728 as I was already making multiple changes to the same code that would cause conflicts and fixed that problem. I kept the commit from this PR to give credit.

pascalkuthe avatar Jan 30 '23 02:01 pascalkuthe

Was included in https://github.com/helix-editor/helix/pull/5728

archseer avatar Mar 09 '23 04:03 archseer