internal/lsp: add version and bug commands
Also log gopls version information on startup in server mode.
Change-Id: If7bf85d19f993430709b1fae83083e6fdfe1b2ca
Reviewed-on: https://go-review.googlesource.com/c/tools/+/175199
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/browser/README.md b/internal/lsp/browser/README.md
new file mode 100644
index 0000000..e5f04df
--- /dev/null
+++ b/internal/lsp/browser/README.md
@@ -0,0 +1 @@
+This package is a copy of cmd/internal/browser from the go distribution
\ No newline at end of file
diff --git a/internal/lsp/browser/browser.go b/internal/lsp/browser/browser.go
new file mode 100644
index 0000000..6867c85
--- /dev/null
+++ b/internal/lsp/browser/browser.go
@@ -0,0 +1,67 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package browser provides utilities for interacting with users' browsers.
+package browser
+
+import (
+ "os"
+ "os/exec"
+ "runtime"
+ "time"
+)
+
+// Commands returns a list of possible commands to use to open a url.
+func Commands() [][]string {
+ var cmds [][]string
+ if exe := os.Getenv("BROWSER"); exe != "" {
+ cmds = append(cmds, []string{exe})
+ }
+ switch runtime.GOOS {
+ case "darwin":
+ cmds = append(cmds, []string{"/usr/bin/open"})
+ case "windows":
+ cmds = append(cmds, []string{"cmd", "/c", "start"})
+ default:
+ if os.Getenv("DISPLAY") != "" {
+ // xdg-open is only for use in a desktop environment.
+ cmds = append(cmds, []string{"xdg-open"})
+ }
+ }
+ cmds = append(cmds,
+ []string{"chrome"},
+ []string{"google-chrome"},
+ []string{"chromium"},
+ []string{"firefox"},
+ )
+ return cmds
+}
+
+// Open tries to open url in a browser and reports whether it succeeded.
+func Open(url string) bool {
+ for _, args := range Commands() {
+ cmd := exec.Command(args[0], append(args[1:], url)...)
+ if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) {
+ return true
+ }
+ }
+ return false
+}
+
+// appearsSuccessful reports whether the command appears to have run successfully.
+// If the command runs longer than the timeout, it's deemed successful.
+// If the command runs within the timeout, it's deemed successful if it exited cleanly.
+func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool {
+ errc := make(chan error, 1)
+ go func() {
+ errc <- cmd.Wait()
+ }()
+
+ select {
+ case <-time.After(timeout):
+ return true
+ case err := <-errc:
+ return err == nil
+ }
+}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index f1baea7..1059f04 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -118,9 +118,11 @@
func (app *Application) commands() []tool.Application {
return []tool.Application{
&app.Serve,
+ &bug{},
&check{app: app},
&format{app: app},
&query{app: app},
+ &version{app: app},
}
}
diff --git a/internal/lsp/cmd/info.go b/internal/lsp/cmd/info.go
new file mode 100644
index 0000000..2514405
--- /dev/null
+++ b/internal/lsp/cmd/info.go
@@ -0,0 +1,84 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cmd
+
+import (
+ "bytes"
+ "context"
+ "flag"
+ "fmt"
+ "net/url"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/internal/lsp"
+ "golang.org/x/tools/internal/lsp/browser"
+)
+
+// version implements the version command.
+type version struct {
+ app *Application
+}
+
+// bug implements the bug command.
+type bug struct{}
+
+func (v *version) Name() string { return "version" }
+func (v *version) Usage() string { return "" }
+func (v *version) ShortHelp() string { return "print the gopls version information" }
+func (v *version) DetailedHelp(f *flag.FlagSet) {
+ fmt.Fprint(f.Output(), ``)
+ f.PrintDefaults()
+}
+
+// Run collects some basic information and then prepares an issue ready to
+// be reported.
+func (v *version) Run(ctx context.Context, args ...string) error {
+ lsp.PrintVersionInfo(os.Stdout, v.app.Verbose, false)
+ return nil
+}
+
+func (b *bug) Name() string { return "bug" }
+func (b *bug) Usage() string { return "" }
+func (b *bug) ShortHelp() string { return "report a bug in gopls" }
+func (b *bug) DetailedHelp(f *flag.FlagSet) {
+ fmt.Fprint(f.Output(), ``)
+ f.PrintDefaults()
+}
+
+const goplsBugPrefix = "gopls: "
+const goplsBugHeader = `Please answer these questions before submitting your issue. Thanks!
+
+#### What did you do?
+If possible, provide a recipe for reproducing the error.
+A complete runnable program is good.
+A link on play.golang.org is better.
+A failing unit test is the best.
+
+#### What did you expect to see?
+
+
+#### What did you see instead?
+
+
+`
+
+// Run collects some basic information and then prepares an issue ready to
+// be reported.
+func (b *bug) Run(ctx context.Context, args ...string) error {
+ buf := &bytes.Buffer{}
+ fmt.Fprint(buf, goplsBugHeader)
+ lsp.PrintVersionInfo(buf, true, true)
+ body := buf.String()
+ title := strings.Join(args, " ")
+ if !strings.HasPrefix(title, goplsBugPrefix) {
+ title = goplsBugPrefix + title
+ }
+ if !browser.Open("https://github.com/golang/go/issues/new?title=" + url.QueryEscape(title) + "&body=" + url.QueryEscape(body)) {
+ fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n")
+ fmt.Print(body)
+ }
+ return nil
+}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 212a039..09f0a4e 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -5,6 +5,7 @@
package lsp
import (
+ "bytes"
"context"
"fmt"
"os"
@@ -139,6 +140,9 @@
}
}
}
+ buf := &bytes.Buffer{}
+ PrintVersionInfo(buf, true, false)
+ s.log.Infof(ctx, "%s", buf)
return nil
}
diff --git a/internal/lsp/info.1.11.go b/internal/lsp/info.1.11.go
new file mode 100644
index 0000000..cd24339
--- /dev/null
+++ b/internal/lsp/info.1.11.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.12
+
+package lsp
+
+import (
+ "fmt"
+ "io"
+)
+
+func printBuildInfo(w io.Writer, verbose bool) {
+ fmt.Fprintf(w, "no module information, gopls not build with go 1.11 or earlier\n")
+}
diff --git a/internal/lsp/info.go b/internal/lsp/info.go
new file mode 100644
index 0000000..476ebb8
--- /dev/null
+++ b/internal/lsp/info.go
@@ -0,0 +1,38 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.12
+
+package lsp
+
+import (
+ "fmt"
+ "io"
+ "runtime/debug"
+)
+
+func printBuildInfo(w io.Writer, verbose bool) {
+ if info, ok := debug.ReadBuildInfo(); ok {
+ fmt.Fprintf(w, "%v\n", info.Path)
+ printModuleInfo(w, &info.Main)
+ if verbose {
+ for _, dep := range info.Deps {
+ printModuleInfo(w, dep)
+ }
+ }
+ } else {
+ fmt.Fprintf(w, "no module information, gopls not built in module mode\n")
+ }
+}
+
+func printModuleInfo(w io.Writer, m *debug.Module) {
+ fmt.Fprintf(w, " %s@%s", m.Path, m.Version)
+ if m.Sum != "" {
+ fmt.Fprintf(w, " %s", m.Sum)
+ }
+ if m.Replace != nil {
+ fmt.Fprintf(w, " => %v", m.Replace.Path)
+ }
+ fmt.Fprintf(w, "\n")
+}
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 9777e7d..a29edd5 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -34,7 +34,6 @@
}
text = change.Text
}
- s.log.Debugf(ctx, "didChange: %s", params.TextDocument.URI)
return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), text)
}
diff --git a/internal/lsp/util.go b/internal/lsp/util.go
index 48d83fc..244e2cb 100644
--- a/internal/lsp/util.go
+++ b/internal/lsp/util.go
@@ -7,12 +7,45 @@
import (
"context"
"fmt"
+ "io"
+ "os/exec"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)
+// This writes the version and environment information to a writer.
+func PrintVersionInfo(w io.Writer, verbose bool, markdown bool) {
+ if !verbose {
+ printBuildInfo(w, false)
+ return
+ }
+ fmt.Fprint(w, "#### Build info\n\n")
+ if markdown {
+ fmt.Fprint(w, "```\n")
+ }
+ printBuildInfo(w, true)
+ fmt.Fprint(w, "\n")
+ if markdown {
+ fmt.Fprint(w, "```\n")
+ }
+ fmt.Fprint(w, "\n#### Go info\n\n")
+ if markdown {
+ fmt.Fprint(w, "```\n")
+ }
+ cmd := exec.Command("go", "version")
+ cmd.Stdout = w
+ cmd.Run()
+ fmt.Fprint(w, "\n")
+ cmd = exec.Command("go", "env")
+ cmd.Stdout = w
+ cmd.Run()
+ if markdown {
+ fmt.Fprint(w, "```\n")
+ }
+}
+
func newColumnMap(ctx context.Context, v source.View, uri span.URI) (source.File, *protocol.ColumnMapper, error) {
f, err := v.GetFile(ctx, uri)
if err != nil {