internal/lsp/debug: improve readability of session options

I find the session options page in gopls debug a bit hard to read. This
change aims to improve readability by:

- Sorting non-default options first (and alphabetically by name)
- Use bold text for option name
- Hide the "current value" if it's string representation equals the
  default value's string representation

Change-Id: I93606ae788b97e46dc1d3aff420bb58f4c4d9674
Reviewed-on: https://go-review.googlesource.com/c/tools/+/352130
Trust: Pontus Leitzler <leitzler@gmail.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/debug/info.go b/internal/lsp/debug/info.go
index 3533b36..6d4861c 100644
--- a/internal/lsp/debug/info.go
+++ b/internal/lsp/debug/info.go
@@ -183,10 +183,15 @@
 	}
 }
 
-func showOptions(o *source.Options) []string {
-	// non-breaking spaces for indenting current and defaults when they are on a separate line
-	const indent = "\u00a0\u00a0\u00a0\u00a0\u00a0"
-	var ans strings.Builder
+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)
@@ -195,17 +200,26 @@
 		val := v.FieldByIndex(f.index)
 		def := do.FieldByIndex(f.index)
 		tx := t.FieldByIndex(f.index)
-		prefix := fmt.Sprintf("%s (type is %s): ", tx.Name, tx.Type)
 		is := strVal(val)
 		was := strVal(def)
-		if len(is) < 30 && len(was) < 30 {
-			fmt.Fprintf(&ans, "%s current:%s, default:%s\n", prefix, is, was)
-		} else {
-			fmt.Fprintf(&ans, "%s\n%scurrent:%s\n%sdefault:%s\n", prefix, indent, is, indent, was)
-		}
+		out = append(out, sessionOption{
+			Name:    tx.Name,
+			Type:    tx.Type.String(),
+			Current: is,
+			Default: was,
+		})
 	}
-	return strings.Split(ans.String(), "\n")
+	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:
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index b3699e1..b6dba60 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -791,7 +791,7 @@
 		}
 		return s
 	},
-	"options": func(s *cache.Session) []string {
+	"options": func(s *cache.Session) []sessionOption {
 		return showOptions(s.Options())
 	},
 })
@@ -919,7 +919,11 @@
 <h2>Overlays</h2>
 <ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
 <h2>Options</h2>
-{{range options .}}<p>{{.}}{{end}}
+{{range options .}}
+<p><b>{{.Name}}</b> {{.Type}}</p>
+<p><i>default:</i> {{.Default}}</p>
+{{if ne .Default .Current}}<p><i>current:</i> {{.Current}}</p>{{end}}
+{{end}}
 {{end}}
 `))