tools/gopls: provide markdown for completion and signature help
If the client prefers markdown, provide markdown.
There is a change to the LSP stubs, with Documentation fields becoming
Or-types (string|MarkupContent). If the client prefers, the returned
Documentation is markdown.
Fixes: golang/go#57300
Change-Id: I57300146333552da3849c1b6bfb97793042faee2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/463377
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Peter Weinberger <pjw@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cmd/signature.go b/gopls/internal/lsp/cmd/signature.go
index 64c892e..4d47cd2 100644
--- a/gopls/internal/lsp/cmd/signature.go
+++ b/gopls/internal/lsp/cmd/signature.go
@@ -73,8 +73,15 @@
// see toProtocolSignatureHelp in lsp/signature_help.go
signature := s.Signatures[0]
fmt.Printf("%s\n", signature.Label)
- if signature.Documentation != "" {
- fmt.Printf("\n%s\n", signature.Documentation)
+ switch x := signature.Documentation.Value.(type) {
+ case string:
+ if x != "" {
+ fmt.Printf("\n%s\n", x)
+ }
+ case protocol.MarkupContent:
+ if x.Value != "" {
+ fmt.Printf("\n%s\n", x.Value)
+ }
}
return nil
diff --git a/gopls/internal/lsp/completion.go b/gopls/internal/lsp/completion.go
index d63e3a3..b2e50cc 100644
--- a/gopls/internal/lsp/completion.go
+++ b/gopls/internal/lsp/completion.go
@@ -101,6 +101,15 @@
continue
}
+ doc := &protocol.Or_CompletionItem_documentation{
+ Value: protocol.MarkupContent{
+ Kind: protocol.Markdown,
+ Value: source.CommentToMarkdown(candidate.Documentation),
+ },
+ }
+ if options.PreferredContentFormat != protocol.Markdown {
+ doc.Value = candidate.Documentation
+ }
item := protocol.CompletionItem{
Label: candidate.Label,
Detail: candidate.Detail,
@@ -121,7 +130,7 @@
FilterText: strings.TrimLeft(candidate.InsertText, "&*"),
Preselect: i == 0,
- Documentation: candidate.Documentation,
+ Documentation: doc,
Tags: candidate.Tags,
Deprecated: candidate.Deprecated,
}
diff --git a/gopls/internal/lsp/completion_test.go b/gopls/internal/lsp/completion_test.go
index 9a72f12..cd3bcec 100644
--- a/gopls/internal/lsp/completion_test.go
+++ b/gopls/internal/lsp/completion_test.go
@@ -115,11 +115,13 @@
toProtocolCompletionItem := func(item *completion.CompletionItem) protocol.CompletionItem {
pItem := protocol.CompletionItem{
- Label: item.Label,
- Kind: item.Kind,
- Detail: item.Detail,
- Documentation: item.Documentation,
- InsertText: item.InsertText,
+ Label: item.Label,
+ Kind: item.Kind,
+ Detail: item.Detail,
+ Documentation: &protocol.Or_CompletionItem_documentation{
+ Value: item.Documentation,
+ },
+ InsertText: item.InsertText,
TextEdit: &protocol.TextEdit{
NewText: item.Snippet(),
},
diff --git a/gopls/internal/lsp/fake/editor.go b/gopls/internal/lsp/fake/editor.go
index 70d5e5e..eaeca85 100644
--- a/gopls/internal/lsp/fake/editor.go
+++ b/gopls/internal/lsp/fake/editor.go
@@ -1184,6 +1184,23 @@
return e.Server.Implementation(ctx, params)
}
+func (e *Editor) SignatureHelp(ctx context.Context, loc protocol.Location) (*protocol.SignatureHelp, error) {
+ if e.Server == nil {
+ return nil, nil
+ }
+ path := e.sandbox.Workdir.URIToPath(loc.URI)
+ e.mu.Lock()
+ _, ok := e.buffers[path]
+ e.mu.Unlock()
+ if !ok {
+ return nil, fmt.Errorf("buffer %q is not open", path)
+ }
+ params := &protocol.SignatureHelpParams{
+ TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
+ }
+ return e.Server.SignatureHelp(ctx, params)
+}
+
func (e *Editor) RenameFile(ctx context.Context, oldPath, newPath string) error {
closed, opened, err := e.renameBuffers(ctx, oldPath, newPath)
if err != nil {
diff --git a/gopls/internal/lsp/protocol/generate/tables.go b/gopls/internal/lsp/protocol/generate/tables.go
index 93a0436..838990c 100644
--- a/gopls/internal/lsp/protocol/generate/tables.go
+++ b/gopls/internal/lsp/protocol/generate/tables.go
@@ -128,11 +128,10 @@
// For gopls compatibility, use a different, typically more restrictive, type for some fields.
var renameProp = map[prop]string{
- {"CancelParams", "id"}: "interface{}",
- {"Command", "arguments"}: "[]json.RawMessage",
- {"CompletionItem", "documentation"}: "string",
- {"CompletionItem", "textEdit"}: "TextEdit",
- {"Diagnostic", "code"}: "interface{}",
+ {"CancelParams", "id"}: "interface{}",
+ {"Command", "arguments"}: "[]json.RawMessage",
+ {"CompletionItem", "textEdit"}: "TextEdit",
+ {"Diagnostic", "code"}: "interface{}",
{"DocumentDiagnosticReportPartialResult", "relatedDocuments"}: "map[DocumentURI]interface{}",
@@ -181,7 +180,6 @@
{"ServerCapabilities", "typeDefinitionProvider"}: "interface{}",
{"ServerCapabilities", "typeHierarchyProvider"}: "interface{}",
{"ServerCapabilities", "workspaceSymbolProvider"}: "bool",
- {"SignatureInformation", "documentation"}: "string",
{"TextDocumentEdit", "edits"}: "[]TextEdit",
{"TextDocumentSyncOptions", "save"}: "SaveOptions",
{"WorkspaceEdit", "documentChanges"}: "[]DocumentChanges",
diff --git a/gopls/internal/lsp/protocol/tsclient.go b/gopls/internal/lsp/protocol/tsclient.go
index 116a81b..b51b1f6 100644
--- a/gopls/internal/lsp/protocol/tsclient.go
+++ b/gopls/internal/lsp/protocol/tsclient.go
@@ -7,7 +7,7 @@
package protocol
// Code generated from version 3.17.0 of protocol/metaModel.json.
-// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-14)
+// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-23)
import (
"context"
diff --git a/gopls/internal/lsp/protocol/tsjson.go b/gopls/internal/lsp/protocol/tsjson.go
index a0f11c6..0c8bf10 100644
--- a/gopls/internal/lsp/protocol/tsjson.go
+++ b/gopls/internal/lsp/protocol/tsjson.go
@@ -7,7 +7,7 @@
package protocol
// Code generated from version 3.17.0 of protocol/metaModel.json.
-// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-14)
+// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-23)
import "encoding/json"
diff --git a/gopls/internal/lsp/protocol/tsprotocol.go b/gopls/internal/lsp/protocol/tsprotocol.go
index becb255..54a878c 100644
--- a/gopls/internal/lsp/protocol/tsprotocol.go
+++ b/gopls/internal/lsp/protocol/tsprotocol.go
@@ -7,7 +7,7 @@
package protocol
// Code generated from version 3.17.0 of protocol/metaModel.json.
-// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-14)
+// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-23)
import "encoding/json"
@@ -667,7 +667,7 @@
*/
Detail string `json:"detail,omitempty"`
// A human-readable string that represents a doc-comment.
- Documentation string `json:"documentation,omitempty"`
+ Documentation *Or_CompletionItem_documentation `json:"documentation,omitempty"`
/*
* Indicates if this item is deprecated.
* @deprecated Use `tags` instead.
@@ -2970,8 +2970,8 @@
Text string `json:"text"`
}
-// created for Literal (Lit_TextDocumentFilter_Item1)
-type Msg_TextDocumentFilter struct { // line 14193
+// created for Literal (Lit_TextDocumentFilter_Item0)
+type Msg_TextDocumentFilter struct { // line 14160
// A language id, like `typescript`.
Language string `json:"language"`
// A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
@@ -4885,7 +4885,7 @@
* The human-readable doc-comment of this signature. Will be shown
* in the UI but can be omitted.
*/
- Documentation string `json:"documentation,omitempty"`
+ Documentation *Or_SignatureInformation_documentation `json:"documentation,omitempty"`
// The parameters of this signature.
Parameters []ParameterInformation `json:"parameters,omitempty"`
/*
diff --git a/gopls/internal/lsp/protocol/tsserver.go b/gopls/internal/lsp/protocol/tsserver.go
index 6446a01..d968010 100644
--- a/gopls/internal/lsp/protocol/tsserver.go
+++ b/gopls/internal/lsp/protocol/tsserver.go
@@ -7,7 +7,7 @@
package protocol
// Code generated from version 3.17.0 of protocol/metaModel.json.
-// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-14)
+// git hash 8de18faed635819dd2bc631d2c26ce4a18f7cf4a (as of 2023-01-23)
import (
"context"
diff --git a/gopls/internal/lsp/regtest/wrappers.go b/gopls/internal/lsp/regtest/wrappers.go
index 1207a14..cdd39e5 100644
--- a/gopls/internal/lsp/regtest/wrappers.go
+++ b/gopls/internal/lsp/regtest/wrappers.go
@@ -417,6 +417,16 @@
}
}
+// SignatureHelp wraps Editor.SignatureHelp, calling t.Fatal on error
+func (e *Env) SignatureHelp(loc protocol.Location) *protocol.SignatureHelp {
+ e.T.Helper()
+ sighelp, err := e.Editor.SignatureHelp(e.Ctx, loc)
+ if err != nil {
+ e.T.Fatal(err)
+ }
+ return sighelp
+}
+
// Completion executes a completion request on the server.
func (e *Env) Completion(loc protocol.Location) *protocol.CompletionList {
e.T.Helper()
diff --git a/gopls/internal/lsp/source/signature_help.go b/gopls/internal/lsp/source/signature_help.go
index 1d43dc5..4902496 100644
--- a/gopls/internal/lsp/source/signature_help.go
+++ b/gopls/internal/lsp/source/signature_help.go
@@ -10,6 +10,7 @@
"go/ast"
"go/token"
"go/types"
+ "strings"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/gopls/internal/lsp/protocol"
@@ -117,7 +118,7 @@
}
return &protocol.SignatureInformation{
Label: name + s.Format(),
- Documentation: s.doc,
+ Documentation: stringToSigInfoDocumentation(s.doc, snapshot.View().Options()),
Parameters: paramInfo,
}, activeParam, nil
}
@@ -134,7 +135,7 @@
activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos)
return &protocol.SignatureInformation{
Label: sig.name + sig.Format(),
- Documentation: sig.doc,
+ Documentation: stringToSigInfoDocumentation(sig.doc, snapshot.View().Options()),
Parameters: paramInfo,
}, activeParam, nil
@@ -165,3 +166,21 @@
}
return activeParam
}
+
+func stringToSigInfoDocumentation(s string, options *Options) *protocol.Or_SignatureInformation_documentation {
+ v := s
+ k := protocol.PlainText
+ if options.PreferredContentFormat == protocol.Markdown {
+ v = CommentToMarkdown(s)
+ // whether or not content is newline terminated may not matter for LSP clients,
+ // but our tests expect trailing newlines to be stripped.
+ v = strings.TrimSuffix(v, "\n") // TODO(pjw): change the golden files
+ k = protocol.Markdown
+ }
+ return &protocol.Or_SignatureInformation_documentation{
+ Value: protocol.MarkupContent{
+ Kind: k,
+ Value: v,
+ },
+ }
+}
diff --git a/gopls/internal/regtest/misc/hover_test.go b/gopls/internal/regtest/misc/hover_test.go
index 6b1d243..7054993 100644
--- a/gopls/internal/regtest/misc/hover_test.go
+++ b/gopls/internal/regtest/misc/hover_test.go
@@ -10,7 +10,9 @@
"testing"
"golang.org/x/tools/gopls/internal/lsp/fake"
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
. "golang.org/x/tools/gopls/internal/lsp/regtest"
+ "golang.org/x/tools/internal/testenv"
)
func TestHoverUnexported(t *testing.T) {
@@ -270,3 +272,62 @@
env.Hover(env.RegexpSearch("go.mod", "go")) // no panic
})
}
+
+func TestHoverCompletionMarkdown(t *testing.T) {
+ testenv.NeedsGo1Point(t, 19)
+ const source = `
+-- go.mod --
+module mod.com
+go 1.19
+-- main.go --
+package main
+// Just says [hello].
+//
+// [hello]: https://en.wikipedia.org/wiki/Hello
+func Hello() string {
+ Hello() //Here
+ return "hello"
+}
+`
+ Run(t, source, func(t *testing.T, env *Env) {
+ // Hover, Completion, and SignatureHelp should all produce markdown
+ // check that the markdown for SignatureHelp and Completion are
+ // the same, and contained in that for Hover (up to trailing \n)
+ env.OpenFile("main.go")
+ loc := env.RegexpSearch("main.go", "func (Hello)")
+ hover, _ := env.Hover(loc)
+ hoverContent := hover.Value
+
+ loc = env.RegexpSearch("main.go", "//Here")
+ loc.Range.Start.Character -= 3 // Hello(_) //Here
+ completions := env.Completion(loc)
+ signatures := env.SignatureHelp(loc)
+
+ if len(completions.Items) != 1 {
+ t.Errorf("got %d completions, expected 1", len(completions.Items))
+ }
+ if len(signatures.Signatures) != 1 {
+ t.Errorf("got %d signatures, expected 1", len(signatures.Signatures))
+ }
+ item := completions.Items[0].Documentation.Value
+ var itemContent string
+ if x, ok := item.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown {
+ t.Fatalf("%#v is not markdown", item)
+ } else {
+ itemContent = strings.Trim(x.Value, "\n")
+ }
+ sig := signatures.Signatures[0].Documentation.Value
+ var sigContent string
+ if x, ok := sig.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown {
+ t.Fatalf("%#v is not markdown", item)
+ } else {
+ sigContent = x.Value
+ }
+ if itemContent != sigContent {
+ t.Errorf("item:%q not sig:%q", itemContent, sigContent)
+ }
+ if !strings.Contains(hoverContent, itemContent) {
+ t.Errorf("hover:%q does not containt sig;%q", hoverContent, sigContent)
+ }
+ })
+}