aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/debug/info.go
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2023-03-15 13:19:36 -0400
committerDan Willemsen <dwillemsen@google.com>2023-03-15 14:18:08 -0400
commit09c5a32afc5b66f28f166a68afe1fc71afbf9b73 (patch)
tree194d7b0e539d014393564a256bec571e18d6533a /gopls/internal/lsp/debug/info.go
parentf10932f763d058b0dcb3acfb795c869996fef47b (diff)
parent031fc75960d487b0b15db12fb328676236a3a39c (diff)
downloadgolang-x-tools-09c5a32afc5b66f28f166a68afe1fc71afbf9b73.tar.gz
Upgrade golang-x-tools to v0.7.0HEADmastermain
Not using external_updater this time to switch to the new upstream tags. Test: treehugger Change-Id: I31488b4958a366ed7f183bb387d3e1446acc13ae
Diffstat (limited to 'gopls/internal/lsp/debug/info.go')
-rw-r--r--gopls/internal/lsp/debug/info.go254
1 files changed, 254 insertions, 0 deletions
diff --git a/gopls/internal/lsp/debug/info.go b/gopls/internal/lsp/debug/info.go
new file mode 100644
index 000000000..00752e6f9
--- /dev/null
+++ b/gopls/internal/lsp/debug/info.go
@@ -0,0 +1,254 @@
+// 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 debug exports debug information for gopls.
+package debug
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "reflect"
+ "runtime"
+ "runtime/debug"
+ "sort"
+ "strings"
+
+ "golang.org/x/tools/gopls/internal/lsp/source"
+)
+
+type PrintMode int
+
+const (
+ PlainText = PrintMode(iota)
+ Markdown
+ HTML
+ JSON
+)
+
+// Version is a manually-updated mechanism for tracking versions.
+const Version = "master"
+
+// ServerVersion is the format used by gopls to report its version to the
+// client. This format is structured so that the client can parse it easily.
+type ServerVersion struct {
+ *BuildInfo
+ Version string
+}
+
+// VersionInfo returns the build info for the gopls process. If it was not
+// built in module mode, we return a GOPATH-specific message with the
+// hardcoded version.
+func VersionInfo() *ServerVersion {
+ if info, ok := readBuildInfo(); ok {
+ return getVersion(info)
+ }
+ buildInfo := &BuildInfo{}
+ // go1.17 or earlier, part of s.BuildInfo are embedded fields.
+ buildInfo.Path = "gopls, built in GOPATH mode"
+ buildInfo.GoVersion = runtime.Version()
+ return &ServerVersion{
+ Version: Version,
+ BuildInfo: buildInfo,
+ }
+}
+
+func getVersion(info *BuildInfo) *ServerVersion {
+ return &ServerVersion{
+ Version: Version,
+ BuildInfo: info,
+ }
+}
+
+// PrintServerInfo writes HTML debug info to w for the Instance.
+func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) {
+ section(w, HTML, "Server Instance", func() {
+ fmt.Fprintf(w, "Start time: %v\n", i.StartTime)
+ fmt.Fprintf(w, "LogFile: %s\n", i.Logfile)
+ fmt.Fprintf(w, "Working directory: %s\n", i.Workdir)
+ fmt.Fprintf(w, "Address: %s\n", i.ServerAddress)
+ fmt.Fprintf(w, "Debug address: %s\n", i.DebugAddress())
+ })
+ PrintVersionInfo(ctx, w, true, HTML)
+ section(w, HTML, "Command Line", func() {
+ fmt.Fprintf(w, "<a href=/debug/pprof/cmdline>cmdline</a>")
+ })
+}
+
+// PrintVersionInfo writes version information to w, using the output format
+// specified by mode. verbose controls whether additional information is
+// written, including section headers.
+func PrintVersionInfo(_ context.Context, w io.Writer, verbose bool, mode PrintMode) error {
+ info := VersionInfo()
+ if mode == JSON {
+ return printVersionInfoJSON(w, info)
+ }
+
+ if !verbose {
+ printBuildInfo(w, info, false, mode)
+ return nil
+ }
+ section(w, mode, "Build info", func() {
+ printBuildInfo(w, info, true, mode)
+ })
+ return nil
+}
+
+func printVersionInfoJSON(w io.Writer, info *ServerVersion) error {
+ js, err := json.MarshalIndent(info, "", "\t")
+ if err != nil {
+ return err
+ }
+ _, err = fmt.Fprint(w, string(js))
+ return err
+}
+
+func section(w io.Writer, mode PrintMode, title string, body func()) {
+ switch mode {
+ case PlainText:
+ fmt.Fprintln(w, title)
+ fmt.Fprintln(w, strings.Repeat("-", len(title)))
+ body()
+ case Markdown:
+ fmt.Fprintf(w, "#### %s\n\n```\n", title)
+ body()
+ fmt.Fprintf(w, "```\n")
+ case HTML:
+ fmt.Fprintf(w, "<h3>%s</h3>\n<pre>\n", title)
+ body()
+ fmt.Fprint(w, "</pre>\n")
+ }
+}
+
+func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) {
+ fmt.Fprintf(w, "%v %v\n", info.Path, Version)
+ printModuleInfo(w, info.Main, mode)
+ if !verbose {
+ return
+ }
+ for _, dep := range info.Deps {
+ printModuleInfo(w, *dep, mode)
+ }
+ fmt.Fprintf(w, "go: %v\n", info.GoVersion)
+}
+
+func printModuleInfo(w io.Writer, m debug.Module, _ PrintMode) {
+ 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")
+}
+
+type field struct {
+ index []int
+}
+
+var fields []field
+
+// find all the options. The presumption is that the Options are nested structs
+// and that pointers don't need to be dereferenced
+func swalk(t reflect.Type, ix []int, indent string) {
+ switch t.Kind() {
+ case reflect.Struct:
+ for i := 0; i < t.NumField(); i++ {
+ fld := t.Field(i)
+ ixx := append(append([]int{}, ix...), i)
+ swalk(fld.Type, ixx, indent+". ")
+ }
+ default:
+ // everything is either a struct or a field (that's an assumption about Options)
+ fields = append(fields, field{ix})
+ }
+}
+
+type sessionOption struct {
+ Name string
+ Type string
+ Current string
+ Default string
+}
+
+func showOptions(o *source.Options) []sessionOption {
+ var out []sessionOption
+ t := reflect.TypeOf(*o)
+ swalk(t, []int{}, "")
+ v := reflect.ValueOf(*o)
+ do := reflect.ValueOf(*source.DefaultOptions())
+ for _, f := range fields {
+ val := v.FieldByIndex(f.index)
+ def := do.FieldByIndex(f.index)
+ tx := t.FieldByIndex(f.index)
+ is := strVal(val)
+ was := strVal(def)
+ out = append(out, sessionOption{
+ Name: tx.Name,
+ Type: tx.Type.String(),
+ Current: is,
+ Default: was,
+ })
+ }
+ sort.Slice(out, func(i, j int) bool {
+ rd := out[i].Current == out[i].Default
+ ld := out[j].Current == out[j].Default
+ if rd != ld {
+ return ld
+ }
+ return out[i].Name < out[j].Name
+ })
+ return out
+}
+
+func strVal(val reflect.Value) string {
+ switch val.Kind() {
+ case reflect.Bool:
+ return fmt.Sprintf("%v", val.Interface())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return fmt.Sprintf("%v", val.Interface())
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return fmt.Sprintf("%v", val.Interface())
+ case reflect.Uintptr, reflect.UnsafePointer:
+ return fmt.Sprintf("0x%x", val.Pointer())
+ case reflect.Complex64, reflect.Complex128:
+ return fmt.Sprintf("%v", val.Complex())
+ case reflect.Array, reflect.Slice:
+ ans := []string{}
+ for i := 0; i < val.Len(); i++ {
+ ans = append(ans, strVal(val.Index(i)))
+ }
+ sort.Strings(ans)
+ return fmt.Sprintf("%v", ans)
+ case reflect.Chan, reflect.Func, reflect.Ptr:
+ return val.Kind().String()
+ case reflect.Struct:
+ var x source.Analyzer
+ if val.Type() != reflect.TypeOf(x) {
+ return val.Kind().String()
+ }
+ // this is sort of ugly, but usable
+ str := val.FieldByName("Analyzer").Elem().FieldByName("Doc").String()
+ ix := strings.Index(str, "\n")
+ if ix == -1 {
+ ix = len(str)
+ }
+ return str[:ix]
+ case reflect.String:
+ return fmt.Sprintf("%q", val.Interface())
+ case reflect.Map:
+ ans := []string{}
+ iter := val.MapRange()
+ for iter.Next() {
+ k := iter.Key()
+ v := iter.Value()
+ ans = append(ans, fmt.Sprintf("%s:%s, ", strVal(k), strVal(v)))
+ }
+ sort.Strings(ans)
+ return fmt.Sprintf("%v", ans)
+ }
+ return fmt.Sprintf("??%s??", val.Type())
+}