LuaLS / lua-language-server

A language server that offers Lua language support - programmed in Lua
https://luals.github.io
MIT License
3.32k stars 312 forks source link

completion returns wrong and according to the spec invalid results #2762

Closed max397574 closed 1 month ago

max397574 commented 3 months ago

How are you using the lua-language-server?

NeoVim

Which OS are you using?

MacOS

What is the issue affecting?

Completion

Expected Behaviour

Get back a completion item which does nothing which is explicitly forbidden in the spec and actually is correct

Actual Behaviour

Returns a completion item with a textEdit which makes no sense and additionalTextEdits which wouldn't even be allowed by the spec

Reproduction steps

I completed with the following context (notice that the cursor position uses different based indexing because neovim being like it is)

local entry_context = {
    bufnr = 1,
    cursor = { col = 35, row = 1 },
    line = "    completion_item.textEdit.range.",
    line_before_cursor = "    completion_item.textEdit.range.",
    previous = nil,
    reason = 1,
}

I get back an entry like this

local completion_item = {
    additionalTextEdits = {
        {
            newText = "",
            range = {
                ["end"] = { character = 35, line = 69 },
                start = { character = 34, line = 69 },
            },
        },
    },
    data = { id = 119, uri = "not relevant" },
    detail = "lsp.Position",
    documentation = { kind = "markdown", value = "not relevant" },
    insertTextFormat = 2,
    kind = 5,
    label = '"end"',
    sortText = "0001",
    textEdit = {
        newText = '["end"]',
        range = {
            ["end"] = { character = 35, line = 69 },
            start = { character = 4, line = 71 },
        },
    },
}

Additional Notes

I have no idea why the text edit would modify 3 different lines when just a single statement would have to be inserted. About additionalTextEdits from the lsp specification:

/**

  • An optional array of additional text edits that are applied when
  • selecting this completion. Edits must not overlap (including the same
  • insert position) with the main edit nor with themselves.
  • Additional text edits should be used to change text unrelated to the
  • current cursor position (for example adding an import statement at the
  • top of the file if the completion item will insert an unqualified type). */

In this case the additionalTextEdit overlaps with the edit and also doesn't what's it intended for (2nd paragraph)

Log File

No response

max397574 commented 2 months ago

@sumneko @CppCXY @tomlau10 could you help me with this or point me in the direction where I could fix this in the code myself?

max397574 commented 2 months ago

still happening with latests version is there anything I can do about this?

tomlau10 commented 2 months ago

I use vscode and I don't quite understand what this issue is talking about.

I also cannot follow your Reproduction steps 😅


I'd be happy to help if you can elaborate more on this issue, though I can't guarantee I'll be able to find a solution 😄 @max397574

max397574 commented 2 months ago

I'm writing a completion engine for neovim and there I get completions when I have a type with an end field (which is the case for lsp ranges)

the entry context is the context in which completion was requested/received from the server I0m not seeing any errors. I just get a completion item which does really wrong things.

I'll try to create a minimal example

I could try this on vscode But perhaps it won't be reproducible because e..g some other completion engine I used in neovim didn't even return the entry because it was filtered out as a duplicate of simply .end

tomlau10 commented 2 months ago

I have a type with an end field

I just crafted a range type with the field start and end, and I experienced some strange issue when auto completing the end field. 😕 As you may know end is a lua keyword and we just can't write t.end, we have to write it as t["end"].

---@class range
---@field start integer
---@field end integer

---@type range
local r

r["|"] --< `|` is the cursor and I trigger completion there

However this is different from what you're encountering. And there maybe more bugs in LuaLS side of it's completion function. 😂

max397574 commented 2 months ago

minimal example

---@class Range
---@field start integer
---@field end integer

---@type Range
local x = { ["end"] = 3, start = 5 }
x.|
local y = 3

cursor is the | select the completion "end" (notice the quotes) it seems relevant that there is code after the line on which you complete

this leads to the invalid completion item as noted above (overlapping, multiple lines)

tomlau10 commented 2 months ago

I just tried with you example:

Maybe in the 1st case, there's the wrong completion item and vscode will filter it out silently.


With some digging in the code base, I believe the logic of handling completion request is here:

That's all I know, maybe you can start debugging from there 🤔

max397574 commented 2 months ago

well but the language server does also return items in the case with the local y which are just filtered out by vscode then it seems. And it looks like they are only problematic in this case. Otherwise I also get the completion and it also is correct.

max397574 commented 2 months ago

I'll try to look into the code parts you sent

tomlau10 commented 2 months ago

I forgot to mention that, you can start LuaLS with --rpcjson=true argument, and LuaLS will logs the json data that received and sent in the log file.

the language server does also return items in the case with the local y

And yes I confirmed in the case with the local y, LuaLS also return the wrong completion item, as seen from the log. Below is the beautified json data:

{
  "id": 20,
  "jsonrpc": "2.0",
  "result": {
    "isIncomplete": false,
    "items": [
      {
        "additionalTextEdits": [
          {
            "newText": "",
            "range": {
              "end": {
                "character": 2,
                "line": 6
              },
              "start": {
                "character": 1,
                "line": 6
              }
            }
          }
        ],
        "data": {
          "id": 1,
          "uri": "file:///c%3A/Users/TomLau/test/test.lua"
        },
        "insertTextFormat": 2,
        "kind": 13,
        "label": "\"end\"",
        "sortText": "0001",
        "textEdit": {
          "newText": "[\"end\"]",
          "range": {
            "end": {
              "character": 2,
              "line": 6
            },
            "start": {
              "character": 0,
              "line": 8
            }
          }
        }
      },
      {
        "data": {
          "id": 2,
          "uri": "file:///c%3A/Users/TomLau/test/test.lua"
        },
        "insertTextFormat": 2,
        "kind": 5,
        "label": "end",
        "sortText": "0002"
      },
      {
        "data": {
          "id": 3,
          "uri": "file:///c%3A/Users/TomLau/test/test.lua"
        },
        "insertTextFormat": 2,
        "kind": 13,
        "label": "start",
        "sortText": "0003"
      }
    ]
  }
}
tomlau10 commented 2 months ago

For the case when a completion item's newText is a ["field"], I believe it is handled by checkFieldFromFieldToIndex() https://github.com/LuaLS/lua-language-server/blob/aaf16240f77bb75a3d8db89c2a99934c8b6022ee/script/core/completion/completion.lua#L449-L477

Maybe the offset calculation here is incorrect 🤔

tomlau10 commented 2 months ago

(Off topic)

Oh for the incorrect completion inside quoted string that I mentioned in this comment: https://github.com/LuaLS/lua-language-server/issues/2762#issuecomment-2282695433, I found that you have reported it in another issue: #2688.

Maybe during your debugging journey, you can fix both of them 😄

max397574 commented 1 month ago

@sumneko I tried to debug this myself but couldn't figure it out. Could you take a look at this?

tomlau10 commented 1 month ago

I don't think author have time to debug this, given that seems no others are encountering this issue, so this have a very low low priority ☚ī¸ .

As many editors will just ignore invalid entries on client side, maybe in the meantime you can filter invalid result in your client side as well? Another approach that I can think of is to filter invalid result on LuaLS side before it returns the result items, but this just hides the problem ... 😇

So seems the best way is you tackle it in your client side, and leave this issue open in LuaLS repo. Maybe in someday it will get solved finally, by author or by others, (or maybe not...) 😂 @max397574

CppCXY commented 1 month ago

Some parts of the specification are difficult to understand. We understood some things through translation software, and the actual implementation of the functionality was simply tested by continuously checking whether VSCode worked correctly at the time.

tomlau10 commented 1 month ago

I have spent some time to debug this issue and I am 100% sure the related logic resides in checkFieldFromFieldToIndex(). Here are some findings 🤔

Purpose of additionalTextEdits

Problem when triggering completion in middle

The unknown logic of finding next word offset

This implies that the bug is triggered when completing immediately after a dot, and there are words following the cursor. In this case (completing immediately after a dot) the word value in checkFieldFromFieldToIndex() is empty string. Therefore the following extra logic is being executed: https://github.com/LuaLS/lua-language-server/blob/c9d819305b6f88b53de294d54c820681518430c8/script/core/completion/completion.lua#L461-L467

My proposed solution

Remove this entire if case in the following logic: https://github.com/LuaLS/lua-language-server/blob/c9d819305b6f88b53de294d54c820681518430c8/script/core/completion/completion.lua#L460-L470

max397574 commented 1 month ago

I opened a pr with this change here https://github.com/LuaLS/lua-language-server/pull/2844