diff --git a/gopls/doc/generate/generate.go b/gopls/doc/generate/generate.go index 33735270a9c..bb799e52331 100644 --- a/gopls/doc/generate/generate.go +++ b/gopls/doc/generate/generate.go @@ -39,7 +39,6 @@ import ( "golang.org/x/tools/gopls/internal/doc" "golang.org/x/tools/gopls/internal/golang" "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" "golang.org/x/tools/gopls/internal/protocol/command/commandmeta" "golang.org/x/tools/gopls/internal/settings" "golang.org/x/tools/gopls/internal/util/maps" @@ -136,23 +135,12 @@ func loadAPI() (*doc.API, error) { &packages.Config{ Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps, }, - "golang.org/x/tools/gopls/internal/settings", // for settings - "golang.org/x/tools/gopls/internal/protocol", // for lenses + "golang.org/x/tools/gopls/internal/settings", ) if err != nil { return nil, err } - // TODO(adonovan): document at packages.Load that the result - // order does not match the pattern order. - var protocolPkg, settingsPkg *packages.Package - for _, pkg := range pkgs { - switch pkg.Types.Name() { - case "settings": - settingsPkg = pkg - case "protocol": - protocolPkg = pkg - } - } + settingsPkg := pkgs[0] defaults := settings.DefaultOptions() api := &doc.API{ @@ -164,7 +152,7 @@ func loadAPI() (*doc.API, error) { if err != nil { return nil, err } - api.Lenses, err = loadLenses(protocolPkg, defaults.Codelenses) + api.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses) if err != nil { return nil, err } @@ -185,8 +173,8 @@ func loadAPI() (*doc.API, error) { catName := strings.TrimSuffix(category.Type().Name(), "Options") api.Options[catName] = opts - // Hardcode the expected values for the analyses and code lenses - // settings, since their keys are not enums. + // Hardcode the expected values for the "analyses" and "hints" settings, + // since their map keys are strings, not enums. for _, opt := range opts { switch opt.Name { case "analyses": @@ -197,24 +185,9 @@ func loadAPI() (*doc.API, error) { Default: strconv.FormatBool(a.Default), }) } - case "codelenses": - // Hack: Lenses don't set default values, and we don't want to - // pass in the list of expected lenses to loadOptions. Instead, - // format the defaults using reflection here. The hackiest part - // is reversing lowercasing of the field name. - reflectField := category.FieldByName(upperFirst(opt.Name)) - for _, l := range api.Lenses { - def, err := formatDefaultFromEnumBoolMap(reflectField, l.Lens) - if err != nil { - return nil, err - } - opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{ - Name: fmt.Sprintf("%q", l.Lens), - Doc: l.Doc, - Default: def, - }) - } case "hints": + // TODO(adonovan): simplify InlayHints to use an enum, + // following CodeLensSource. for _, a := range api.Hints { opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{ Name: fmt.Sprintf("%q", a.Name), @@ -282,24 +255,48 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa return nil, err } + // Derive the doc-and-api.json type from the Go field type. + // + // In principle, we should use JSON nomenclature here + // (number, array, object, etc; see #68057), but in + // practice we use the Go type string ([]T, map[K]V, + // etc) with only one tweak: enumeration types are + // replaced by "enum", including when they appear as + // map keys. + // + // Notable edge cases: + // - any (e.g. in linksInHover) is really a sum of false | true | "internal". + // - time.Duration is really a string with a particular syntax. typ := typesField.Type().String() if _, ok := enums[typesField.Type()]; ok { typ = "enum" } name := lowerFirst(typesField.Name()) + // enum-keyed maps var enumKeys doc.EnumKeys if m, ok := typesField.Type().Underlying().(*types.Map); ok { - e, ok := enums[m.Key()] + values, ok := enums[m.Key()] if ok { - typ = strings.Replace(typ, m.Key().String(), m.Key().Underlying().String(), 1) - } - keys, err := collectEnumKeys(name, m, reflectField, e) - if err != nil { - return nil, err + // Update type name: "map[CodeLensSource]T" -> "map[enum]T" + // hack: assumes key substring is unique! + typ = strings.Replace(typ, m.Key().String(), "enum", 1) } - if keys != nil { - enumKeys = *keys + + // Edge case: "analyses" is a string (not enum) keyed map, + // but its EnumKeys.ValueType was historically populated. + // (But not "env"; not sure why.) + if ok || name == "analyses" { + enumKeys.ValueType = m.Elem().String() + + // For map[enum]T fields, gather the set of valid + // EnumKeys (from type information). If T=bool, also + // record the default value (from reflection). + keys, err := collectEnumKeys(m, reflectField, values) + if err != nil { + return nil, err + } + enumKeys.Keys = keys } } @@ -350,20 +347,13 @@ func loadEnums(pkg *packages.Package) (map[types.Type][]doc.EnumValue, error) { return enums, nil } -func collectEnumKeys(name string, m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) (*doc.EnumKeys, error) { - // Make sure the value type gets set for analyses and codelenses - // too. - if len(enumValues) == 0 && !hardcodedEnumKeys(name) { - return nil, nil - } - keys := &doc.EnumKeys{ - ValueType: m.Elem().String(), - } +func collectEnumKeys(m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) ([]doc.EnumKey, error) { // We can get default values for enum -> bool maps. var isEnumBoolMap bool if basic, ok := m.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Bool { isEnumBoolMap = true } + var keys []doc.EnumKey for _, v := range enumValues { var def string if isEnumBoolMap { @@ -373,7 +363,7 @@ func collectEnumKeys(name string, m *types.Map, reflectField reflect.Value, enum return nil, err } } - keys.Keys = append(keys.Keys, doc.EnumKey{ + keys = append(keys, doc.EnumKey{ Name: v.Value, Doc: v.Doc, Default: def, @@ -529,19 +519,19 @@ func structDoc(fields []*commandmeta.Field, level int) string { return b.String() } -// loadLenses combines the syntactic comments from the protocol +// loadLenses combines the syntactic comments from the settings // package with the default values from settings.DefaultOptions(), and // returns a list of Code Lens descriptors. -func loadLenses(protocolPkg *packages.Package, defaults map[protocol.CodeLensSource]bool) ([]*doc.Lens, error) { +func loadLenses(settingsPkg *packages.Package, defaults map[settings.CodeLensSource]bool) ([]*doc.Lens, error) { // Find the CodeLensSource enums among the files of the protocol package. // Map each enum value to its doc comment. enumDoc := make(map[string]string) - for _, f := range protocolPkg.Syntax { + for _, f := range settingsPkg.Syntax { for _, decl := range f.Decls { if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.CONST { for _, spec := range decl.Specs { spec := spec.(*ast.ValueSpec) - posn := safetoken.StartPosition(protocolPkg.Fset, spec.Pos()) + posn := safetoken.StartPosition(settingsPkg.Fset, spec.Pos()) if id, ok := spec.Type.(*ast.Ident); ok && id.Name == "CodeLensSource" { if len(spec.Names) != 1 || len(spec.Values) != 1 { return nil, fmt.Errorf("%s: declare one CodeLensSource per line", posn) @@ -566,7 +556,7 @@ func loadLenses(protocolPkg *packages.Package, defaults map[protocol.CodeLensSou // Build list of Lens descriptors. var lenses []*doc.Lens - addAll := func(sources map[protocol.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error { + addAll := func(sources map[settings.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error { slice := maps.Keys(sources) sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] }) for _, source := range slice { @@ -732,10 +722,6 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) { buf.WriteString(opt.Doc) // enums - // - // TODO(adonovan): `CodeLensSource` should be treated as an enum, - // but loadEnums considers only the `settings` package, - // not `protocol`. write := func(name, doc string) { if doc != "" { unbroken := parBreakRE.ReplaceAllString(doc, "\\\n") @@ -745,12 +731,14 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) { } } if len(opt.EnumValues) > 0 && opt.Type == "enum" { + // enum as top-level type constructor buf.WriteString("\nMust be one of:\n\n") for _, val := range opt.EnumValues { write(val.Value, val.Doc) } } else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) { - buf.WriteString("\nCan contain any of:\n\n") + // enum as map key (currently just "annotations") + buf.WriteString("\nEach enum must be one of:\n\n") for _, val := range opt.EnumKeys.Keys { write(val.Name, val.Doc) } @@ -772,7 +760,9 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) { var parBreakRE = regexp.MustCompile("\n{2,}") func shouldShowEnumKeysInSettings(name string) bool { - // These fields have too many possible options to print. + // These fields have too many possible options, + // or too voluminous documentation, to render as enums. + // Instead they each get their own page in the manual. return !(name == "analyses" || name == "codelenses" || name == "hints") } @@ -830,10 +820,6 @@ func collectGroups(opts []*doc.Option) []optionsGroup { return groups } -func hardcodedEnumKeys(name string) bool { - return name == "analyses" || name == "codelenses" -} - func capitalize(s string) string { return string(unicode.ToUpper(rune(s[0]))) + s[1:] } diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index 1a08c56e32a..6aec26fd2a1 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -180,7 +180,7 @@ Default: `false`. ## UI -### `codelenses` *map[golang.org/x/tools/gopls/internal/protocol.CodeLensSource]bool* +### `codelenses` *map[enum]bool* codelenses overrides the enabled/disabled state of each of gopls' sources of [Code Lenses](codelenses.md). @@ -325,14 +325,14 @@ These analyses are documented on Default: `false`. -### `annotations` *map[string]bool* +### `annotations` *map[enum]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: +Each enum must be one of: * `"bounds"` controls bounds checking diagnostics. * `"escape"` controls diagnostics about escape choices. diff --git a/gopls/internal/cmd/codelens.go b/gopls/internal/cmd/codelens.go index b07f15429ce..452e978094f 100644 --- a/gopls/internal/cmd/codelens.go +++ b/gopls/internal/cmd/codelens.go @@ -78,9 +78,9 @@ func (r *codelens) Run(ctx context.Context, args ...string) error { origOptions(opts) } if opts.Codelenses == nil { - opts.Codelenses = make(map[protocol.CodeLensSource]bool) + opts.Codelenses = make(map[settings.CodeLensSource]bool) } - opts.Codelenses[protocol.CodeLensTest] = true + opts.Codelenses[settings.CodeLensTest] = true } conn, err := r.app.connect(ctx) diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index 96ff0545041..ca4a9384899 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json @@ -646,7 +646,7 @@ }, { "Name": "annotations", - "Type": "map[string]bool", + "Type": "map[enum]bool", "Doc": "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n", "EnumKeys": { "ValueType": "bool", @@ -799,49 +799,49 @@ }, { "Name": "codelenses", - "Type": "map[golang.org/x/tools/gopls/internal/protocol.CodeLensSource]bool", + "Type": "map[enum]bool", "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\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", "EnumKeys": { "ValueType": "bool", "Keys": [ { "Name": "\"gc_details\"", - "Doc": "\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", + "Doc": "`\"gc_details\"`: Toggle display of Go compiler optimization decisions\n\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", "Default": "false" }, { "Name": "\"generate\"", - "Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n", + "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n", "Default": "true" }, { "Name": "\"regenerate_cgo\"", - "Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n", + "Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n", "Default": "true" }, { - "Name": "\"test\"", - "Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n", + "Name": "\"run_govulncheck\"", + "Doc": "`\"run_govulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", "Default": "false" }, { - "Name": "\"run_govulncheck\"", - "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", + "Name": "\"test\"", + "Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n", "Default": "false" }, { "Name": "\"tidy\"", - "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n", + "Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n", "Default": "true" }, { "Name": "\"upgrade_dependency\"", - "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n", + "Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n", "Default": "true" }, { "Name": "\"vendor\"", - "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n", + "Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n", "Default": "true" } ] diff --git a/gopls/internal/golang/code_lens.go b/gopls/internal/golang/code_lens.go index 82ff0f5bec0..a4aab3e16b0 100644 --- a/gopls/internal/golang/code_lens.go +++ b/gopls/internal/golang/code_lens.go @@ -17,15 +17,16 @@ import ( "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" ) // CodeLensSources returns the supported sources of code lenses for Go files. -func CodeLensSources() map[protocol.CodeLensSource]cache.CodeLensSourceFunc { - return map[protocol.CodeLensSource]cache.CodeLensSourceFunc{ - protocol.CodeLensGenerate: goGenerateCodeLens, // commands: Generate - protocol.CodeLensTest: runTestCodeLens, // commands: Test - protocol.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo - protocol.CodeLensGCDetails: toggleDetailsCodeLens, // commands: GCDetails +func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc { + return map[settings.CodeLensSource]cache.CodeLensSourceFunc{ + settings.CodeLensGenerate: goGenerateCodeLens, // commands: Generate + settings.CodeLensTest: runTestCodeLens, // commands: Test + settings.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo + settings.CodeLensGCDetails: toggleDetailsCodeLens, // commands: GCDetails } } diff --git a/gopls/internal/mod/code_lens.go b/gopls/internal/mod/code_lens.go index 89942722e75..f23cc641365 100644 --- a/gopls/internal/mod/code_lens.go +++ b/gopls/internal/mod/code_lens.go @@ -15,15 +15,16 @@ import ( "golang.org/x/tools/gopls/internal/file" "golang.org/x/tools/gopls/internal/protocol" "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" ) // CodeLensSources returns the sources of code lenses for go.mod files. -func CodeLensSources() map[protocol.CodeLensSource]cache.CodeLensSourceFunc { - return map[protocol.CodeLensSource]cache.CodeLensSourceFunc{ - protocol.CodeLensUpgradeDependency: upgradeLenses, // commands: CheckUpgrades, UpgradeDependency - protocol.CodeLensTidy: tidyLens, // commands: Tidy - protocol.CodeLensVendor: vendorLens, // commands: Vendor - protocol.CodeLensRunGovulncheck: vulncheckLenses, // commands: RunGovulncheck +func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc { + return map[settings.CodeLensSource]cache.CodeLensSourceFunc{ + settings.CodeLensUpgradeDependency: upgradeLenses, // commands: CheckUpgrades, UpgradeDependency + settings.CodeLensTidy: tidyLens, // commands: Tidy + settings.CodeLensVendor: vendorLens, // commands: Vendor + settings.CodeLensRunGovulncheck: vulncheckLenses, // commands: RunGovulncheck } } diff --git a/gopls/internal/protocol/codeactionkind.go b/gopls/internal/protocol/codeactionkind.go index d493b452f86..045067dd71e 100644 --- a/gopls/internal/protocol/codeactionkind.go +++ b/gopls/internal/protocol/codeactionkind.go @@ -4,7 +4,7 @@ package protocol -// This file defines constants for non-standard CodeActions and CodeLenses. +// This file defines constants for non-standard CodeActions. // CodeAction kinds specific to gopls // @@ -84,114 +84,3 @@ const ( // CodeAction request is unknown. A missing // CodeActionContext.TriggerKind should be treated as equivalent. const CodeActionUnknownTrigger CodeActionTriggerKind = 0 - -// A CodeLensSource identifies an (algorithmic) source of code lenses. -type CodeLensSource string - -// CodeLens sources -// -// These identifiers appear in the "codelenses" configuration setting, -// and in the user documentation thereof, which is generated by -// gopls/doc/generate/generate.go parsing this file. -// -// Doc comments should use GitHub Markdown. -// The first line becomes the title. -// -// (For historical reasons, each code lens source identifier typically -// matches the name of one of the command.Commands returned by it, -// but that isn't essential.) -const ( - // Toggle display of Go compiler optimization decisions - // - // This codelens source causes the `package` declaration of - // each file to be annotated with a command to toggle the - // state of the per-session variable that controls whether - // optimization decisions from the Go compiler (formerly known - // as "gc") should be displayed as diagnostics. - // - // Optimization decisions include: - // - whether a variable escapes, and how escape is inferred; - // - whether a nil-pointer check is implied or eliminated; - // - whether a function can be inlined. - // - // TODO(adonovan): this source is off by default because the - // annotation is annoying and because VS Code has a separate - // "Toggle gc details" command. Replace it with a Code Action - // ("Source action..."). - CodeLensGCDetails CodeLensSource = "gc_details" - - // Run `go generate` - // - // This codelens source annotates any `//go:generate` comments - // with commands to run `go generate` in this directory, on - // all directories recursively beneath this one. - // - // See [Generating code](https://go.dev/blog/generate) for - // more details. - CodeLensGenerate CodeLensSource = "generate" - - // Re-generate cgo declarations - // - // This codelens source annotates an `import "C"` declaration - // with a command to re-run the [cgo - // command](https://pkg.go.dev/cmd/cgo) to regenerate the - // corresponding Go declarations. - // - // Use this after editing the C code in comments attached to - // the import, or in C header files included by it. - CodeLensRegenerateCgo CodeLensSource = "regenerate_cgo" - - // Run govulncheck - // - // This codelens source annotates the `module` directive in a - // go.mod file with a command to run Govulncheck. - // - // [Govulncheck](https://go.dev/blog/vuln) is a static - // analysis tool that computes the set of functions reachable - // within your application, including dependencies; - // queries a database of known security vulnerabilities; and - // reports any potential problems it finds. - CodeLensRunGovulncheck CodeLensSource = "run_govulncheck" - - // Run tests and benchmarks - // - // This codelens source annotates each `Test` and `Benchmark` - // function in a `*_test.go` file with a command to run it. - // - // This source is off by default because VS Code has - // a client-side custom UI for testing, and because progress - // notifications are not a great UX for streamed test output. - // See: - // - golang/go#67400 for a discussion of this feature. - // - https://github.com/joaotavora/eglot/discussions/1402 - // for an alternative approach. - CodeLensTest CodeLensSource = "test" - - // Tidy go.mod file - // - // This codelens source annotates the `module` directive in a - // go.mod file with a command to run [`go mod - // tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures - // that the go.mod file matches the source code in the module. - CodeLensTidy CodeLensSource = "tidy" - - // Update dependencies - // - // This codelens source annotates the `module` directive in a - // go.mod file with commands to: - // - // - check for available upgrades, - // - upgrade direct dependencies, and - // - upgrade all dependencies transitively. - CodeLensUpgradeDependency CodeLensSource = "upgrade_dependency" - - // Update vendor directory - // - // This codelens source annotates the `module` directive in a - // go.mod file with a command to run [`go mod - // vendor`](https://go.dev/ref/mod#go-mod-vendor), which - // creates or updates the directory named `vendor` in the - // module root so that it contains an up-to-date copy of all - // necessary package dependencies. - CodeLensVendor CodeLensSource = "vendor" -) diff --git a/gopls/internal/server/code_lens.go b/gopls/internal/server/code_lens.go index 5a720cdc78b..67b359e866c 100644 --- a/gopls/internal/server/code_lens.go +++ b/gopls/internal/server/code_lens.go @@ -15,6 +15,7 @@ import ( "golang.org/x/tools/gopls/internal/label" "golang.org/x/tools/gopls/internal/mod" "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" "golang.org/x/tools/internal/event" ) @@ -30,7 +31,7 @@ func (s *server) CodeLens(ctx context.Context, params *protocol.CodeLensParams) } defer release() - var lensFuncs map[protocol.CodeLensSource]cache.CodeLensSourceFunc + var lensFuncs map[settings.CodeLensSource]cache.CodeLensSourceFunc switch snapshot.FileKind(fh) { case file.Mod: lensFuncs = mod.CodeLensSources() diff --git a/gopls/internal/settings/default.go b/gopls/internal/settings/default.go index fec64e39d2f..fe7749de1d3 100644 --- a/gopls/internal/settings/default.go +++ b/gopls/internal/settings/default.go @@ -101,14 +101,14 @@ func DefaultOptions(overrides ...func(*Options)) *Options { ExperimentalPostfixCompletions: true, CompleteFunctionCalls: true, }, - Codelenses: map[protocol.CodeLensSource]bool{ - protocol.CodeLensGenerate: true, - protocol.CodeLensRegenerateCgo: true, - protocol.CodeLensTidy: true, - protocol.CodeLensGCDetails: false, - protocol.CodeLensUpgradeDependency: true, - protocol.CodeLensVendor: true, - protocol.CodeLensRunGovulncheck: false, // TODO(hyangah): enable + Codelenses: map[CodeLensSource]bool{ + CodeLensGenerate: true, + CodeLensRegenerateCgo: true, + CodeLensTidy: true, + CodeLensGCDetails: false, + CodeLensUpgradeDependency: true, + CodeLensVendor: true, + CodeLensRunGovulncheck: false, // TODO(hyangah): enable }, }, }, diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go index a5e9518d6a1..3281d206880 100644 --- a/gopls/internal/settings/settings.go +++ b/gopls/internal/settings/settings.go @@ -184,7 +184,7 @@ type UIOptions struct { // ... // } // ``` - Codelenses map[protocol.CodeLensSource]bool + Codelenses map[CodeLensSource]bool // SemanticTokens controls whether the LSP server will send // semantic tokens to the client. @@ -197,6 +197,117 @@ type UIOptions struct { NoSemanticNumber bool `status:"experimental"` } +// A CodeLensSource identifies an (algorithmic) source of code lenses. +type CodeLensSource string + +// CodeLens sources +// +// These identifiers appear in the "codelenses" configuration setting, +// and in the user documentation thereof, which is generated by +// gopls/doc/generate/generate.go parsing this file. +// +// Doc comments should use GitHub Markdown. +// The first line becomes the title. +// +// (For historical reasons, each code lens source identifier typically +// matches the name of one of the command.Commands returned by it, +// but that isn't essential.) +const ( + // Toggle display of Go compiler optimization decisions + // + // This codelens source causes the `package` declaration of + // each file to be annotated with a command to toggle the + // state of the per-session variable that controls whether + // optimization decisions from the Go compiler (formerly known + // as "gc") should be displayed as diagnostics. + // + // Optimization decisions include: + // - whether a variable escapes, and how escape is inferred; + // - whether a nil-pointer check is implied or eliminated; + // - whether a function can be inlined. + // + // TODO(adonovan): this source is off by default because the + // annotation is annoying and because VS Code has a separate + // "Toggle gc details" command. Replace it with a Code Action + // ("Source action..."). + CodeLensGCDetails CodeLensSource = "gc_details" + + // Run `go generate` + // + // This codelens source annotates any `//go:generate` comments + // with commands to run `go generate` in this directory, on + // all directories recursively beneath this one. + // + // See [Generating code](https://go.dev/blog/generate) for + // more details. + CodeLensGenerate CodeLensSource = "generate" + + // Re-generate cgo declarations + // + // This codelens source annotates an `import "C"` declaration + // with a command to re-run the [cgo + // command](https://pkg.go.dev/cmd/cgo) to regenerate the + // corresponding Go declarations. + // + // Use this after editing the C code in comments attached to + // the import, or in C header files included by it. + CodeLensRegenerateCgo CodeLensSource = "regenerate_cgo" + + // Run govulncheck + // + // This codelens source annotates the `module` directive in a + // go.mod file with a command to run Govulncheck. + // + // [Govulncheck](https://go.dev/blog/vuln) is a static + // analysis tool that computes the set of functions reachable + // within your application, including dependencies; + // queries a database of known security vulnerabilities; and + // reports any potential problems it finds. + CodeLensRunGovulncheck CodeLensSource = "run_govulncheck" + + // Run tests and benchmarks + // + // This codelens source annotates each `Test` and `Benchmark` + // function in a `*_test.go` file with a command to run it. + // + // This source is off by default because VS Code has + // a client-side custom UI for testing, and because progress + // notifications are not a great UX for streamed test output. + // See: + // - golang/go#67400 for a discussion of this feature. + // - https://github.com/joaotavora/eglot/discussions/1402 + // for an alternative approach. + CodeLensTest CodeLensSource = "test" + + // Tidy go.mod file + // + // This codelens source annotates the `module` directive in a + // go.mod file with a command to run [`go mod + // tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures + // that the go.mod file matches the source code in the module. + CodeLensTidy CodeLensSource = "tidy" + + // Update dependencies + // + // This codelens source annotates the `module` directive in a + // go.mod file with commands to: + // + // - check for available upgrades, + // - upgrade direct dependencies, and + // - upgrade all dependencies transitively. + CodeLensUpgradeDependency CodeLensSource = "upgrade_dependency" + + // Update vendor directory + // + // This codelens source annotates the `module` directive in a + // go.mod file with a command to run [`go mod + // vendor`](https://go.dev/ref/mod#go-mod-vendor), which + // creates or updates the directory named `vendor` in the + // module root so that it contains an up-to-date copy of all + // necessary package dependencies. + CodeLensVendor CodeLensSource = "vendor" +) + // Note: CompletionOptions must be comparable with reflect.DeepEqual. type CompletionOptions struct { // Placeholders enables placeholders for function parameters or struct @@ -845,7 +956,7 @@ func (o *Options) set(name string, value any, seen map[string]struct{}) error { ModeVulncheckImports) case "codelenses", "codelens": - lensOverrides, err := asBoolMap[protocol.CodeLensSource](value) + lensOverrides, err := asBoolMap[CodeLensSource](value) if err != nil { return err } diff --git a/gopls/internal/test/integration/codelens/codelens_test.go b/gopls/internal/test/integration/codelens/codelens_test.go index 800856d3f7a..75b9fda1fbf 100644 --- a/gopls/internal/test/integration/codelens/codelens_test.go +++ b/gopls/internal/test/integration/codelens/codelens_test.go @@ -10,6 +10,7 @@ import ( "testing" "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" "golang.org/x/tools/gopls/internal/test/compare" . "golang.org/x/tools/gopls/internal/test/integration" "golang.org/x/tools/gopls/internal/util/bug" @@ -54,7 +55,7 @@ const ( }, { label: "generate disabled", - enabled: map[string]bool{string(protocol.CodeLensGenerate): false}, + enabled: map[string]bool{string(settings.CodeLensGenerate): false}, wantCodeLens: false, }, }