gopls/doc: generate settings JSON, docs

gopls has many settings. We want to automatically generate
documentation, and we want to allow clients to perform their own
validation if they so desire.

Using all three of AST, type information, and reflection, generate a
JSON description of the settings and their default values. Add a gopls
command that prints it. Add a documentation generator that uses it to
write settings.md.

I assumed that everything not explicitly documented was experimental,
and moved it into that section. I also moved expandWorkspaceToModule to
experimental; I hope it's not long for this world, personally.

Along the way, rename many fields, make the enum matching case
insensitive, and add a stringer call so that the defaults print nicely.

Fixes golang/go#33544.

Change-Id: Ibb652002933e355ed3c6038d6ca48345b39b3025
Reviewed-on: https://go-review.googlesource.com/c/tools/+/252322
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/doc/generate.go b/gopls/doc/generate.go
new file mode 100644
index 0000000..5948101
--- /dev/null
+++ b/gopls/doc/generate.go
@@ -0,0 +1,81 @@
+// Copyright 2020 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.
+
+// Command generate updates settings.md from the UserOptions struct.
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+
+	"golang.org/x/tools/internal/lsp/source"
+)
+
+func main() {
+	if err := doMain(); err != nil {
+		fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
+		os.Exit(1)
+	}
+}
+
+func doMain() error {
+	var opts map[string][]option
+	if err := json.Unmarshal([]byte(source.OptionsJson), &opts); err != nil {
+		return err
+	}
+
+	doc, err := ioutil.ReadFile("gopls/doc/settings.md")
+	if err != nil {
+		return err
+	}
+
+	content, err := rewriteDoc(doc, opts)
+	if err != nil {
+		return err
+	}
+
+	if err := ioutil.WriteFile("gopls/doc/settings.md", content, 0); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+type option struct {
+	Name    string
+	Type    string
+	Doc     string
+	Default string
+}
+
+func rewriteDoc(doc []byte, categories map[string][]option) ([]byte, error) {
+	var err error
+	for _, cat := range []string{"User", "Experimental"} {
+		doc, err = rewriteSection(doc, categories, cat)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return doc, nil
+}
+
+func rewriteSection(doc []byte, categories map[string][]option, category string) ([]byte, error) {
+	section := bytes.NewBuffer(nil)
+	for _, opt := range categories[category] {
+		fmt.Fprintf(section, "### **%v** *%v*\n%v\nDefault: `%v`.\n", opt.Name, opt.Type, opt.Doc, opt.Default)
+	}
+	re := regexp.MustCompile(fmt.Sprintf(`(?s)<!-- BEGIN %v.* -->\n(.*?)<!-- END %v.* -->`, category, category))
+	idx := re.FindSubmatchIndex(doc)
+	if idx == nil {
+		return nil, fmt.Errorf("could not find section %v", category)
+	}
+	result := append([]byte(nil), doc[:idx[2]]...)
+	result = append(result, section.Bytes()...)
+	result = append(result, doc[idx[3]:]...)
+	return result, nil
+}
diff --git a/gopls/doc/generate_test.go b/gopls/doc/generate_test.go
new file mode 100644
index 0000000..a165ea6
--- /dev/null
+++ b/gopls/doc/generate_test.go
@@ -0,0 +1,34 @@
+// Copyright 2020 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 main
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/testenv"
+)
+
+func TestGenerated(t *testing.T) {
+	testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
+
+	var opts map[string][]option
+	if err := json.Unmarshal([]byte(source.OptionsJson), &opts); err != nil {
+		t.Fatal(err)
+	}
+
+	doc, err := ioutil.ReadFile("settings.md")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	got, err := rewriteDoc(doc, opts)
+	if !bytes.Equal(got, doc) {
+		t.Error("settings.md needs updating. run: `go run gopls/doc/generate.go` from the root of tools.")
+	}
+}
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 79ed3d2..c8b1299 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -17,17 +17,19 @@
 
 Below is the list of settings that are officially supported for `gopls`.
 
-### **buildFlags** *array of strings*
+<!-- BEGIN User: DO NOT MANUALLY EDIT THIS SECTION -->
+### **buildFlags** *[]string*
+buildFlags is the set of flags passed on to the build system when invoked.
+It is applied to queries like `go list`, which is used when discovering files.
+The most common use is to set `-tags`.
 
-This is the set of flags passed on to the build system when invoked. It is applied to queries like `go list`, which is used when discovering files. The most common use is to set `-tags`.
+Default: `[]`.
+### **env** *[]string*
+env adds environment variables to external commands run by `gopls`, most notably `go list`.
 
-### **env** *map of string to value*
-
-This can be used to add environment variables. These will not affect `gopls` itself, but will be used for any external commands it invokes.
-
-### **hoverKind** *string*
-
-This controls the information that appears in the hover text.
+Default: `[]`.
+### **hoverKind** *golang.org/x/tools/internal/lsp/source.HoverKind*
+hoverKind controls the information that appears in the hover text.
 It must be one of:
 * `"NoDocumentation"`
 * `"SynopsisDocumentation"`
@@ -38,47 +40,39 @@
 * `"SingleLine"`
 * `"Structured"`
 
-Default: `"SynopsisDocumentation"`.
-
-### **usePlaceholders** *boolean*
-
-If true, then completion responses may contain placeholders for function parameters or struct fields.
+Default: `"FullDocumentation"`.
+### **usePlaceholders** *bool*
+placeholders enables placeholders for function parameters or struct fields in completion responses.
 
 Default: `false`.
-
 ### **linkTarget** *string*
-
-This controls where points documentation for given package in `textDocument/documentLink`.
+linkTarget controls where documentation links go.
 It might be one of:
 
 * `"godoc.org"`
 * `"pkg.go.dev"`
+
 If company chooses to use its own `godoc.org`, its address can be used as well.
 
 Default: `"pkg.go.dev"`.
-
 ### **local** *string*
-
-This is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.
+local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.
 It should be the prefix of the import path whose imports should be grouped separately.
 
 Default: `""`.
+### **gofumpt** *bool*
+gofumpt indicates if we should run gofumpt formatting.
 
-### **expandWorkspaceToModule** *boolean*
-
-This is true if `gopls` should expand the scope of the workspace to include the
-modules containing the workspace folders. Set this to false to avoid loading
-your entire module. This is particularly useful for those working in a monorepo.
-
-Default: `true`.
+Default: `false`.
+<!-- END User: DO NOT MANUALLY EDIT THIS SECTION -->
 
 ## Experimental
 
 The below settings are considered experimental. They may be deprecated or changed in the future. They are typically used to test experimental opt-in features or to disable features.
 
+<!-- BEGIN Experimental: DO NOT MANUALLY EDIT THIS SECTION -->
 ### **analyses** *map[string]bool*
-
-Analyses specify analyses that the user would like to enable or disable.
+analyses specify analyses that the user would like to enable or disable.
 A map of the names of analysis passes that should be enabled/disabled.
 A full list of analyzers that gopls uses can be found [here](analyzers.md)
 
@@ -92,15 +86,15 @@
 ...
 ```
 
+Default: `null`.
 ### **codelens** *map[string]bool*
-
-Overrides the enabled/disabled state of various code lenses. Currently, we
+overrides the enabled/disabled state of various code lenses. Currently, we
 support several code lenses:
 
-* `generate`: [default: enabled] run `go generate` as specified by a `//go:generate` directive.
-* `upgrade_dependency`: [default: enabled] upgrade a dependency listed in a `go.mod` file.
-* `test`: [default: disabled] run `go test -run` for a test func.
-* `gc_details`: [default: disabled] Show the gc compiler's choices for inline analysis and escaping.
+* `generate`: run `go generate` as specified by a `//go:generate` directive.
+* `upgrade_dependency`: upgrade a dependency listed in a `go.mod` file.
+* `test`: run `go test -run` for a test func.
+* `gc_details`: Show the gc compiler's choices for inline analysis and escaping.
 
 Example Usage:
 ```json5
@@ -113,23 +107,18 @@
 ...
 }
 ```
-### **completionDocumentation** *boolean*
 
-If false, indicates that the user does not want documentation with completion results.
-
-Default value: `true`.
-
-### **completeUnimported** *boolean*
-
-If true, the completion engine is allowed to make suggestions for packages that you do not currently import.
-
-Default: `false`.
-
-### **deepCompletion** *boolean*
-
-If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
+Default: `{"gc_details":false,"generate":true,"regenerate_cgo":true,"tidy":true,"upgrade_dependency":true,"vendor":true}`.
+### **completionDocumentation** *bool*
+completionDocumentation enables documentation with completion results.
 
 Default: `true`.
+### **completeUnimported** *bool*
+completeUnimported enables completion for packages that you do not currently import.
+
+Default: `true`.
+### **deepCompletion** *bool*
+deepCompletion If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
 
 Consider this example:
 
@@ -150,42 +139,70 @@
 
 At the location of the `<>` in this program, deep completion would suggest the result `x.str`.
 
-### **fuzzyMatching** *boolean*
+Default: `true`.
+### **matcher** *golang.org/x/tools/internal/lsp/source.Matcher*
+matcher sets the algorithm that is used when calculating completion candidates. Must be one of:
 
-If true, this enables server side fuzzy matching of completion candidates.
+* `"fuzzy"`
+* `"caseSensitive"`
+* `"caseInsensitive"`
+
+Default: `"Fuzzy"`.
+### **annotations** *map[string]bool*
+annotations suppress various kinds of optimization diagnostics
+that would be reported by the gc_details command.
+  noNilcheck suppresses display of nilchecks.
+  noEscape suppresses escape choices.
+  noInline suppresses inlining choices.
+  noBounds suppresses bounds checking diagnositcs.
+
+Default: `null`.
+### **staticcheck** *bool*
+staticcheck enables additional analyses from staticcheck.io.
+
+Default: `false`.
+### **symbolMatcher** *golang.org/x/tools/internal/lsp/source.SymbolMatcher*
+symbolMatcher sets the algorithm that is used when finding workspace symbols. Must be one of:
+
+* `"fuzzy"`
+* `"caseSensitive"`
+* `"caseInsensitive"`
+
+Default: `"SymbolFuzzy"`.
+### **symbolStyle** *golang.org/x/tools/internal/lsp/source.SymbolStyle*
+symbolStyle specifies what style of symbols to return in symbol requests. Must be one of:
+
+* `"full"`
+* `"dynamic"`
+* `"package"`
+
+Default: `"PackageQualifiedSymbols"`.
+### **linksInHover** *bool*
+linksInHover toggles the presence of links to documentation in hover.
 
 Default: `true`.
+### **tempModfile** *bool*
+tempModfile controls the use of the -modfile flag in Go 1.14.
 
-### **matcher** *string*
+Default: `true`.
+### **importShortcut** *golang.org/x/tools/internal/lsp/source.ImportShortcut*
+importShortcut specifies whether import statements should link to
+documentation or go to definitions. Must be one of:
 
-Defines the algorithm that is used when calculating completion candidates. Must be one of:
+* `"both"`
+* `"link"`
+* `"definition"`
 
-* `"fuzzy"`
-* `"caseSensitive"`
-* `"caseInsensitive"`
+Default: `"Both"`.
+### **verboseWorkDoneProgress** *bool*
+verboseWorkDoneProgress controls whether the LSP server should send
+progress reports for all work done outside the scope of an RPC.
 
-Default: `"caseInsensitive"`
+Default: `false`.
+### **expandWorkspaceToModule** *bool*
+expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the
+modules containing the workspace folders. Set this to false to avoid loading
+your entire module. This is particularly useful for those working in a monorepo.
 
-### **annotations** *map[string]bool*
-
-**noBounds** suppresses gc_details diagnostics about bounds checking.
-
-**noEscape** suppresses gc_details diagnostics about escape analysis.
-
-**noInline** suppresses gc_details diagnostics about inlining.
-
-**noNilcheck** suppresses gc_details diagnostics about generated nil checks.
-
-### **staticcheck** *boolean*
-
-If true, it enables the use of the staticcheck.io analyzers.
-
-### **symbolMatcher** *string*
-
-Defines the algorithm that is used when calculating workspace symbol results. Must be one of:
-
-* `"fuzzy"`
-* `"caseSensitive"`
-* `"caseInsensitive"`
-
-Default: `"fuzzy"`.
+Default: `true`.
+<!-- END Experimental: DO NOT MANUALLY EDIT THIS SECTION -->
diff --git a/gopls/internal/hooks/analysis.go b/gopls/internal/hooks/analysis.go
index f46a93f..2f263c7 100644
--- a/gopls/internal/hooks/analysis.go
+++ b/gopls/internal/hooks/analysis.go
@@ -12,7 +12,7 @@
 )
 
 func updateAnalyzers(options *source.Options) {
-	if options.StaticCheck {
+	if options.Staticcheck {
 		for _, a := range simple.Analyzers {
 			options.AddDefaultAnalyzer(a)
 		}
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
index feceea3..5f43202 100644
--- a/gopls/test/gopls_test.go
+++ b/gopls/test/gopls_test.go
@@ -30,7 +30,7 @@
 }
 
 func commandLineOptions(options *source.Options) {
-	options.StaticCheck = true
+	options.Staticcheck = true
 	options.GoDiff = false
 	hooks.Options(options)
 }
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index bfb68ab..964dba1 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -337,7 +337,7 @@
 	// v.goEnv is immutable -- changes make a new view. Options can change.
 	// We can't compare build flags directly because we may add -modfile.
 	v.optionsMu.Lock()
-	localPrefix := v.options.LocalPrefix
+	localPrefix := v.options.Local
 	currentBuildFlags := v.options.BuildFlags
 	changed := !reflect.DeepEqual(currentBuildFlags, v.cachedBuildFlags) ||
 		v.options.VerboseOutput != (v.processEnv.Logf != nil) ||
@@ -451,7 +451,7 @@
 // envLocked returns the environment and build flags for the current view.
 // It assumes that the caller is holding the view's optionsMu.
 func (v *View) envLocked() ([]string, []string) {
-	env := append([]string{}, v.options.Env...)
+	env := append(os.Environ(), v.options.Env...)
 	buildFlags := append([]string{}, v.options.BuildFlags...)
 	return env, buildFlags
 }
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 3d80051..35fea61 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -170,6 +170,7 @@
 		&app.Serve,
 		&version{app: app},
 		&bug{},
+		&settingsJson{},
 	}
 }
 
diff --git a/internal/lsp/cmd/info.go b/internal/lsp/cmd/info.go
index 8c013de..2884b89 100644
--- a/internal/lsp/cmd/info.go
+++ b/internal/lsp/cmd/info.go
@@ -15,6 +15,7 @@
 
 	"golang.org/x/tools/internal/lsp/browser"
 	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/source"
 )
 
 // version implements the version command.
@@ -81,3 +82,18 @@
 	}
 	return nil
 }
+
+type settingsJson struct{}
+
+func (sj *settingsJson) Name() string      { return "settings-json" }
+func (sj *settingsJson) Usage() string     { return "" }
+func (sj *settingsJson) ShortHelp() string { return "print json describing gopls settings" }
+func (sj *settingsJson) DetailedHelp(f *flag.FlagSet) {
+	fmt.Fprint(f.Output(), ``)
+	f.PrintDefaults()
+}
+
+func (sj *settingsJson) Run(ctx context.Context, args ...string) error {
+	fmt.Fprintf(os.Stdout, source.OptionsJson)
+	return nil
+}
diff --git a/internal/lsp/code_lens.go b/internal/lsp/code_lens.go
index 558344d..b9891ab 100644
--- a/internal/lsp/code_lens.go
+++ b/internal/lsp/code_lens.go
@@ -32,7 +32,7 @@
 	}
 	var result []protocol.CodeLens
 	for lens, lf := range lensFuncs {
-		if !snapshot.View().Options().EnabledCodeLens[lens] {
+		if !snapshot.View().Options().Codelens[lens] {
 			continue
 		}
 		added, err := lf(ctx, snapshot, fh)
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index c8992f5..7c6df27 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -14,7 +14,7 @@
 	got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = false
 		opts.Matcher = source.CaseInsensitive
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 		opts.InsertTextFormat = protocol.SnippetTextFormat
 		if !strings.Contains(string(src.URI()), "literal") {
 			opts.LiteralCompletions = false
@@ -29,10 +29,10 @@
 
 func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
 	list := r.callCompletion(t, src, func(opts *source.Options) {
-		opts.Placeholders = placeholders
+		opts.UsePlaceholders = placeholders
 		opts.DeepCompletion = true
 		opts.Matcher = source.Fuzzy
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got := tests.FindItem(list, *items[expected.CompletionItem])
 	want := expected.PlainSnippet
@@ -57,7 +57,7 @@
 	got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = true
 		opts.Matcher = source.CaseInsensitive
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
@@ -70,7 +70,7 @@
 	got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = true
 		opts.Matcher = source.Fuzzy
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
@@ -82,7 +82,7 @@
 func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
 	got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.Matcher = source.CaseSensitive
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
@@ -95,7 +95,7 @@
 	got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = true
 		opts.Matcher = source.Fuzzy
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 		opts.LiteralCompletions = true
 	})
 	want := expected(t, test, items)
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 57b4e95..4c17dec 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -29,7 +29,7 @@
 	session := cache.NewSession(ctx)
 	options := tests.DefaultOptions()
 	options.TempModfile = true
-	options.Env = append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT=")
+	options.Env = []string{"GOPACKAGESDRIVER=off", "GOROOT="}
 
 	// Make sure to copy the test directory to a temporary directory so we do not
 	// modify the test code or add go.sum files when we run the tests.
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index 9ae9e66..f1e16a8 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -574,10 +574,10 @@
 		opts: &completionOptions{
 			matcher:           opts.Matcher,
 			deepCompletion:    opts.DeepCompletion,
-			unimported:        opts.UnimportedCompletion,
+			unimported:        opts.CompleteUnimported,
 			documentation:     opts.CompletionDocumentation,
 			fullDocumentation: opts.HoverKind == source.FullDocumentation,
-			placeholders:      opts.Placeholders,
+			placeholders:      opts.UsePlaceholders,
 			literal:           opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
 			budget:            opts.CompletionBudget,
 		},
diff --git a/internal/lsp/source/enums_string.go b/internal/lsp/source/enums_string.go
new file mode 100644
index 0000000..f963ffc
--- /dev/null
+++ b/internal/lsp/source/enums_string.go
@@ -0,0 +1,103 @@
+// Code generated by "stringer -output enums_string.go -type ImportShortcut,HoverKind,Matcher,SymbolMatcher,SymbolStyle"; DO NOT EDIT.
+
+package source
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[Both-0]
+	_ = x[Link-1]
+	_ = x[Definition-2]
+}
+
+const _ImportShortcut_name = "BothLinkDefinition"
+
+var _ImportShortcut_index = [...]uint8{0, 4, 8, 18}
+
+func (i ImportShortcut) String() string {
+	if i < 0 || i >= ImportShortcut(len(_ImportShortcut_index)-1) {
+		return "ImportShortcut(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _ImportShortcut_name[_ImportShortcut_index[i]:_ImportShortcut_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[SingleLine-0]
+	_ = x[NoDocumentation-1]
+	_ = x[SynopsisDocumentation-2]
+	_ = x[FullDocumentation-3]
+	_ = x[Structured-4]
+}
+
+const _HoverKind_name = "SingleLineNoDocumentationSynopsisDocumentationFullDocumentationStructured"
+
+var _HoverKind_index = [...]uint8{0, 10, 25, 46, 63, 73}
+
+func (i HoverKind) String() string {
+	if i < 0 || i >= HoverKind(len(_HoverKind_index)-1) {
+		return "HoverKind(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _HoverKind_name[_HoverKind_index[i]:_HoverKind_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[Fuzzy-0]
+	_ = x[CaseInsensitive-1]
+	_ = x[CaseSensitive-2]
+}
+
+const _Matcher_name = "FuzzyCaseInsensitiveCaseSensitive"
+
+var _Matcher_index = [...]uint8{0, 5, 20, 33}
+
+func (i Matcher) String() string {
+	if i < 0 || i >= Matcher(len(_Matcher_index)-1) {
+		return "Matcher(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _Matcher_name[_Matcher_index[i]:_Matcher_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[SymbolFuzzy-0]
+	_ = x[SymbolCaseInsensitive-1]
+	_ = x[SymbolCaseSensitive-2]
+}
+
+const _SymbolMatcher_name = "SymbolFuzzySymbolCaseInsensitiveSymbolCaseSensitive"
+
+var _SymbolMatcher_index = [...]uint8{0, 11, 32, 51}
+
+func (i SymbolMatcher) String() string {
+	if i < 0 || i >= SymbolMatcher(len(_SymbolMatcher_index)-1) {
+		return "SymbolMatcher(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _SymbolMatcher_name[_SymbolMatcher_index[i]:_SymbolMatcher_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[PackageQualifiedSymbols-0]
+	_ = x[FullyQualifiedSymbols-1]
+	_ = x[DynamicSymbols-2]
+}
+
+const _SymbolStyle_name = "PackageQualifiedSymbolsFullyQualifiedSymbolsDynamicSymbols"
+
+var _SymbolStyle_index = [...]uint8{0, 23, 44, 58}
+
+func (i SymbolStyle) String() string {
+	if i < 0 || i >= SymbolStyle(len(_SymbolStyle_index)-1) {
+		return "SymbolStyle(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _SymbolStyle_name[_SymbolStyle_index[i]:_SymbolStyle_index[i+1]]
+}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 310bac3..adb09e9 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -136,7 +136,7 @@
 // ComputeOneImportFixEdits returns text edits for a single import fix.
 func ComputeOneImportFixEdits(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile, fix *imports.ImportFix) ([]protocol.TextEdit, error) {
 	options := &imports.Options{
-		LocalPrefix: snapshot.View().Options().LocalPrefix,
+		LocalPrefix: snapshot.View().Options().Local,
 		// Defaults.
 		AllErrors:  true,
 		Comments:   true,
diff --git a/internal/lsp/source/genopts/generate.go b/internal/lsp/source/genopts/generate.go
new file mode 100644
index 0000000..887fc4e
--- /dev/null
+++ b/internal/lsp/source/genopts/generate.go
@@ -0,0 +1,174 @@
+// Copyright 2020 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.
+
+// Command genopts generates JSON describing gopls' user options.
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"go/ast"
+	"go/types"
+	"os"
+	"reflect"
+	"strings"
+
+	"golang.org/x/tools/go/ast/astutil"
+	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/internal/lsp/source"
+)
+
+var (
+	output = flag.String("output", "", "output file")
+)
+
+func main() {
+	flag.Parse()
+	if err := doMain(); err != nil {
+		fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
+		os.Exit(1)
+	}
+}
+
+func doMain() error {
+	out := os.Stdout
+	if *output != "" {
+		var err error
+		out, err = os.OpenFile(*output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
+		if err != nil {
+			return err
+		}
+		defer out.Close()
+	}
+
+	content, err := generate()
+	if err != nil {
+		return err
+	}
+	if _, err := out.Write(content); err != nil {
+		return err
+	}
+
+	return out.Close()
+}
+
+func generate() ([]byte, error) {
+	pkgs, err := packages.Load(
+		&packages.Config{
+			Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps,
+		},
+		"golang.org/x/tools/internal/lsp/source",
+	)
+	if err != nil {
+		return nil, err
+	}
+	pkg := pkgs[0]
+
+	defaults := source.DefaultOptions()
+	categories := map[string][]option{}
+	for _, cat := range []reflect.Value{
+		reflect.ValueOf(defaults.DebuggingOptions),
+		reflect.ValueOf(defaults.UserOptions),
+		reflect.ValueOf(defaults.ExperimentalOptions),
+	} {
+		opts, err := loadOptions(cat, pkg)
+		if err != nil {
+			return nil, err
+		}
+		catName := strings.TrimSuffix(cat.Type().Name(), "Options")
+		categories[catName] = opts
+	}
+
+	marshaled, err := json.Marshal(categories)
+	if err != nil {
+		return nil, err
+	}
+
+	buf := bytes.NewBuffer(nil)
+	fmt.Fprintf(buf, "// Code generated by \"golang.org/x/tools/internal/lsp/source/genopts\"; DO NOT EDIT.\n\npackage source\n\nconst OptionsJson = %q\n", string(marshaled))
+	return buf.Bytes(), nil
+}
+
+type option struct {
+	Name    string
+	Type    string
+	Doc     string
+	Default string
+}
+
+func loadOptions(category reflect.Value, pkg *packages.Package) ([]option, error) {
+	// Find the type information and ast.File corresponding to the category.
+	optsType := pkg.Types.Scope().Lookup(category.Type().Name())
+	if optsType == nil {
+		return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
+	}
+
+	fset := pkg.Fset
+	var file *ast.File
+	for _, f := range pkg.Syntax {
+		if fset.Position(f.Pos()).Filename == fset.Position(optsType.Pos()).Filename {
+			file = f
+		}
+	}
+	if file == nil {
+		return nil, fmt.Errorf("no file for opts type %v", optsType)
+	}
+
+	var opts []option
+	optsStruct := optsType.Type().Underlying().(*types.Struct)
+	for i := 0; i < optsStruct.NumFields(); i++ {
+		// The types field gives us the type.
+		typesField := optsStruct.Field(i)
+		path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
+		if len(path) < 1 {
+			return nil, fmt.Errorf("could not find AST node for field %v", typesField)
+		}
+		// The AST field gives us the doc.
+		astField, ok := path[1].(*ast.Field)
+		if !ok {
+			return nil, fmt.Errorf("unexpected AST path %v", path)
+		}
+
+		// The reflect field gives us the default value.
+		reflectField := category.FieldByName(typesField.Name())
+		if !reflectField.IsValid() {
+			return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
+		}
+
+		// Format the default value. String values should look like strings. Other stuff should look like JSON literals.
+		var defString string
+		switch def := reflectField.Interface().(type) {
+		case fmt.Stringer:
+			defString = `"` + def.String() + `"`
+		case string:
+			defString = `"` + def + `"`
+		default:
+			defString = fmt.Sprint(def)
+		}
+		if reflectField.Kind() == reflect.Map {
+			b, err := json.Marshal(reflectField.Interface())
+			if err != nil {
+				return nil, err
+			}
+			defString = string(b)
+		}
+
+		opts = append(opts, option{
+			Name:    lowerFirst(typesField.Name()),
+			Type:    typesField.Type().String(),
+			Doc:     lowerFirst(astField.Doc.Text()),
+			Default: defString,
+		})
+	}
+	return opts, nil
+}
+
+func lowerFirst(x string) string {
+	if x == "" {
+		return x
+	}
+	return strings.ToLower(x[:1]) + x[1:]
+}
diff --git a/internal/lsp/source/genopts/generate_test.go b/internal/lsp/source/genopts/generate_test.go
new file mode 100644
index 0000000..51d7a63
--- /dev/null
+++ b/internal/lsp/source/genopts/generate_test.go
@@ -0,0 +1,30 @@
+// Copyright 2020 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 main
+
+import (
+	"bytes"
+	"io/ioutil"
+	"testing"
+
+	"golang.org/x/tools/internal/testenv"
+)
+
+func TestGenerate(t *testing.T) {
+	testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
+	testenv.NeedsGoPackages(t)
+
+	got, err := ioutil.ReadFile("../options_json.go")
+	if err != nil {
+		t.Fatal(err)
+	}
+	want, err := generate()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(got, want) {
+		t.Error("options_json is out of sync. Run `go generate ./internal/lsp/source` from the root of tools.")
+	}
+}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index dd026cb..aeba460 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -7,8 +7,8 @@
 import (
 	"context"
 	"fmt"
-	"os"
 	"regexp"
+	"strings"
 	"time"
 
 	"golang.org/x/tools/go/analysis"
@@ -53,6 +53,9 @@
 	errors "golang.org/x/xerrors"
 )
 
+//go:generate go run golang.org/x/tools/cmd/stringer -output enums_string.go -type ImportShortcut,HoverKind,Matcher,SymbolMatcher,SymbolStyle
+//go:generate go run golang.org/x/tools/internal/lsp/source/genopts -output options_json.go
+
 // DefaultOptions is the options that are used for Gopls execution independent
 // of any externally provided configuration (LSP initialization, command
 // invokation, etc.).
@@ -88,16 +91,17 @@
 			SupportedCommands: commands,
 		},
 		UserOptions: UserOptions{
-			Env:                     os.Environ(),
-			HoverKind:               FullDocumentation,
-			LinkTarget:              "pkg.go.dev",
-			LinksInHover:            true,
-			Matcher:                 Fuzzy,
-			SymbolMatcher:           SymbolFuzzy,
-			DeepCompletion:          true,
-			UnimportedCompletion:    true,
-			CompletionDocumentation: true,
-			EnabledCodeLens: map[string]bool{
+			HoverKind:  FullDocumentation,
+			LinkTarget: "pkg.go.dev",
+		},
+		DebuggingOptions: DebuggingOptions{
+			CompletionBudget:   100 * time.Millisecond,
+			LiteralCompletions: true,
+		},
+		ExperimentalOptions: ExperimentalOptions{
+			TempModfile:             true,
+			ExpandWorkspaceToModule: true,
+			Codelens: map[string]bool{
 				CommandGenerate.Name:          true,
 				CommandRegenerateCgo.Name:     true,
 				CommandTidy.Name:              true,
@@ -105,14 +109,12 @@
 				CommandUpgradeDependency.Name: true,
 				CommandVendor.Name:            true,
 			},
-			ExpandWorkspaceToModule: true,
-		},
-		DebuggingOptions: DebuggingOptions{
-			CompletionBudget:   100 * time.Millisecond,
-			LiteralCompletions: true,
-		},
-		ExperimentalOptions: ExperimentalOptions{
-			TempModfile: true,
+			LinksInHover:            true,
+			CompleteUnimported:      true,
+			CompletionDocumentation: true,
+			DeepCompletion:          true,
+			Matcher:                 Fuzzy,
+			SymbolMatcher:           SymbolFuzzy,
 		},
 		Hooks: Hooks{
 			ComputeEdits:         myers.ComputeEdits,
@@ -158,96 +160,44 @@
 // UserOptions holds custom Gopls configuration (not part of the LSP) that is
 // modified by the client.
 type UserOptions struct {
-	// Env is the current set of environment overrides on this view.
-	Env []string
-
-	// BuildFlags is used to adjust the build flags applied to the view.
+	// BuildFlags is the set of flags passed on to the build system when invoked.
+	// It is applied to queries like `go list`, which is used when discovering files.
+	// The most common use is to set `-tags`.
 	BuildFlags []string
 
-	// HoverKind specifies the format of the content for hover requests.
+	// Env adds environment variables to external commands run by `gopls`, most notably `go list`.
+	Env []string
+
+	// HoverKind controls the information that appears in the hover text.
+	// It must be one of:
+	// * `"NoDocumentation"`
+	// * `"SynopsisDocumentation"`
+	// * `"FullDocumentation"`
+	//
+	// Authors of editor clients may wish to handle hover text differently, and so might use different settings. The options below are not intended for use by anyone other than the authors of editor plugins.
+	//
+	// * `"SingleLine"`
+	// * `"Structured"`
 	HoverKind HoverKind
 
-	// UserEnabledAnalyses specifies analyses that the user would like to enable
-	// or disable. A map of the names of analysis passes that should be
-	// enabled/disabled. A full list of analyzers that gopls uses can be found
-	// [here](analyzers.md).
+	// Placeholders enables placeholders for function parameters or struct fields in completion responses.
+	UsePlaceholders bool
+
+	// LinkTarget controls where documentation links go.
+	// It might be one of:
 	//
-	// Example Usage:
-	// ...
-	// "analyses": {
-	//   "unreachable": false, // Disable the unreachable analyzer.
-	//   "unusedparams": true  // Enable the unusedparams analyzer.
-	// }
-	UserEnabledAnalyses map[string]bool
-
-	// EnabledCodeLens specifies which codelens are enabled, keyed by the gopls
-	// command that they provide.
-	EnabledCodeLens map[string]bool
-
-	// StaticCheck enables additional analyses from staticcheck.io.
-	StaticCheck bool
-
-	// LinkTarget is the website used for documentation. If empty, no link is
-	// provided.
+	// * `"godoc.org"`
+	// * `"pkg.go.dev"`
+	//
+	// If company chooses to use its own `godoc.org`, its address can be used as well.
 	LinkTarget string
 
-	// LinksInHover toggles the presence of links to documentation in hover.
-	LinksInHover bool
-
-	// ImportShortcut specifies whether import statements should link to
-	// documentation or go to definitions. The default is both.
-	ImportShortcut ImportShortcut
-
-	// LocalPrefix is used to specify goimports's -local behavior.
-	LocalPrefix string
-
-	// Matcher specifies the type of matcher to use for completion requests.
-	Matcher Matcher
-
-	// SymbolMatcher specifies the type of matcher to use for symbol requests.
-	SymbolMatcher SymbolMatcher
-
-	// SymbolStyle specifies what style of symbols to return in symbol requests
-	// (package qualified, fully qualified, etc).
-	SymbolStyle SymbolStyle
-
-	// DeepCompletion allows completion to perform nested searches through
-	// possible candidates.
-	DeepCompletion bool
-
-	// UnimportedCompletion enables completion for unimported packages.
-	UnimportedCompletion bool
-
-	// CompletionDocumentation returns additional documentation with completion
-	// requests.
-	CompletionDocumentation bool
-
-	// Placeholders adds placeholders to parameters and structs in completion
-	// results.
-	Placeholders bool
+	// Local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.
+	// It should be the prefix of the import path whose imports should be grouped separately.
+	Local string
 
 	// Gofumpt indicates if we should run gofumpt formatting.
 	Gofumpt bool
-
-	// ExpandWorkspaceToModule is true if we should expand the scope of the
-	// workspace to include the modules containing the workspace folders.
-	ExpandWorkspaceToModule bool
-}
-
-type ImportShortcut int
-
-const (
-	Both ImportShortcut = iota
-	Link
-	Definition
-)
-
-func (s ImportShortcut) ShowLinks() bool {
-	return s == Both || s == Link
-}
-
-func (s ImportShortcut) ShowDefinition() bool {
-	return s == Both || s == Definition
 }
 
 // Hooks contains configuration that is provided to the Gopls command by the
@@ -270,12 +220,76 @@
 // development. WARNING: This configuration will be changed in the future. It
 // only exists while these features are under development.
 type ExperimentalOptions struct {
-	// TempModfile controls the use of the -modfile flag in Go 1.14.
-	TempModfile bool
+	// Analyses specify analyses that the user would like to enable or disable.
+	// A map of the names of analysis passes that should be enabled/disabled.
+	// A full list of analyzers that gopls uses can be found [here](analyzers.md)
+	//
+	// Example Usage:
+	// ```json5
+	// ...
+	// "analyses": {
+	//   "unreachable": false, // Disable the unreachable analyzer.
+	//   "unusedparams": true  // Enable the unusedparams analyzer.
+	// }
+	// ...
+	// ```
+	Analyses map[string]bool
 
-	// VerboseWorkDoneProgress controls whether the LSP server should send
-	// progress reports for all work done outside the scope of an RPC.
-	VerboseWorkDoneProgress bool
+	// Overrides the enabled/disabled state of various code lenses. Currently, we
+	// support several code lenses:
+	//
+	// * `generate`: run `go generate` as specified by a `//go:generate` directive.
+	// * `upgrade_dependency`: upgrade a dependency listed in a `go.mod` file.
+	// * `test`: run `go test -run` for a test func.
+	// * `gc_details`: Show the gc compiler's choices for inline analysis and escaping.
+	//
+	// Example Usage:
+	// ```json5
+	// "gopls": {
+	// ...
+	//   "codelens": {
+	//     "generate": false,  // Don't run `go generate`.
+	//     "gc_details": true  // Show a code lens toggling the display of gc's choices.
+	//   }
+	// ...
+	// }
+	// ```
+	Codelens map[string]bool
+
+	// CompletionDocumentation enables documentation with completion results.
+	CompletionDocumentation bool
+
+	// CompleteUnimported enables completion for packages that you do not currently import.
+	CompleteUnimported bool
+
+	// DeepCompletion If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
+	//
+	// Consider this example:
+	//
+	// ```go
+	// package main
+	//
+	// import "fmt"
+	//
+	// type wrapString struct {
+	//     str string
+	// }
+	//
+	// func main() {
+	//     x := wrapString{"hello world"}
+	//     fmt.Printf(<>)
+	// }
+	// ```
+	//
+	// At the location of the `<>` in this program, deep completion would suggest the result `x.str`.
+	DeepCompletion bool
+
+	// Matcher sets the algorithm that is used when calculating completion candidates. Must be one of:
+	//
+	// * `"fuzzy"`
+	// * `"caseSensitive"`
+	// * `"caseInsensitive"`
+	Matcher Matcher
 
 	// Annotations suppress various kinds of optimization diagnostics
 	// that would be reported by the gc_details command.
@@ -284,11 +298,52 @@
 	//   noInline suppresses inlining choices.
 	//   noBounds suppresses bounds checking diagnositcs.
 	Annotations map[string]bool
+
+	// Staticcheck enables additional analyses from staticcheck.io.
+	Staticcheck bool
+
+	// SymbolMatcher sets the algorithm that is used when finding workspace symbols. Must be one of:
+	//
+	// * `"fuzzy"`
+	// * `"caseSensitive"`
+	// * `"caseInsensitive"`
+	SymbolMatcher SymbolMatcher
+
+	// SymbolStyle specifies what style of symbols to return in symbol requests. Must be one of:
+	//
+	// * `"full"`
+	// * `"dynamic"`
+	// * `"package"`
+	SymbolStyle SymbolStyle
+
+	// LinksInHover toggles the presence of links to documentation in hover.
+	LinksInHover bool
+
+	// TempModfile controls the use of the -modfile flag in Go 1.14.
+	TempModfile bool
+
+	// ImportShortcut specifies whether import statements should link to
+	// documentation or go to definitions. Must be one of:
+	//
+	// * `"both"`
+	// * `"link"`
+	// * `"definition"`
+	ImportShortcut ImportShortcut
+
+	// VerboseWorkDoneProgress controls whether the LSP server should send
+	// progress reports for all work done outside the scope of an RPC.
+	VerboseWorkDoneProgress bool
+
+	// ExpandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the
+	// modules containing the workspace folders. Set this to false to avoid loading
+	// your entire module. This is particularly useful for those working in a monorepo.
+	ExpandWorkspaceToModule bool
 }
 
 // DebuggingOptions should not affect the logical execution of Gopls, but may
 // be altered for debugging purposes.
 type DebuggingOptions struct {
+	// VerboseOutput enables additional debug logging.
 	VerboseOutput bool
 
 	// CompletionBudget is the soft latency goal for completion requests. Most
@@ -304,6 +359,22 @@
 	LiteralCompletions bool
 }
 
+type ImportShortcut int
+
+const (
+	Both ImportShortcut = iota
+	Link
+	Definition
+)
+
+func (s ImportShortcut) ShowLinks() bool {
+	return s == Both || s == Link
+}
+
+func (s ImportShortcut) ShowDefinition() bool {
+	return s == Both || s == Definition
+}
+
 type Matcher int
 
 const (
@@ -431,11 +502,11 @@
 	case "completionDocumentation":
 		result.setBool(&o.CompletionDocumentation)
 	case "usePlaceholders":
-		result.setBool(&o.Placeholders)
+		result.setBool(&o.UsePlaceholders)
 	case "deepCompletion":
 		result.setBool(&o.DeepCompletion)
 	case "completeUnimported":
-		result.setBool(&o.UnimportedCompletion)
+		result.setBool(&o.CompleteUnimported)
 	case "completionBudget":
 		if v, ok := result.asString(); ok {
 			d, err := time.ParseDuration(v)
@@ -451,10 +522,10 @@
 		if !ok {
 			break
 		}
-		switch matcher {
+		switch strings.ToLower(matcher) {
 		case "fuzzy":
 			o.Matcher = Fuzzy
-		case "caseSensitive":
+		case "casesensitive":
 			o.Matcher = CaseSensitive
 		default:
 			o.Matcher = CaseInsensitive
@@ -465,10 +536,10 @@
 		if !ok {
 			break
 		}
-		switch matcher {
+		switch strings.ToLower(matcher) {
 		case "fuzzy":
 			o.SymbolMatcher = SymbolFuzzy
-		case "caseSensitive":
+		case "casesensitive":
 			o.SymbolMatcher = SymbolCaseSensitive
 		default:
 			o.SymbolMatcher = SymbolCaseInsensitive
@@ -479,7 +550,7 @@
 		if !ok {
 			break
 		}
-		switch style {
+		switch strings.ToLower(style) {
 		case "full":
 			o.SymbolStyle = FullyQualifiedSymbols
 		case "dynamic":
@@ -495,16 +566,16 @@
 		if !ok {
 			break
 		}
-		switch hoverKind {
-		case "NoDocumentation":
+		switch strings.ToLower(hoverKind) {
+		case "nodocumentation":
 			o.HoverKind = NoDocumentation
-		case "SingleLine":
+		case "singleline":
 			o.HoverKind = SingleLine
-		case "SynopsisDocumentation":
+		case "synopsisdocumentation":
 			o.HoverKind = SynopsisDocumentation
-		case "FullDocumentation":
+		case "fulldocumentation":
 			o.HoverKind = FullDocumentation
-		case "Structured":
+		case "structured":
 			o.HoverKind = Structured
 		default:
 			result.errorf("Unsupported hover kind %q", hoverKind)
@@ -519,7 +590,7 @@
 	case "importShortcut":
 		var s string
 		result.setString(&s)
-		switch s {
+		switch strings.ToLower(s) {
 		case "both":
 			o.ImportShortcut = Both
 		case "link":
@@ -529,7 +600,7 @@
 		}
 
 	case "analyses":
-		result.setBoolMap(&o.UserEnabledAnalyses)
+		result.setBoolMap(&o.Analyses)
 
 	case "annotations":
 		result.setBoolMap(&o.Annotations)
@@ -547,19 +618,19 @@
 		var lensOverrides map[string]bool
 		result.setBoolMap(&lensOverrides)
 		if result.Error == nil {
-			if o.EnabledCodeLens == nil {
-				o.EnabledCodeLens = make(map[string]bool)
+			if o.Codelens == nil {
+				o.Codelens = make(map[string]bool)
 			}
 			for lens, enabled := range lensOverrides {
-				o.EnabledCodeLens[lens] = enabled
+				o.Codelens[lens] = enabled
 			}
 		}
 
 	case "staticcheck":
-		result.setBool(&o.StaticCheck)
+		result.setBool(&o.Staticcheck)
 
 	case "local":
-		result.setString(&o.LocalPrefix)
+		result.setString(&o.Local)
 
 	case "verboseOutput":
 		result.setBool(&o.VerboseOutput)
diff --git a/internal/lsp/source/options_json.go b/internal/lsp/source/options_json.go
new file mode 100755
index 0000000..7007fb2
--- /dev/null
+++ b/internal/lsp/source/options_json.go
@@ -0,0 +1,5 @@
+// Code generated by "golang.org/x/tools/internal/lsp/source/genopts"; DO NOT EDIT.
+
+package source
+
+const OptionsJson = "{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"Default\":\"\\\"100ms\\\"\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"Default\":\"true\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"Default\":\"null\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"overrides the enabled/disabled state of various code lenses. Currently, we\\nsupport several code lenses:\\n\\n* `generate`: run `go generate` as specified by a `//go:generate` directive.\\n* `upgrade_dependency`: upgrade a dependency listed in a `go.mod` file.\\n* `test`: run `go test -run` for a test func.\\n* `gc_details`: Show the gc compiler's choices for inline analysis and escaping.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't run `go generate`.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"golang.org/x/tools/internal/lsp/source.Matcher\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates. Must be one of:\\n\\n* `\\\"fuzzy\\\"`\\n* `\\\"caseSensitive\\\"`\\n* `\\\"caseInsensitive\\\"`\\n\",\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n  noNilcheck suppresses display of nilchecks.\\n  noEscape suppresses escape choices.\\n  noInline suppresses inlining choices.\\n  noBounds suppresses bounds checking diagnositcs.\\n\",\"Default\":\"null\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"golang.org/x/tools/internal/lsp/source.SymbolMatcher\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols. Must be one of:\\n\\n* `\\\"fuzzy\\\"`\\n* `\\\"caseSensitive\\\"`\\n* `\\\"caseInsensitive\\\"`\\n\",\"Default\":\"\\\"SymbolFuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"golang.org/x/tools/internal/lsp/source.SymbolStyle\",\"Doc\":\"symbolStyle specifies what style of symbols to return in symbol requests. Must be one of:\\n\\n* `\\\"full\\\"`\\n* `\\\"dynamic\\\"`\\n* `\\\"package\\\"`\\n\",\"Default\":\"\\\"PackageQualifiedSymbols\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"golang.org/x/tools/internal/lsp/source.ImportShortcut\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions. Must be one of:\\n\\n* `\\\"both\\\"`\\n* `\\\"link\\\"`\\n* `\\\"definition\\\"`\\n\",\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"Default\":\"true\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"golang.org/x/tools/internal/lsp/source.HoverKind\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nIt must be one of:\\n* `\\\"NoDocumentation\\\"`\\n* `\\\"SynopsisDocumentation\\\"`\\n* `\\\"FullDocumentation\\\"`\\n\\nAuthors of editor clients may wish to handle hover text differently, and so might use different settings. The options below are not intended for use by anyone other than the authors of editor plugins.\\n\\n* `\\\"SingleLine\\\"`\\n* `\\\"Structured\\\"`\\n\",\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"Default\":\"false\"}]}"
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index b16dcee..c35e916 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -179,7 +179,7 @@
 	_, got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.Matcher = source.CaseInsensitive
 		opts.DeepCompletion = false
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 		opts.InsertTextFormat = protocol.SnippetTextFormat
 		if !strings.Contains(string(src.URI()), "literal") {
 			opts.LiteralCompletions = false
@@ -193,9 +193,9 @@
 
 func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
 	_, list := r.callCompletion(t, src, func(opts *source.Options) {
-		opts.Placeholders = placeholders
+		opts.UsePlaceholders = placeholders
 		opts.DeepCompletion = true
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got := tests.FindItem(list, *items[expected.CompletionItem])
 	want := expected.PlainSnippet
@@ -227,7 +227,7 @@
 	prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = true
 		opts.Matcher = source.CaseInsensitive
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	list = tests.FilterBuiltins(src, list)
 	fuzzyMatcher := fuzzy.NewMatcher(prefix)
@@ -251,7 +251,7 @@
 	_, got := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.DeepCompletion = true
 		opts.Matcher = source.Fuzzy
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	got = tests.FilterBuiltins(src, got)
 	if msg := tests.DiffCompletionItems(want, got); msg != "" {
@@ -266,7 +266,7 @@
 	}
 	_, list := r.callCompletion(t, src, func(opts *source.Options) {
 		opts.Matcher = source.CaseSensitive
-		opts.UnimportedCompletion = false
+		opts.CompleteUnimported = false
 	})
 	list = tests.FilterBuiltins(src, list)
 	if diff := tests.DiffCompletionItems(want, list); diff != "" {
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index e463491..34e6b31 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -439,7 +439,7 @@
 }
 
 func (a Analyzer) Enabled(view View) bool {
-	if enabled, ok := view.Options().UserEnabledAnalyses[a.Analyzer.Name]; ok {
+	if enabled, ok := view.Options().Analyses[a.Analyzer.Name]; ok {
 		return enabled
 	}
 	return a.enabled
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index f358fc5..7a9d582 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -236,7 +236,7 @@
 		},
 		source.Sum: {},
 	}
-	o.UserOptions.EnabledCodeLens[source.CommandTest.Name] = true
+	o.ExperimentalOptions.Codelens[source.CommandTest.Name] = true
 	o.HoverKind = source.SynopsisDocumentation
 	o.InsertTextFormat = protocol.SnippetTextFormat
 	o.CompletionBudget = time.Minute
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
index fbef835..83a204d 100644
--- a/internal/lsp/tests/util.go
+++ b/internal/lsp/tests/util.go
@@ -526,22 +526,22 @@
 }
 
 func EnableAllAnalyzers(view source.View, opts *source.Options) {
-	if opts.UserEnabledAnalyses == nil {
-		opts.UserEnabledAnalyses = make(map[string]bool)
+	if opts.Analyses == nil {
+		opts.Analyses = make(map[string]bool)
 	}
 	for _, a := range opts.DefaultAnalyzers {
 		if !a.Enabled(view) {
-			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
+			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 	for _, a := range opts.TypeErrorAnalyzers {
 		if !a.Enabled(view) {
-			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
+			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 	for _, a := range opts.ConvenienceAnalyzers {
 		if !a.Enabled(view) {
-			opts.UserEnabledAnalyses[a.Analyzer.Name] = true
+			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 }