zed-industries / zed

Code at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.
https://zed.dev
Other
47.64k stars 2.78k forks source link

Go LSP Snippet Completion Does Not Show Up #7523

Closed marwan-at-work closed 7 months ago

marwan-at-work commented 7 months ago

Check for existing issues

Describe the bug / provide steps to reproduce it

Hi there,

Go has a few snippet completions that are sent via its gopls LSP server. For example, this VSCode completion triggers an error check inside a test function:

Screenshot 2024-02-07 at 4 51 43 PM

However, the same completion inside Zed simply does not show up.

I know there is https://github.com/zed-industries/zed/issues/4611 but that seems to be more about user-defined snippets versus a snippet that the LSP natively returns for requests.

Environment

Zed: v0.121.5 (Zed) OS: macOS 14.3.0 Memory: 96 GiB Architecture: aarch64

If applicable, add mockups / screenshots to help explain present your vision of the feature

No response

If applicable, attach your ~/Library/Logs/Zed/Zed.log file to this issue.

If you only need the most recent lines, you can run the zed: open log command palette action to see the last 1000.

No response

mrnugget commented 7 months ago

Are you sure that these snippets are coming from gopls? Because my suspicion is that they're part of the vscode-go extension: https://github.com/golang/vscode-go/blob/baab7be148eeba4f130bfaa077f5a401adea61dd/extension/snippets/go.json#L113-L116

marwan-at-work commented 7 months ago

@mrnugget 👋🏼 yes they're coming from gopls, specifically from here: https://github.com/golang/tools/blob/1b39a8b6a9ba38b140eb9b84196ddcb4e8f53ce2/gopls/internal/golang/completion/statements.go#L178

What you linked to is the hard-coded snippet completion that returns nil, err but gopls's error check dynamically returns the zero value of the function signature you're in. So it could be 0, "", err instead of the blind nil, err

And notice it also does the *testing.T check which vscode snippets does not have at all.

mrnugget commented 7 months ago

Thank you! Okay, very interesting. That requires an investigation.

mrnugget commented 7 months ago

So, first finding after digging into this a bit:

Here's what we show when typing i (like in your example) here:

screenshot-2024-02-14-17 51 29@2x

VS Code also doesn't show the snippet:

screenshot-2024-02-14-17 52 53@2x

And when I check the traces, we also don't get that snippet sent back from the LSP, meaning we don't filter it out:

{
  "jsonrpc": "2.0",
  "result": {
    "isIncomplete": true,
    "items": [
      {
        "label": "if",
        "kind": 14,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "preselect": true,
        "sortText": "00000",
        "filterText": "if",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "if"
        }
      },
      {
        "label": "imag",
        "kind": 3,
        "detail": "func(c complex128) float64",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00001",
        "filterText": "imag",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "imag(${1:})"
        }
      },
      {
        "label": "int",
        "kind": 7,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00002",
        "filterText": "int",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "int"
        }
      },
      {
        "label": "int16",
        "kind": 7,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00003",
        "filterText": "int16",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "int16"
        }
      },
      {
        "label": "int32",
        "kind": 7,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00004",
        "filterText": "int32",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "int32"
        }
      },
      {
        "label": "int64",
        "kind": 7,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00005",
        "filterText": "int64",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "int64"
        }
      },
      {
        "label": "int8",
        "kind": 7,
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00006",
        "filterText": "int8",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "int8"
        }
      },
      {
        "label": "io",
        "kind": 9,
        "detail": "\"io\"",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00007",
        "filterText": "io",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "io"
        },
        "additionalTextEdits": [
          {
            "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 7 } },
            "newText": "(\n\t"
          },
          {
            "range": { "start": { "line": 2, "character": 8 }, "end": { "line": 2, "character": 9 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 10 } },
            "newText": "rror"
          },
          {
            "range": { "start": { "line": 2, "character": 11 }, "end": { "line": 2, "character": 15 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 0 }, "end": { "line": 3, "character": 0 } },
            "newText": "\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 1 }, "end": { "line": 3, "character": 3 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 4 }, "end": { "line": 3, "character": 5 } },
            "newText": "\"\n\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 8 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 9 }, "end": { "line": 3, "character": 13 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 14 }, "end": { "line": 3, "character": 14 } },
            "newText": "ting"
          },
          {
            "range": { "start": { "line": 3, "character": 15 }, "end": { "line": 3, "character": 15 } },
            "newText": "\n)"
          }
        ]
      },
      {
        "label": "intern",
        "kind": 9,
        "detail": "\"internal/intern\"",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00008",
        "filterText": "intern",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "intern"
        },
        "additionalTextEdits": [
          {
            "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 7 } },
            "newText": "(\n\t"
          },
          {
            "range": { "start": { "line": 2, "character": 8 }, "end": { "line": 2, "character": 9 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 10 } },
            "newText": "rror"
          },
          {
            "range": { "start": { "line": 2, "character": 11 }, "end": { "line": 2, "character": 11 } },
            "newText": "\"\n\t\"in"
          },
          {
            "range": { "start": { "line": 2, "character": 12 }, "end": { "line": 2, "character": 12 } },
            "newText": "ernal/"
          },
          {
            "range": { "start": { "line": 2, "character": 14 }, "end": { "line": 2, "character": 15 } },
            "newText": "tern"
          },
          {
            "range": { "start": { "line": 3, "character": 0 }, "end": { "line": 3, "character": 5 } },
            "newText": "\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 8 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 9 }, "end": { "line": 3, "character": 13 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 14 }, "end": { "line": 3, "character": 14 } },
            "newText": "ting"
          },
          {
            "range": { "start": { "line": 3, "character": 15 }, "end": { "line": 3, "character": 15 } },
            "newText": "\n)"
          }
        ]
      },
      {
        "label": "internal",
        "kind": 9,
        "detail": "\"log/internal\"",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00009",
        "filterText": "internal",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "internal"
        },
        "additionalTextEdits": [
          {
            "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 7 } },
            "newText": "(\n\t"
          },
          {
            "range": { "start": { "line": 2, "character": 8 }, "end": { "line": 2, "character": 9 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 10 } },
            "newText": "rror"
          },
          {
            "range": { "start": { "line": 2, "character": 11 }, "end": { "line": 2, "character": 12 } },
            "newText": "\"\n\t\"log/"
          },
          {
            "range": { "start": { "line": 2, "character": 14 }, "end": { "line": 2, "character": 15 } },
            "newText": "ternal"
          },
          {
            "range": { "start": { "line": 3, "character": 0 }, "end": { "line": 3, "character": 5 } },
            "newText": "\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 8 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 9 }, "end": { "line": 3, "character": 13 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 14 }, "end": { "line": 3, "character": 14 } },
            "newText": "ting"
          },
          {
            "range": { "start": { "line": 3, "character": 15 }, "end": { "line": 3, "character": 15 } },
            "newText": "\n)"
          }
        ]
      },
      {
        "label": "internal",
        "kind": 9,
        "detail": "\"net/http/internal\"",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00010",
        "filterText": "internal",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "internal"
        },
        "additionalTextEdits": [
          { "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 7 } },
            "newText": "(\n\t"
          },
          {
            "range": { "start": { "line": 2, "character": 8 }, "end": { "line": 2, "character": 9 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 10 } },
            "newText": "rror"
          },
          {
            "range": { "start": { "line": 2, "character": 11 }, "end": { "line": 2, "character": 11 } },
            "newText": "\"\n\t\"ne"
          },
          {
            "range": { "start": { "line": 2, "character": 12 }, "end": { "line": 2, "character": 12 } },
            "newText": "/http/"
          },
          {
            "range": { "start": { "line": 2, "character": 14 }, "end": { "line": 2, "character": 15 } },
            "newText": "ternal"
          },
          {
            "range": { "start": { "line": 3, "character": 0 }, "end": { "line": 3, "character": 5 } },
            "newText": "\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 8 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 9 }, "end": { "line": 3, "character": 13 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 14 }, "end": { "line": 3, "character": 14 } },
            "newText": "ting"
          },
          {
            "range": { "start": { "line": 3, "character": 15 }, "end": { "line": 3, "character": 15 } },
            "newText": "\n)"
          }
        ]
      },
      {
        "label": "itoa",
        "kind": 9,
        "detail": "\"internal/itoa\"",
        "documentation": {
          "kind": "markdown",
          "value": ""
        },
        "sortText": "00011",
        "filterText": "itoa",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "itoa"
        },
        "additionalTextEdits": [
          {
            "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 7 } },
            "newText": "(\n\t"
          },
          {
            "range": { "start": { "line": 2, "character": 8 }, "end": { "line": 2, "character": 9 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 2, "character": 10 }, "end": { "line": 2, "character": 10 } },
            "newText": "rror"
          },
          {
            "range": { "start": { "line": 2, "character": 11 }, "end": { "line": 2, "character": 15 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 0 }, "end": { "line": 3, "character": 0 } },
            "newText": "\t\""
          },
          {
            "range": { "start": { "line": 3, "character": 1 }, "end": { "line": 3, "character": 4 } },
            "newText": "nte"
          },
          {
            "range": { "start": { "line": 3, "character": 5 }, "end": { "line": 3, "character": 5 } },
            "newText": "nal/i"
          },
          {
            "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 7 } },
            "newText": "oa\"\n\t"
          },
          {
            "range": { "start": { "line": 3, "character": 8 }, "end": { "line": 3, "character": 8 } },
            "newText": "t"
          },
          {
            "range": { "start": { "line": 3, "character": 9 }, "end": { "line": 3, "character": 13 } },
            "newText": ""
          },
          {
            "range": { "start": { "line": 3, "character": 14 }, "end": { "line": 3, "character": 14 } },
            "newText": "ting"
          },
          {
            "range": { "start": { "line": 3, "character": 15 }, "end": { "line": 3, "character": 15 } },
            "newText": "\n)"
          }
        ]
      },
      {
        "label": "errors.Is",
        "kind": 3,
        "detail": "func(err error, target error) bool",
        "documentation": {
          "kind": "markdown",
          "value": "Is reports whether any error in err's tree matches target.\n\nThe tree consists of err itself, followed by the errors obtained by repeatedly calling Unwrap. When err wraps multiple errors, Is examines err followed by a depth-first traversal of its children.\n\nAn error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.\n\nAn error type might provide an Is method so it can be treated as equivalent to an existing error. For example, if MyError defines\n\n\tfunc (m MyError) Is(target error) bool { return target == fs.ErrExist }\n\nthen Is(MyError{}, fs.ErrExist) returns true. See [syscall.Errno.Is](https://pkg.go.dev/syscall#Errno.Is) for an example in the standard library. An Is method should only shallowly compare err and the target and not call Unwrap on either.\n"
        },
        "sortText": "00012",
        "filterText": "errors.Is",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "errors.Is(${1:})"
        }
      },
      {
        "label": "testing.Init",
        "kind": 3,
        "detail": "func()",
        "documentation": {
          "kind": "markdown",
          "value": "Init registers testing flags. These flags are automatically registered by the \"go test\" command before running test functions, so Init is only needed when calling functions such as Benchmark without using \"go test\".\n\nInit has no effect if it was already called.\n"
        },
        "sortText": "00013",
        "filterText": "testing.Init",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "testing.Init()"
        }
      },
      {
        "label": "testing.InternalBenchmark",
        "kind": 22,
        "detail": "struct{...}",
        "documentation": {
          "kind": "markdown",
          "value": "InternalBenchmark is an internal type but exported because it is cross-package; it is part of the implementation of the \"go test\" command.\n"
        },
        "sortText": "00014",
        "filterText": "testing.InternalBenchmark",
        "insertTextFormat": 2,
        "textEdit": {
          "range": { "start": { "line": 11, "character": 1 }, "end": { "line": 11, "character": 2 } },
          "newText": "testing.InternalBenchmark"
        }
      }
    ]
  },
  "id": 11
}

And even if I type more, I don't get that snippet from VS Code:

screenshot-2024-02-14-17 55 56@2x

I'm wondering: do you have some special configuration for gopls?

mrnugget commented 7 months ago

Okay, I got it. We need to send usePlaceholders: true to gopls to enable snippets that have fields/tabstops in it.

See: https://github.com/golang/tools/blob/master/gopls/doc/settings.md#completion

In the Zed settings.json:

{
  // ...
  "lsp": {
    "gopls": {
      "initialization_options": {
        "usePlaceholders": true, // <-- important line
        "hints": {
          "assignVariableTypes": true,
          "compositeLiteralFields": true,
          "compositeLiteralTypes": true,
          "constantValues": true,
          "functionTypeParameters": true,
          "parameterNames": true,
          "rangeVariableTypes": true
        }
      }
    }
  }

}

Then we get these: image

I also had to turn these on in the vscode-go extension.

BUT I do think there's a case for enabling these by default.

marwan-at-work commented 7 months ago

@mrnugget thank you! Those settings names get me every time. I knew it wasn't on-by-default, but I went looking for it before opening this issue and couldn't find anything when looking up the word "snippet" so I assumed they were now on by default.

Thanks for digging and fixing the issue!

mrnugget commented 7 months ago

Those settings names get me every time.

Yeah, same. The docs also aren't that clear about the effect here.

JeetChhatrala commented 6 months ago

I see placeHolders are now enabled by default. I am still not getting such code snippets as completion. I tried to manually add it to settings.json but still no luck. please tell me if i am missing something.

I am using Zed 0.128.3, on Macbook pro M2 pro 16gb

mrnugget commented 5 months ago

@JeetChhatrala can you create a ticket with minimal reproducible example & steps to reproduce? Without example code, your settings, steps to reproduce, Go setup in the repository, it's very hard to reproduce.

See if you need a go.work file: https://github.com/zed-industries/zed/issues/8460#issuecomment-1971216276 Make sure go is setup correctly: https://github.com/zed-industries/zed/issues/8861#issuecomment-2015740544

JeetChhatrala commented 5 months ago

@JeetChhatrala can you create a ticket with minimal reproducible example & steps to reproduce? Without example code, your settings, steps to reproduce, Go setup in the repository, it's very hard to reproduce.

See if you need a go.work file: #8460 (comment) Make sure go is setup correctly: #8861 (comment)

I checked both of your concerns, i have installed go using default website's pkg installer. since go to implementation works for me that should not be the issue here. and i am already using go.work in my project.

There are no steps to reproduce since i just installed the zed editor and tried it. Opening Go project and it does not work. for the same project everything works fine in VSCode.

please help me out here, this is the only reason i am still not using zed as daily driver. I really love zed and would like to use it ASAP.

mrnugget commented 5 months ago

There are no steps to reproduce since i just installed the zed editor and tried it. Opening Go project and it does not work. for the same project everything works fine in VSCode.

Steps to reproduce and a minimal reproducible example would be something like: "when I have this directory structure, these files, then open Zed in this directory, and try to do this, then this not work"

Right now I can't fix anything for you, because I don't know: does gopls start up? does it not startup? are there any logs? is this specific to your project? something in general not working?