From 44dd5717712cd41bf80691fcbe296bc704710e48 Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Fri, 3 May 2019 14:31:03 -0400 Subject: [PATCH] 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 TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/browser/README.md | 1 + internal/lsp/browser/browser.go | 67 ++++++++++++++++++++++ internal/lsp/cmd/cmd.go | 2 + internal/lsp/cmd/info.go | 84 ++++++++++++++++++++++++++++ internal/lsp/general.go | 4 ++ internal/lsp/info.1.11.go | 16 ++++++ internal/lsp/info.go | 38 +++++++++++++ internal/lsp/text_synchronization.go | 1 - internal/lsp/util.go | 33 +++++++++++ 9 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 internal/lsp/browser/README.md create mode 100644 internal/lsp/browser/browser.go create mode 100644 internal/lsp/cmd/info.go create mode 100644 internal/lsp/info.1.11.go create mode 100644 internal/lsp/info.go diff --git a/internal/lsp/browser/README.md b/internal/lsp/browser/README.md new file mode 100644 index 00000000..e5f04df4 --- /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 00000000..6867c85d --- /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 f1baea7d..1059f04b 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -118,9 +118,11 @@ func (app *Application) Run(ctx context.Context, args ...string) error { 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 00000000..25144050 --- /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 212a0390..09f0a4ec 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 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa } } } + 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 00000000..cd243393 --- /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 00000000..476ebb8b --- /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 9777e7dd..a29edd5b 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -34,7 +34,6 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo } 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 48d83fcc..244e2cbb 100644 --- a/internal/lsp/util.go +++ b/internal/lsp/util.go @@ -7,12 +7,45 @@ package lsp 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 {