From d2d86cca024c9fbc27f5b2fc2c40761072674c4d Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Thu, 24 Dec 2020 15:48:53 -0500 Subject: [PATCH] internal/lsp: restructure user options (CL 278433 continued) This CL copies Heschi's structural changes to the options from CL 278433 and makes the necessary adjustments in the JSON and documentation generation. Nested settings are grouped together and the "status" of a given setting is also listed. Currently the only possible statuses are "experimental" and "debug", but I will add "advanced" in a follow-up (to indicate that a setting is only for advanced users). The options "set" function still expects flattened settings to avoid fundamentally changing people's current configurations, so VS Code Go will just have to make sure to flatten the settings before sending them to gopls (which should be easy enough). No names of any settings are changed (Heschi's earlier CL adjusted the experimental prefixes). As discussed offline, we've decided to prefix any setting that we expect to delete with "experimental", and so we'll leave existing setting names as they are. Updates golang/go#43101 Change-Id: I55cf7ef09ce7b5b1f8af06fcadb4ba2a44ec9b17 Reviewed-on: https://go-review.googlesource.com/c/tools/+/280192 Trust: Rebecca Stambler Run-TryBot: Rebecca Stambler gopls-CI: kokoro TryBot-Result: Go Bot Reviewed-by: Suzy Mueller --- gopls/doc/generate.go | 221 ++++++++++--- gopls/doc/settings.md | 461 ++++++++++++++++------------ internal/lsp/source/api_json.go | 372 ++++++++++++---------- internal/lsp/source/options.go | 410 +++++++++++++------------ internal/lsp/source/options_test.go | 9 +- 5 files changed, 894 insertions(+), 579 deletions(-) diff --git a/gopls/doc/generate.go b/gopls/doc/generate.go index 416f606f3d..ba4393bc86 100644 --- a/gopls/doc/generate.go +++ b/gopls/doc/generate.go @@ -14,6 +14,7 @@ import ( "go/format" "go/token" "go/types" + "io" "io/ioutil" "os" "path/filepath" @@ -22,6 +23,7 @@ import ( "sort" "strings" "time" + "unicode" "github.com/sanity-io/litter" "golang.org/x/tools/go/ast/astutil" @@ -75,16 +77,19 @@ func loadAPI() (*source.APIJSON, error) { Options: map[string][]*source.OptionJSON{}, } defaults := source.DefaultOptions() - for _, cat := range []reflect.Value{ - reflect.ValueOf(defaults.DebuggingOptions), + for _, category := range []reflect.Value{ reflect.ValueOf(defaults.UserOptions), - reflect.ValueOf(defaults.ExperimentalOptions), } { - opts, err := loadOptions(cat, pkg) + // 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()) + } + opts, err := loadOptions(category, optsType, pkg, "") if err != nil { return nil, err } - catName := strings.TrimSuffix(cat.Type().Name(), "Options") + catName := strings.TrimSuffix(category.Type().Name(), "Options") api.Options[catName] = opts } @@ -109,13 +114,7 @@ func loadAPI() (*source.APIJSON, error) { return api, nil } -func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.OptionJSON, 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()) - } - +func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Package, hierarchy string) ([]*source.OptionJSON, error) { file, err := fileForPos(pkg, optsType.Pos()) if err != nil { return nil, err @@ -131,6 +130,21 @@ func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.Optio for i := 0; i < optsStruct.NumFields(); i++ { // The types field gives us the type. typesField := optsStruct.Field(i) + + // If the field name ends with "Options", assume it is a struct with + // additional options and process it recursively. + if h := strings.TrimSuffix(typesField.Name(), "Options"); h != typesField.Name() { + // Keep track of the parent structs. + if hierarchy != "" { + h = hierarchy + "." + h + } + options, err := loadOptions(category, typesField, pkg, strings.ToLower(h)) + if err != nil { + return nil, err + } + opts = append(opts, options...) + continue + } path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos()) if len(path) < 2 { return nil, fmt.Errorf("could not find AST node for field %v", typesField) @@ -183,6 +197,12 @@ func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.Optio typ = strings.Replace(typ, m.Key().String(), m.Key().Underlying().String(), 1) } } + // Get the status of the field by checking its struct tags. + reflectStructField, ok := category.Type().FieldByName(typesField.Name()) + if !ok { + return nil, fmt.Errorf("no struct field for %s", typesField.Name()) + } + status := reflectStructField.Tag.Get("status") opts = append(opts, &source.OptionJSON{ Name: lowerFirst(typesField.Name()), @@ -190,6 +210,8 @@ func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.Optio Doc: lowerFirst(astField.Doc.Text()), Default: string(defBytes), EnumValues: enumValues, + Status: status, + Hierarchy: hierarchy, }) } return opts, nil @@ -411,34 +433,39 @@ func rewriteAPI(input []byte, api *source.APIJSON) ([]byte, error) { var parBreakRE = regexp.MustCompile("\n{2,}") +type optionsGroup struct { + title string + final string + level int + options []*source.OptionJSON +} + func rewriteSettings(doc []byte, api *source.APIJSON) ([]byte, error) { result := doc for category, opts := range api.Options { + groups := collectGroups(opts) + + // First, print a table of contents. section := bytes.NewBuffer(nil) - for _, opt := range opts { - var enumValues strings.Builder - if len(opt.EnumValues) > 0 { - var msg string - if opt.Type == "enum" { - msg = "\nMust be one of:\n\n" - } else { - msg = "\nCan contain any of:\n\n" - } - enumValues.WriteString(msg) - for i, val := range opt.EnumValues { - if val.Doc != "" { - // Don't break the list item by starting a new paragraph. - unbroken := parBreakRE.ReplaceAllString(val.Doc, "\\\n") - fmt.Fprintf(&enumValues, "* %s", unbroken) - } else { - fmt.Fprintf(&enumValues, "* `%s`", val.Value) - } - if i < len(opt.EnumValues)-1 { - fmt.Fprint(&enumValues, "\n") - } - } + fmt.Fprintln(section, "") + for _, h := range groups { + writeBullet(section, h.final, h.level) + } + fmt.Fprintln(section, "") + + // Currently, the settings document has a title and a subtitle, so + // start at level 3 for a header beginning with "###". + baseLevel := 3 + for _, h := range groups { + level := baseLevel + h.level + writeTitle(section, h.final, level) + for _, opt := range h.options { + header := strMultiply("#", level+1) + fmt.Fprintf(section, "%s **%v** *%v*\n\n", header, opt.Name, opt.Type) + writeStatus(section, opt.Status) + enumValues := collectEnumValues(opt) + fmt.Fprintf(section, "%v%v\nDefault: `%v`.\n\n", opt.Doc, enumValues, opt.Default) } - fmt.Fprintf(section, "### **%v** *%v*\n%v%v\n\nDefault: `%v`.\n", opt.Name, opt.Type, opt.Doc, enumValues.String(), opt.Default) } var err error result, err = replaceSection(result, category, section.Bytes()) @@ -449,11 +476,133 @@ func rewriteSettings(doc []byte, api *source.APIJSON) ([]byte, error) { section := bytes.NewBuffer(nil) for _, lens := range api.Lenses { - fmt.Fprintf(section, "### **%v**\nIdentifier: `%v`\n\n%v\n\n", lens.Title, lens.Lens, lens.Doc) + fmt.Fprintf(section, "### **%v**\n\nIdentifier: `%v`\n\n%v\n", lens.Title, lens.Lens, lens.Doc) } return replaceSection(result, "Lenses", section.Bytes()) } +func collectGroups(opts []*source.OptionJSON) []optionsGroup { + optsByHierarchy := map[string][]*source.OptionJSON{} + for _, opt := range opts { + optsByHierarchy[opt.Hierarchy] = append(optsByHierarchy[opt.Hierarchy], opt) + } + + // As a hack, assume that uncategorized items are less important to + // users and force the empty string to the end of the list. + var containsEmpty bool + var sorted []string + for h := range optsByHierarchy { + if h == "" { + containsEmpty = true + continue + } + sorted = append(sorted, h) + } + sort.Strings(sorted) + if containsEmpty { + sorted = append(sorted, "") + } + var groups []optionsGroup + baseLevel := 0 + for _, h := range sorted { + split := strings.SplitAfter(h, ".") + last := split[len(split)-1] + // Hack to capitalize all of UI. + if last == "ui" { + last = "UI" + } + // A hierarchy may look like "ui.formatting". If "ui" has no + // options of its own, it may not be added to the map, but it + // still needs a heading. + components := strings.Split(h, ".") + for i := 1; i < len(components); i++ { + parent := strings.Join(components[0:i], ".") + if _, ok := optsByHierarchy[parent]; !ok { + groups = append(groups, optionsGroup{ + title: parent, + final: last, + level: baseLevel + i, + }) + } + } + groups = append(groups, optionsGroup{ + title: h, + final: last, + level: baseLevel + strings.Count(h, "."), + options: optsByHierarchy[h], + }) + } + return groups +} + +func collectEnumValues(opt *source.OptionJSON) string { + var enumValues strings.Builder + if len(opt.EnumValues) > 0 { + var msg string + if opt.Type == "enum" { + msg = "\nMust be one of:\n\n" + } else { + msg = "\nCan contain any of:\n\n" + } + enumValues.WriteString(msg) + for i, val := range opt.EnumValues { + if val.Doc != "" { + unbroken := parBreakRE.ReplaceAllString(val.Doc, "\\\n") + fmt.Fprintf(&enumValues, "* %s", unbroken) + } else { + fmt.Fprintf(&enumValues, "* `%s`", val.Value) + } + if i < len(opt.EnumValues)-1 { + fmt.Fprint(&enumValues, "\n") + } + } + } + return enumValues.String() +} + +func writeBullet(w io.Writer, title string, level int) { + if title == "" { + return + } + // Capitalize the first letter of each title. + prefix := strMultiply(" ", level) + fmt.Fprintf(w, "%s* [%s](#%s)\n", prefix, capitalize(title), strings.ToLower(title)) +} + +func writeTitle(w io.Writer, title string, level int) { + if title == "" { + return + } + // Capitalize the first letter of each title. + fmt.Fprintf(w, "%s %s\n\n", strMultiply("#", level), capitalize(title)) +} + +func writeStatus(section io.Writer, status string) { + switch status { + case "": + case "advanced": + fmt.Fprint(section, "**This is an advanced setting and should not be configured by most `gopls` users.**\n\n") + case "debug": + fmt.Fprint(section, "**This setting is for debugging purposes only.**\n\n") + case "experimental": + fmt.Fprint(section, "**This setting is experimental and may be deleted.**\n\n") + default: + fmt.Fprintf(section, "**Status: %s.**\n\n", status) + } +} + +func capitalize(s string) string { + return string(unicode.ToUpper(rune(s[0]))) + s[1:] +} + +func strMultiply(str string, count int) string { + var result string + for i := 0; i < count; i++ { + result += string(str) + } + return result +} + func rewriteCommands(doc []byte, api *source.APIJSON) ([]byte, error) { section := bytes.NewBuffer(nil) for _, command := range api.Commands { diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index 5f92dcf58a..9e13d1bc4b 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -2,9 +2,13 @@ -This document describes the global settings for `gopls` inside the editor. The settings block will be called `"gopls"` and contains a collection of controls for `gopls` that the editor is not expected to understand or control. These settings can also be configured differently per workspace folder. +This document describes the global settings for `gopls` inside the editor. +The settings block will be called `"gopls"` and contains a collection of +controls for `gopls` that the editor is not expected to understand or control. +These settings can also be configured differently per workspace folder. -In VSCode, this would be a section in your `settings.json` file that might look like this: +In VSCode, this would be a section in your `settings.json` file that might look +like this: ```json5 "gopls": { @@ -17,23 +21,257 @@ In VSCode, this would be a section in your `settings.json` file that might look Below is the list of settings that are officially supported for `gopls`. +Any settings that are experimental or for debugging purposes are marked as +such. + To enable all experimental features, use **allExperiments: `true`**. You will still be able to independently override specific experimental features. -### **buildFlags** *[]string* + +* [Build](#build) +* [Formatting](#formatting) +* [UI](#ui) + * [Completion](#completion) + * [Diagnostic](#diagnostic) + * [Documentation](#documentation) + * [Navigation](#navigation) + +### Build + +#### **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`. - Default: `[]`. -### **env** *map[string]string* + +#### **env** *map[string]string* + env adds environment variables to external commands run by `gopls`, most notably `go list`. +Default: `{}`. + +#### **directoryFilters** *[]string* + +directoryFilters can be used to exclude unwanted directories from the +workspace. By default, all directories are included. Filters are an +operator, `+` to include and `-` to exclude, followed by a path prefix +relative to the workspace folder. They are evaluated in order, and +the last filter that applies to a path controls whether it is included. +The path prefix can be empty, so an initial `-` excludes everything. + +Examples: +Exclude node_modules: `-node_modules` +Include only project_a: `-` (exclude everything), `+project_a` +Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules` + +Default: `[]`. + +#### **expandWorkspaceToModule** *bool* + +**This setting is experimental and may be deleted.** + +expandWorkspaceToModule instructs `gopls` to adjust the scope of the +workspace to find the best available module root. `gopls` first looks for +a go.mod file in any parent directory of the workspace folder, expanding +the scope to that directory if it exists. If no viable parent directory is +found, gopls will check if there is exactly one child directory containing +a go.mod file, narrowing the scope to that directory if it exists. + +Default: `true`. + +#### **experimentalWorkspaceModule** *bool* + +**This setting is experimental and may be deleted.** + +experimentalWorkspaceModule opts a user into the experimental support +for multi-module workspaces. + +Default: `false`. + +#### **experimentalPackageCacheKey** *bool* + +**This setting is experimental and may be deleted.** + +experimentalPackageCacheKey controls whether to use a coarser cache key +for package type information to increase cache hits. This setting removes +the user's environment, build flags, and working directory from the cache +key, which should be a safe change as all relevant inputs into the type +checking pass are already hashed into the key. This is temporarily guarded +by an experiment because caching behavior is subtle and difficult to +comprehensively test. + +Default: `true`. + +#### **allowModfileModifications** *bool* + +**This setting is experimental and may be deleted.** + +allowModfileModifications disables -mod=readonly, allowing imports from +out-of-scope modules. This option will eventually be removed. + +Default: `false`. + +#### **allowImplicitNetworkAccess** *bool* + +**This setting is experimental and may be deleted.** + +allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module +downloads rather than requiring user action. This option will eventually +be removed. + +Default: `false`. + +### Formatting + +#### **local** *string* + +local is the equivalent of the `goimports -local` flag, which puts +imports beginning with this string after third-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. + +Default: `false`. + +### UI + +#### **codelenses** *map[string]bool* + +codelenses overrides the enabled/disabled state of code lenses. See the "Code Lenses" +section of settings.md for the list of supported lenses. + +Example Usage: + +```json5 +"gopls": { +... + "codelens": { + "generate": false, // Don't show the `go generate` lens. + "gc_details": true // Show a code lens toggling the display of gc's choices. + } +... +} +``` + +Default: `{"gc_details":false,"generate":true,"regenerate_cgo":true,"tidy":true,"upgrade_dependency":true,"vendor":true}`. + +#### **semanticTokens** *bool* + +**This setting is experimental and may be deleted.** + +semanticTokens controls whether the LSP server will send +semantic tokens to the client. + +Default: `false`. + +#### Completion + +##### **usePlaceholders** *bool* + +placeholders enables placeholders for function parameters or struct +fields in completion responses. + +Default: `false`. + +##### **completionBudget** *time.Duration* + +**This setting is for debugging purposes only.** + +completionBudget is the soft latency goal for completion requests. Most +requests finish in a couple milliseconds, but in some cases deep +completions can take much longer. As we use up our budget we +dynamically reduce the search scope to ensure we return timely +results. Zero means unlimited. + +Default: `"100ms"`. + +##### **matcher** *enum* + +**This is an advanced setting and should not be configured by most `gopls` users.** + +matcher sets the algorithm that is used when calculating completion +candidates. + +Must be one of: + +* `"CaseInsensitive"` +* `"CaseSensitive"` +* `"Fuzzy"` +Default: `"Fuzzy"`. + +#### Diagnostic + +##### **analyses** *map[string]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. +} +... +``` Default: `{}`. -### **hoverKind** *enum* + +##### **staticcheck** *bool* + +**This setting is experimental and may be deleted.** + +staticcheck enables additional analyses from staticcheck.io. + +Default: `false`. + +##### **annotations** *map[string]bool* + +**This setting is experimental and may be deleted.** + +annotations specifies the various kinds of optimization diagnostics +that should be reported by the gc_details command. + +Can contain any of: + +* `"bounds"` controls bounds checking diagnostics. + +* `"escape"` controls diagnostics about escape choices. + +* `"inline"` controls diagnostics about inlining choices. + +* `"nil"` controls nil checks. + +Default: `{"bounds":true,"escape":true,"inline":true,"nil":true}`. + +##### **experimentalDiagnosticsDelay** *time.Duration* + +**This setting is experimental and may be deleted.** + +experimentalDiagnosticsDelay controls the amount of time that gopls waits +after the most recent file modification before computing deep diagnostics. +Simple diagnostics (parsing and type-checking) are always run immediately +on recently modified packages. + +This option must be set to a valid duration string, for example `"250ms"`. + +Default: `"250ms"`. + +#### Documentation + +##### **hoverKind** *enum* + hoverKind controls the information that appears in the hover text. SingleLine and Structured are intended for use only by authors of editor plugins. @@ -48,14 +286,10 @@ can do more manipulation of these fields.\ This should only be used by clients that support this behavior. * `"SynopsisDocumentation"` - Default: `"FullDocumentation"`. -### **usePlaceholders** *bool* -placeholders enables placeholders for function parameters or struct fields in completion responses. +##### **linkTarget** *string* -Default: `false`. -### **linkTarget** *string* linkTarget controls where documentation links go. It might be one of: @@ -64,60 +298,18 @@ It might be one of: If company chooses to use its own `godoc.org`, its address can be used as well. - Default: `"pkg.go.dev"`. -### **local** *string* -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. +##### **linksInHover** *bool* -Default: `""`. -### **gofumpt** *bool* -gofumpt indicates if we should run gofumpt formatting. - - -Default: `false`. -### **analyses** *map[string]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. -} -... -``` - - -Default: `{}`. -### **codelenses** *map[string]bool* -codelenses overrides the enabled/disabled state of code lenses. See the "Code Lenses" -section of settings.md for the list of supported lenses. - -Example Usage: -```json5 -"gopls": { -... - "codelenses": { - "generate": false, // Don't show the `go generate` lens. - "gc_details": true // Show a code lens toggling the display of gc's choices. - } -... -} -``` - - -Default: `{"gc_details":false,"generate":true,"regenerate_cgo":true,"tidy":true,"upgrade_dependency":true,"vendor":true}`. -### **linksInHover** *bool* linksInHover toggles the presence of links to documentation in hover. - Default: `true`. -### **importShortcut** *enum* + +#### Navigation + +##### **importShortcut** *enum* + importShortcut specifies whether import statements should link to documentation or go to definitions. @@ -126,19 +318,12 @@ Must be one of: * `"Both"` * `"Definition"` * `"Link"` - Default: `"Both"`. -### **matcher** *enum* -matcher sets the algorithm that is used when calculating completion candidates. -Must be one of: +##### **symbolMatcher** *enum* -* `"CaseInsensitive"` -* `"CaseSensitive"` -* `"Fuzzy"` +**This is an advanced setting and should not be configured by most `gopls` users.** -Default: `"Fuzzy"`. -### **symbolMatcher** *enum* symbolMatcher sets the algorithm that is used when finding workspace symbols. Must be one of: @@ -146,12 +331,16 @@ Must be one of: * `"CaseInsensitive"` * `"CaseSensitive"` * `"Fuzzy"` - Default: `"Fuzzy"`. -### **symbolStyle** *enum* + +##### **symbolStyle** *enum* + +**This is an advanced setting and should not be configured by most `gopls` users.** + symbolStyle controls how symbols are qualified in symbol responses. Example Usage: + ```json5 "gopls": { ... @@ -173,129 +362,17 @@ just "Foo.Field". * `"Package"` is package qualified symbols i.e. "pkg.Foo.Field". - Default: `"Dynamic"`. -### **directoryFilters** *[]string* -directoryFilters can be used to exclude unwanted directories from the -workspace. By default, all directories are included. Filters are an -operator, `+` to include and `-` to exclude, followed by a path prefix -relative to the workspace folder. They are evaluated in order, and -the last filter that applies to a path controls whether it is included. -The path prefix can be empty, so an initial `-` excludes everything. -Examples: -Exclude node_modules: `-node_modules` -Include only project_a: `-` (exclude everything), `+project_a` -Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules` +#### **verboseOutput** *bool* +**This setting is for debugging purposes only.** -Default: `[]`. - - -## 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. - - -### **annotations** *map[string]bool* -annotations specifies the various kinds of optimization diagnostics -that should be reported by the gc_details command. - -Can contain any of: - -* `"bounds"` controls bounds checking diagnostics. - -* `"escape"` controls diagnostics about escape choices. - -* `"inline"` controls diagnostics about inlining choices. - -* `"nil"` controls nil checks. - - -Default: `{"bounds":true,"escape":true,"inline":true,"nil":true}`. -### **staticcheck** *bool* -staticcheck enables additional analyses from staticcheck.io. - - -Default: `false`. -### **semanticTokens** *bool* -semanticTokens controls whether the LSP server will send -semantic tokens to the client. - - -Default: `false`. -### **expandWorkspaceToModule** *bool* -expandWorkspaceToModule instructs `gopls` to adjust the scope of the -workspace to find the best available module root. `gopls` first looks for -a go.mod file in any parent directory of the workspace folder, expanding -the scope to that directory if it exists. If no viable parent directory is -found, gopls will check if there is exactly one child directory containing -a go.mod file, narrowing the scope to that directory if it exists. - - -Default: `true`. -### **experimentalWorkspaceModule** *bool* -experimentalWorkspaceModule opts a user into the experimental support -for multi-module workspaces. - - -Default: `false`. -### **experimentalDiagnosticsDelay** *time.Duration* -experimentalDiagnosticsDelay controls the amount of time that gopls waits -after the most recent file modification before computing deep diagnostics. -Simple diagnostics (parsing and type-checking) are always run immediately -on recently modified packages. - -This option must be set to a valid duration string, for example `"250ms"`. - - -Default: `"250ms"`. -### **experimentalPackageCacheKey** *bool* -experimentalPackageCacheKey controls whether to use a coarser cache key -for package type information to increase cache hits. This setting removes -the user's environment, build flags, and working directory from the cache -key, which should be a safe change as all relevant inputs into the type -checking pass are already hashed into the key. This is temporarily guarded -by an experiment because caching behavior is subtle and difficult to -comprehensively test. - - -Default: `true`. -### **allowModfileModifications** *bool* -allowModfileModifications disables -mod=readonly, allowing imports from -out-of-scope modules. This option will eventually be removed. - - -Default: `false`. -### **allowImplicitNetworkAccess** *bool* -allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module -downloads rather than requiring user action. This option will eventually -be removed. - - -Default: `false`. - - -## Debugging - -The below settings are for use in debugging `gopls`. Like the experimental options, they may be deprecated or changed in the future. - - -### **verboseOutput** *bool* verboseOutput enables additional debug logging. - Default: `false`. -### **completionBudget** *time.Duration* -completionBudget is the soft latency goal for completion requests. Most -requests finish in a couple milliseconds, but in some cases deep -completions can take much longer. As we use up our budget we -dynamically reduce the search scope to ensure we return timely -results. Zero means unlimited. - -Default: `"100ms"`. - + ## Code Lenses @@ -303,45 +380,45 @@ These are the code lenses that `gopls` currently supports. They can be enabled a ### **Run go generate** + Identifier: `generate` generate runs `go generate` for a given directory. - ### **Regenerate cgo** + Identifier: `regenerate_cgo` regenerate_cgo regenerates cgo definitions. - ### **Run test(s)** + Identifier: `test` test runs `go test` for a specific test function. - ### **Run go mod tidy** + Identifier: `tidy` tidy runs `go mod tidy` for a module. - ### **Upgrade dependency** + Identifier: `upgrade_dependency` upgrade_dependency upgrades a dependency. - ### **Run go mod vendor** + Identifier: `vendor` vendor runs `go mod vendor` for a module. - ### **Toggle gc_details** + Identifier: `gc_details` gc_details controls calculation of gc annotations. - diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go index e2444e1866..85d91cd301 100755 --- a/internal/lsp/source/api_json.go +++ b/internal/lsp/source/api_json.go @@ -4,104 +4,6 @@ package source var GeneratedAPIJSON = &APIJSON{ Options: map[string][]*OptionJSON{ - "Debugging": { - { - Name: "verboseOutput", - Type: "bool", - Doc: "verboseOutput enables additional debug logging.\n", - EnumValues: nil, - 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", - EnumValues: nil, - Default: "\"100ms\"", - }, - }, - "Experimental": { - { - Name: "annotations", - Type: "map[string]bool", - Doc: "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n", - EnumValues: []EnumValue{ - { - Value: "\"bounds\"", - Doc: "`\"bounds\"` controls bounds checking diagnostics.\n", - }, - { - Value: "\"escape\"", - Doc: "`\"escape\"` controls diagnostics about escape choices.\n", - }, - { - Value: "\"inline\"", - Doc: "`\"inline\"` controls diagnostics about inlining choices.\n", - }, - { - Value: "\"nil\"", - Doc: "`\"nil\"` controls nil checks.\n", - }, - }, - Default: "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}", - }, - { - Name: "staticcheck", - Type: "bool", - Doc: "staticcheck enables additional analyses from staticcheck.io.\n", - EnumValues: nil, - Default: "false", - }, - { - Name: "semanticTokens", - Type: "bool", - Doc: "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n", - EnumValues: nil, - Default: "false", - }, - { - Name: "expandWorkspaceToModule", - Type: "bool", - Doc: "expandWorkspaceToModule instructs `gopls` to adjust the scope of the\nworkspace to find the best available module root. `gopls` first looks for\na go.mod file in any parent directory of the workspace folder, expanding\nthe scope to that directory if it exists. If no viable parent directory is\nfound, gopls will check if there is exactly one child directory containing\na go.mod file, narrowing the scope to that directory if it exists.\n", - EnumValues: nil, - Default: "true", - }, - { - Name: "experimentalWorkspaceModule", - Type: "bool", - Doc: "experimentalWorkspaceModule opts a user into the experimental support\nfor multi-module workspaces.\n", - EnumValues: nil, - Default: "false", - }, - { - Name: "experimentalDiagnosticsDelay", - Type: "time.Duration", - Doc: "experimentalDiagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n", - EnumValues: nil, - Default: "\"250ms\"", - }, - { - Name: "experimentalPackageCacheKey", - Type: "bool", - Doc: "experimentalPackageCacheKey controls whether to use a coarser cache key\nfor package type information to increase cache hits. This setting removes\nthe user's environment, build flags, and working directory from the cache\nkey, which should be a safe change as all relevant inputs into the type\nchecking pass are already hashed into the key. This is temporarily guarded\nby an experiment because caching behavior is subtle and difficult to\ncomprehensively test.\n", - EnumValues: nil, - Default: "true", - }, - { - Name: "allowModfileModifications", - Type: "bool", - Doc: "allowModfileModifications disables -mod=readonly, allowing imports from\nout-of-scope modules. This option will eventually be removed.\n", - EnumValues: nil, - Default: "false", - }, - { - Name: "allowImplicitNetworkAccess", - Type: "bool", - Doc: "allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module\ndownloads rather than requiring user action. This option will eventually\nbe removed.\n", - EnumValues: nil, - Default: "false", - }, - }, "User": { { Name: "buildFlags", @@ -109,6 +11,8 @@ var GeneratedAPIJSON = &APIJSON{ 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", EnumValues: nil, Default: "[]", + Status: "", + Hierarchy: "build", }, { Name: "env", @@ -116,6 +20,62 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "env adds environment variables to external commands run by `gopls`, most notably `go list`.\n", EnumValues: nil, Default: "{}", + Status: "", + Hierarchy: "build", + }, + { + Name: "directoryFilters", + Type: "[]string", + Doc: "directoryFilters can be used to exclude unwanted directories from the\nworkspace. By default, all directories are included. Filters are an\noperator, `+` to include and `-` to exclude, followed by a path prefix\nrelative to the workspace folder. They are evaluated in order, and\nthe last filter that applies to a path controls whether it is included.\nThe path prefix can be empty, so an initial `-` excludes everything.\n\nExamples:\nExclude node_modules: `-node_modules`\nInclude only project_a: `-` (exclude everything), `+project_a`\nInclude only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`\n", + EnumValues: nil, + Default: "[]", + Status: "", + Hierarchy: "build", + }, + { + Name: "expandWorkspaceToModule", + Type: "bool", + Doc: "expandWorkspaceToModule instructs `gopls` to adjust the scope of the\nworkspace to find the best available module root. `gopls` first looks for\na go.mod file in any parent directory of the workspace folder, expanding\nthe scope to that directory if it exists. If no viable parent directory is\nfound, gopls will check if there is exactly one child directory containing\na go.mod file, narrowing the scope to that directory if it exists.\n", + EnumValues: nil, + Default: "true", + Status: "experimental", + Hierarchy: "build", + }, + { + Name: "experimentalWorkspaceModule", + Type: "bool", + Doc: "experimentalWorkspaceModule opts a user into the experimental support\nfor multi-module workspaces.\n", + EnumValues: nil, + Default: "false", + Status: "experimental", + Hierarchy: "build", + }, + { + Name: "experimentalPackageCacheKey", + Type: "bool", + Doc: "experimentalPackageCacheKey controls whether to use a coarser cache key\nfor package type information to increase cache hits. This setting removes\nthe user's environment, build flags, and working directory from the cache\nkey, which should be a safe change as all relevant inputs into the type\nchecking pass are already hashed into the key. This is temporarily guarded\nby an experiment because caching behavior is subtle and difficult to\ncomprehensively test.\n", + EnumValues: nil, + Default: "true", + Status: "experimental", + Hierarchy: "build", + }, + { + Name: "allowModfileModifications", + Type: "bool", + Doc: "allowModfileModifications disables -mod=readonly, allowing imports from\nout-of-scope modules. This option will eventually be removed.\n", + EnumValues: nil, + Default: "false", + Status: "experimental", + Hierarchy: "build", + }, + { + Name: "allowImplicitNetworkAccess", + Type: "bool", + Doc: "allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module\ndownloads rather than requiring user action. This option will eventually\nbe removed.\n", + EnumValues: nil, + Default: "false", + Status: "experimental", + Hierarchy: "build", }, { Name: "hoverKind", @@ -143,14 +103,9 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "", }, }, - Default: "\"FullDocumentation\"", - }, - { - Name: "usePlaceholders", - Type: "bool", - Doc: "placeholders enables placeholders for function parameters or struct fields in completion responses.\n", - EnumValues: nil, - Default: "false", + Default: "\"FullDocumentation\"", + Status: "", + Hierarchy: "ui.documentation", }, { Name: "linkTarget", @@ -158,34 +113,8 @@ var GeneratedAPIJSON = &APIJSON{ 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", EnumValues: nil, 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", - EnumValues: nil, - Default: "\"\"", - }, - { - Name: "gofumpt", - Type: "bool", - Doc: "gofumpt indicates if we should run gofumpt formatting.\n", - EnumValues: nil, - Default: "false", - }, - { - 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", - EnumValues: nil, - Default: "{}", - }, - { - Name: "codelenses", - Type: "map[string]bool", - Doc: "codelenses overrides the enabled/disabled state of code lenses. See the \"Code Lenses\"\nsection of settings.md for the list of supported lenses.\n\nExample Usage:\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n \"gc_details\": true // Show a code lens toggling the display of gc's choices.\n }\n...\n}\n```\n", - EnumValues: nil, - Default: "{\"gc_details\":false,\"generate\":true,\"regenerate_cgo\":true,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}", + Status: "", + Hierarchy: "ui.documentation", }, { Name: "linksInHover", @@ -193,6 +122,48 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "linksInHover toggles the presence of links to documentation in hover.\n", EnumValues: nil, Default: "true", + Status: "", + Hierarchy: "ui.documentation", + }, + { + Name: "usePlaceholders", + Type: "bool", + Doc: "placeholders enables placeholders for function parameters or struct\nfields in completion responses.\n", + EnumValues: nil, + Default: "false", + Status: "", + Hierarchy: "ui.completion", + }, + { + 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", + EnumValues: nil, + Default: "\"100ms\"", + Status: "debug", + Hierarchy: "ui.completion", + }, + { + Name: "matcher", + Type: "enum", + Doc: "matcher sets the algorithm that is used when calculating completion\ncandidates.\n", + EnumValues: []EnumValue{ + { + Value: "\"CaseInsensitive\"", + Doc: "", + }, + { + Value: "\"CaseSensitive\"", + Doc: "", + }, + { + Value: "\"Fuzzy\"", + Doc: "", + }, + }, + Default: "\"Fuzzy\"", + Status: "advanced", + Hierarchy: "ui.completion", }, { Name: "importShortcut", @@ -212,27 +183,9 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "", }, }, - Default: "\"Both\"", - }, - { - Name: "matcher", - Type: "enum", - Doc: "matcher sets the algorithm that is used when calculating completion candidates.\n", - EnumValues: []EnumValue{ - { - Value: "\"CaseInsensitive\"", - Doc: "", - }, - { - Value: "\"CaseSensitive\"", - Doc: "", - }, - { - Value: "\"Fuzzy\"", - Doc: "", - }, - }, - Default: "\"Fuzzy\"", + Default: "\"Both\"", + Status: "", + Hierarchy: "ui.navigation", }, { Name: "symbolMatcher", @@ -252,12 +205,14 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "", }, }, - Default: "\"Fuzzy\"", + Default: "\"Fuzzy\"", + Status: "advanced", + Hierarchy: "ui.navigation", }, { Name: "symbolStyle", Type: "enum", - Doc: "symbolStyle controls how symbols are qualified in symbol responses.\n\nExample Usage:\n```json5\n\"gopls\": {\n...\n \"symbolStyle\": \"dynamic\",\n...\n}\n```\n", + Doc: "symbolStyle controls how symbols are qualified in symbol responses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"symbolStyle\": \"dynamic\",\n...\n}\n```\n", EnumValues: []EnumValue{ { Value: "\"Dynamic\"", @@ -272,14 +227,107 @@ var GeneratedAPIJSON = &APIJSON{ Doc: "`\"Package\"` is package qualified symbols i.e.\n\"pkg.Foo.Field\".\n", }, }, - Default: "\"Dynamic\"", + Default: "\"Dynamic\"", + Status: "advanced", + Hierarchy: "ui.navigation", }, { - Name: "directoryFilters", - Type: "[]string", - Doc: "directoryFilters can be used to exclude unwanted directories from the\nworkspace. By default, all directories are included. Filters are an\noperator, `+` to include and `-` to exclude, followed by a path prefix\nrelative to the workspace folder. They are evaluated in order, and\nthe last filter that applies to a path controls whether it is included.\nThe path prefix can be empty, so an initial `-` excludes everything.\n\nExamples:\nExclude node_modules: `-node_modules`\nInclude only project_a: `-` (exclude everything), `+project_a`\nInclude only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`\n", + 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\n```json5\n...\n\"analyses\": {\n \"unreachable\": false, // Disable the unreachable analyzer.\n \"unusedparams\": true // Enable the unusedparams analyzer.\n}\n...\n```\n", EnumValues: nil, - Default: "[]", + Default: "{}", + Status: "", + Hierarchy: "ui.diagnostic", + }, + { + Name: "staticcheck", + Type: "bool", + Doc: "staticcheck enables additional analyses from staticcheck.io.\n", + EnumValues: nil, + Default: "false", + Status: "experimental", + Hierarchy: "ui.diagnostic", + }, + { + Name: "annotations", + Type: "map[string]bool", + Doc: "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n", + EnumValues: []EnumValue{ + { + Value: "\"bounds\"", + Doc: "`\"bounds\"` controls bounds checking diagnostics.\n", + }, + { + Value: "\"escape\"", + Doc: "`\"escape\"` controls diagnostics about escape choices.\n", + }, + { + Value: "\"inline\"", + Doc: "`\"inline\"` controls diagnostics about inlining choices.\n", + }, + { + Value: "\"nil\"", + Doc: "`\"nil\"` controls nil checks.\n", + }, + }, + Default: "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}", + Status: "experimental", + Hierarchy: "ui.diagnostic", + }, + { + Name: "experimentalDiagnosticsDelay", + Type: "time.Duration", + Doc: "experimentalDiagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n", + EnumValues: nil, + Default: "\"250ms\"", + Status: "experimental", + Hierarchy: "ui.diagnostic", + }, + { + Name: "codelenses", + Type: "map[string]bool", + Doc: "codelenses overrides the enabled/disabled state of code lenses. See the \"Code Lenses\"\nsection of settings.md for the list of supported lenses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelens\": {\n \"generate\": false, // Don't show the `go generate` lens.\n \"gc_details\": true // Show a code lens toggling the display of gc's choices.\n }\n...\n}\n```\n", + EnumValues: nil, + Default: "{\"gc_details\":false,\"generate\":true,\"regenerate_cgo\":true,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}", + Status: "", + Hierarchy: "ui", + }, + { + Name: "semanticTokens", + Type: "bool", + Doc: "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n", + EnumValues: nil, + Default: "false", + Status: "experimental", + Hierarchy: "ui", + }, + { + Name: "local", + Type: "string", + Doc: "local is the equivalent of the `goimports -local` flag, which puts\nimports beginning with this string after third-party packages. It should\nbe the prefix of the import path whose imports should be grouped\nseparately.\n", + EnumValues: nil, + Default: "\"\"", + Status: "", + Hierarchy: "formatting", + }, + { + Name: "gofumpt", + Type: "bool", + Doc: "gofumpt indicates if we should run gofumpt formatting.\n", + EnumValues: nil, + Default: "false", + Status: "", + Hierarchy: "formatting", + }, + { + Name: "verboseOutput", + Type: "bool", + Doc: "verboseOutput enables additional debug logging.\n", + EnumValues: nil, + Default: "false", + Status: "debug", + Hierarchy: "", }, }, }, diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index b1e7ecd109..9ba2773e2b 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -64,8 +64,6 @@ var ( defaultOptions *Options ) -//go:generate go run golang.org/x/tools/internal/lsp/source/genapijson -output api_json.go - // DefaultOptions is the options that are used for Gopls execution independent // of any externally provided configuration (LSP initialization, command // invokation, etc.). @@ -102,34 +100,42 @@ func DefaultOptions() *Options { SupportedCommands: commands, }, UserOptions: UserOptions{ - HoverKind: FullDocumentation, - LinkTarget: "pkg.go.dev", - Codelenses: map[string]bool{ - CommandGenerate.Name: true, - CommandRegenerateCgo.Name: true, - CommandTidy.Name: true, - CommandToggleDetails.Name: false, - CommandUpgradeDependency.Name: true, - CommandVendor.Name: true, + BuildOptions: BuildOptions{ + ExpandWorkspaceToModule: true, + ExperimentalPackageCacheKey: true, }, - LinksInHover: true, - ImportShortcut: Both, - Matcher: Fuzzy, - SymbolMatcher: SymbolFuzzy, - SymbolStyle: DynamicSymbols, - }, - DebuggingOptions: DebuggingOptions{ - CompletionBudget: 100 * time.Millisecond, - }, - ExperimentalOptions: ExperimentalOptions{ - ExpandWorkspaceToModule: true, - ExperimentalPackageCacheKey: true, - ExperimentalDiagnosticsDelay: 250 * time.Millisecond, - Annotations: map[Annotation]bool{ - Bounds: true, - Escape: true, - Inline: true, - Nil: true, + UIOptions: UIOptions{ + DiagnosticOptions: DiagnosticOptions{ + ExperimentalDiagnosticsDelay: 250 * time.Millisecond, + Annotations: map[Annotation]bool{ + Bounds: true, + Escape: true, + Inline: true, + Nil: true, + }, + }, + DocumentationOptions: DocumentationOptions{ + HoverKind: FullDocumentation, + LinkTarget: "pkg.go.dev", + LinksInHover: true, + }, + NavigationOptions: NavigationOptions{ + ImportShortcut: Both, + SymbolMatcher: SymbolFuzzy, + SymbolStyle: DynamicSymbols, + }, + CompletionOptions: CompletionOptions{ + Matcher: Fuzzy, + CompletionBudget: 100 * time.Millisecond, + }, + Codelenses: map[string]bool{ + CommandGenerate.Name: true, + CommandRegenerateCgo.Name: true, + CommandTidy.Name: true, + CommandToggleDetails.Name: false, + CommandUpgradeDependency.Name: true, + CommandVendor.Name: true, + }, }, }, InternalOptions: InternalOptions{ @@ -159,8 +165,6 @@ type Options struct { ClientOptions ServerOptions UserOptions - DebuggingOptions - ExperimentalOptions InternalOptions Hooks } @@ -186,9 +190,7 @@ type ServerOptions struct { SupportedCommands []string } -// UserOptions holds custom Gopls configuration (not part of the LSP) that is -// modified by the client. -type UserOptions struct { +type BuildOptions struct { // 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`. @@ -197,85 +199,6 @@ type UserOptions struct { // Env adds environment variables to external commands run by `gopls`, most notably `go list`. Env map[string]string - // HoverKind controls the information that appears in the hover text. - // SingleLine and Structured are intended for use only by authors of editor plugins. - HoverKind HoverKind - - // 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: - // - // * `"godoc.org"` - // * `"pkg.go.dev"` - // - // If company chooses to use its own `godoc.org`, its address can be used as well. - LinkTarget string - - // 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 - - // 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 - - // Codelenses overrides the enabled/disabled state of code lenses. See the "Code Lenses" - // section of settings.md for the list of supported lenses. - // - // Example Usage: - // ```json5 - // "gopls": { - // ... - // "codelenses": { - // "generate": false, // Don't show the `go generate` lens. - // "gc_details": true // Show a code lens toggling the display of gc's choices. - // } - // ... - // } - // ``` - Codelenses map[string]bool - - // 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. - ImportShortcut ImportShortcut - - // Matcher sets the algorithm that is used when calculating completion candidates. - Matcher Matcher - - // SymbolMatcher sets the algorithm that is used when finding workspace symbols. - SymbolMatcher SymbolMatcher - - // SymbolStyle controls how symbols are qualified in symbol responses. - // - // Example Usage: - // ```json5 - // "gopls": { - // ... - // "symbolStyle": "dynamic", - // ... - // } - // ``` - SymbolStyle SymbolStyle - // DirectoryFilters can be used to exclude unwanted directories from the // workspace. By default, all directories are included. Filters are an // operator, `+` to include and `-` to exclude, followed by a path prefix @@ -288,6 +211,176 @@ type UserOptions struct { // Include only project_a: `-` (exclude everything), `+project_a` // Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules` DirectoryFilters []string + + // ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the + // workspace to find the best available module root. `gopls` first looks for + // a go.mod file in any parent directory of the workspace folder, expanding + // the scope to that directory if it exists. If no viable parent directory is + // found, gopls will check if there is exactly one child directory containing + // a go.mod file, narrowing the scope to that directory if it exists. + ExpandWorkspaceToModule bool `status:"experimental"` + + // ExperimentalWorkspaceModule opts a user into the experimental support + // for multi-module workspaces. + ExperimentalWorkspaceModule bool `status:"experimental"` + + // ExperimentalPackageCacheKey controls whether to use a coarser cache key + // for package type information to increase cache hits. This setting removes + // the user's environment, build flags, and working directory from the cache + // key, which should be a safe change as all relevant inputs into the type + // checking pass are already hashed into the key. This is temporarily guarded + // by an experiment because caching behavior is subtle and difficult to + // comprehensively test. + ExperimentalPackageCacheKey bool `status:"experimental"` + + // AllowModfileModifications disables -mod=readonly, allowing imports from + // out-of-scope modules. This option will eventually be removed. + AllowModfileModifications bool `status:"experimental"` + + // AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module + // downloads rather than requiring user action. This option will eventually + // be removed. + AllowImplicitNetworkAccess bool `status:"experimental"` +} + +type UIOptions struct { + DocumentationOptions + CompletionOptions + NavigationOptions + DiagnosticOptions + + // Codelenses overrides the enabled/disabled state of code lenses. See the "Code Lenses" + // section of settings.md for the list of supported lenses. + // + // Example Usage: + // + // ```json5 + // "gopls": { + // ... + // "codelens": { + // "generate": false, // Don't show the `go generate` lens. + // "gc_details": true // Show a code lens toggling the display of gc's choices. + // } + // ... + // } + // ``` + Codelenses map[string]bool + + // SemanticTokens controls whether the LSP server will send + // semantic tokens to the client. + SemanticTokens bool `status:"experimental"` +} + +type CompletionOptions struct { + // Placeholders enables placeholders for function parameters or struct + // fields in completion responses. + UsePlaceholders bool + + // CompletionBudget is the soft latency goal for completion requests. Most + // requests finish in a couple milliseconds, but in some cases deep + // completions can take much longer. As we use up our budget we + // dynamically reduce the search scope to ensure we return timely + // results. Zero means unlimited. + CompletionBudget time.Duration `status:"debug"` + + // Matcher sets the algorithm that is used when calculating completion + // candidates. + Matcher Matcher `status:"advanced"` +} + +type DocumentationOptions struct { + // HoverKind controls the information that appears in the hover text. + // SingleLine and Structured are intended for use only by authors of editor plugins. + HoverKind HoverKind + + // 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. + LinkTarget string + + // LinksInHover toggles the presence of links to documentation in hover. + LinksInHover bool +} + +type FormattingOptions struct { + // Local is the equivalent of the `goimports -local` flag, which puts + // imports beginning with this string after third-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 +} + +type DiagnosticOptions struct { + // 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 + + // Staticcheck enables additional analyses from staticcheck.io. + Staticcheck bool `status:"experimental"` + + // Annotations specifies the various kinds of optimization diagnostics + // that should be reported by the gc_details command. + Annotations map[Annotation]bool `status:"experimental"` + + // ExperimentalDiagnosticsDelay controls the amount of time that gopls waits + // after the most recent file modification before computing deep diagnostics. + // Simple diagnostics (parsing and type-checking) are always run immediately + // on recently modified packages. + // + // This option must be set to a valid duration string, for example `"250ms"`. + ExperimentalDiagnosticsDelay time.Duration `status:"experimental"` +} + +type NavigationOptions struct { + // ImportShortcut specifies whether import statements should link to + // documentation or go to definitions. + ImportShortcut ImportShortcut + + // SymbolMatcher sets the algorithm that is used when finding workspace symbols. + SymbolMatcher SymbolMatcher `status:"advanced"` + + // SymbolStyle controls how symbols are qualified in symbol responses. + // + // Example Usage: + // + // ```json5 + // "gopls": { + // ... + // "symbolStyle": "dynamic", + // ... + // } + // ``` + SymbolStyle SymbolStyle `status:"advanced"` +} + +// UserOptions holds custom Gopls configuration (not part of the LSP) that is +// modified by the client. +type UserOptions struct { + BuildOptions + UIOptions + FormattingOptions + + // VerboseOutput enables additional debug logging. + VerboseOutput bool `status:"debug"` } // EnvSlice returns Env as a slice of k=v strings. @@ -324,75 +417,6 @@ type Hooks struct { StaticcheckAnalyzers map[string]Analyzer } -// ExperimentalOptions defines configuration for features under active -// development. WARNING: This configuration will be changed in the future. It -// only exists while these features are under development. -type ExperimentalOptions struct { - - // Annotations specifies the various kinds of optimization diagnostics - // that should be reported by the gc_details command. - Annotations map[Annotation]bool - - // Staticcheck enables additional analyses from staticcheck.io. - Staticcheck bool - - // SemanticTokens controls whether the LSP server will send - // semantic tokens to the client. - SemanticTokens bool - - // ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the - // workspace to find the best available module root. `gopls` first looks for - // a go.mod file in any parent directory of the workspace folder, expanding - // the scope to that directory if it exists. If no viable parent directory is - // found, gopls will check if there is exactly one child directory containing - // a go.mod file, narrowing the scope to that directory if it exists. - ExpandWorkspaceToModule bool - - // ExperimentalWorkspaceModule opts a user into the experimental support - // for multi-module workspaces. - ExperimentalWorkspaceModule bool - - // ExperimentalDiagnosticsDelay controls the amount of time that gopls waits - // after the most recent file modification before computing deep diagnostics. - // Simple diagnostics (parsing and type-checking) are always run immediately - // on recently modified packages. - // - // This option must be set to a valid duration string, for example `"250ms"`. - ExperimentalDiagnosticsDelay time.Duration - - // ExperimentalPackageCacheKey controls whether to use a coarser cache key - // for package type information to increase cache hits. This setting removes - // the user's environment, build flags, and working directory from the cache - // key, which should be a safe change as all relevant inputs into the type - // checking pass are already hashed into the key. This is temporarily guarded - // by an experiment because caching behavior is subtle and difficult to - // comprehensively test. - ExperimentalPackageCacheKey bool - - // AllowModfileModifications disables -mod=readonly, allowing imports from - // out-of-scope modules. This option will eventually be removed. - AllowModfileModifications bool - - // AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module - // downloads rather than requiring user action. This option will eventually - // be removed. - AllowImplicitNetworkAccess 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 - // requests finish in a couple milliseconds, but in some cases deep - // completions can take much longer. As we use up our budget we - // dynamically reduce the search scope to ensure we return timely - // results. Zero means unlimited. - CompletionBudget time.Duration -} - // InternalOptions contains settings that are not intended for use by the // average user. These may be settings used by tests or outdated settings that // will soon be deprecated. Some of these settings may not even be configurable @@ -544,8 +568,9 @@ func SetOptions(options *Options, opts interface{}) OptionResults { options.enableAllExperiments() } } + seen := map[string]struct{}{} for name, value := range opts { - results = append(results, options.set(name, value)) + results = append(results, options.set(name, value, seen)) } // Finally, enable any experimental features that are specified in // maps, which allows users to individually toggle them on or off. @@ -589,10 +614,8 @@ func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { func (o *Options) Clone() *Options { result := &Options{ - ClientOptions: o.ClientOptions, - DebuggingOptions: o.DebuggingOptions, - ExperimentalOptions: o.ExperimentalOptions, - InternalOptions: o.InternalOptions, + ClientOptions: o.ClientOptions, + InternalOptions: o.InternalOptions, Hooks: Hooks{ GoDiff: o.Hooks.GoDiff, ComputeEdits: o.Hooks.ComputeEdits, @@ -657,8 +680,17 @@ func (o *Options) enableAllExperimentMaps() { } } -func (o *Options) set(name string, value interface{}) OptionResult { +func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult { + // Flatten the name in case we get options with a hierarchy. + split := strings.Split(name, ".") + name = split[len(split)-1] + result := OptionResult{Name: name, Value: value} + if _, ok := seen[name]; ok { + result.errorf("duplicate configuration for %s", name) + } + seen[name] = struct{}{} + switch name { case "env": menv, ok := value.(map[string]interface{}) @@ -1133,6 +1165,8 @@ type OptionJSON struct { Doc string EnumValues []EnumValue Default string + Status string + Hierarchy string } type EnumValue struct { diff --git a/internal/lsp/source/options_test.go b/internal/lsp/source/options_test.go index dd03044678..83cb7959e8 100644 --- a/internal/lsp/source/options_test.go +++ b/internal/lsp/source/options_test.go @@ -88,6 +88,13 @@ func TestSetOption(t *testing.T) { return o.HoverKind == Structured }, }, + { + name: "ui.documentation.hoverKind", + value: "Structured", + check: func(o Options) bool { + return o.HoverKind == Structured + }, + }, { name: "matcher", value: "Fuzzy", @@ -163,7 +170,7 @@ func TestSetOption(t *testing.T) { for _, test := range tests { var opts Options - result := opts.set(test.name, test.value) + result := opts.set(test.name, test.value, map[string]struct{}{}) if (result.Error != nil) != test.wantError { t.Fatalf("Options.set(%q, %v): result.Error = %v, want error: %t", test.name, test.value, result.Error, test.wantError) }