blob: 02c59163609bb325649eb7ac68ce04ab0f87dae8 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package settings
import (
"fmt"
"maps"
"path/filepath"
"strings"
"time"
"golang.org/x/tools/gopls/internal/file"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/util/frob"
)
type Annotation string
const (
// Nil controls nil checks.
Nil Annotation = "nil"
// Escape controls diagnostics about escape choices.
Escape Annotation = "escape"
// Inline controls diagnostics about inlining choices.
Inline Annotation = "inline"
// Bounds controls bounds checking diagnostics.
Bounds Annotation = "bounds"
)
// Options holds various configuration that affects Gopls execution, organized
// by the nature or origin of the settings.
//
// Options must be comparable with reflect.DeepEqual, and serializable with
// [frob.Codec].
//
// This type defines both the logic of LSP-supplied option parsing
// (see [SetOptions]), and the public documentation of options in
// ../../doc/settings.md (generated by gopls/doc/generate).
//
// Each exported field of each embedded type such as "ClientOptions"
// contributes a user-visible option setting. The option name is the
// field name rendered in camelCase. Unlike most Go doc comments,
// these fields should be documented using GitHub markdown.
type Options struct {
ClientOptions
ServerOptions
UserOptions
InternalOptions
}
// ClientOptions holds LSP-specific configuration that is provided by the
// client.
//
// ClientOptions must be comparable with reflect.DeepEqual.
type ClientOptions struct {
ClientInfo protocol.ClientInfo
InsertTextFormat protocol.InsertTextFormat
InsertReplaceSupported bool
ConfigurationSupported bool
DynamicConfigurationSupported bool
DynamicRegistrationSemanticTokensSupported bool
DynamicWatchedFilesSupported bool
RelativePatternsSupported bool
PreferredContentFormat protocol.MarkupKind
LineFoldingOnly bool
HierarchicalDocumentSymbolSupport bool
SemanticTypes []string
SemanticMods []string
RelatedInformationSupported bool
CompletionTags bool
CompletionDeprecated bool
SupportedResourceOperations []protocol.ResourceOperationKind
CodeActionResolveOptions []string
}
// ServerOptions holds LSP-specific configuration that is provided by the
// server.
//
// ServerOptions must be comparable with reflect.DeepEqual.
type ServerOptions struct {
SupportedCodeActions map[file.Kind]map[protocol.CodeActionKind]bool
SupportedCommands []string
}
// Note: BuildOptions must be comparable with reflect.DeepEqual.
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`.
BuildFlags []string
// Env adds environment variables to external commands run by `gopls`, most notably `go list`.
Env map[string]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.
//
// DirectoryFilters also supports the `**` operator to match 0 or more directories.
//
// Examples:
//
// Exclude node_modules at current depth: `-node_modules`
//
// Exclude node_modules at any depth: `-**/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`
DirectoryFilters []string
// TemplateExtensions gives the extensions of file names that are treated
// as template files. (The extension
// is the part of the file name after the final dot.)
TemplateExtensions []string
// obsolete, no effect
MemoryMode string `status:"experimental"`
// ExpandWorkspaceToModule determines which packages are considered
// "workspace packages" when the workspace is using modules.
//
// Workspace packages affect the scope of workspace-wide operations. Notably,
// gopls diagnoses all packages considered to be part of the workspace after
// every keystroke, so by setting "ExpandWorkspaceToModule" to false, and
// opening a nested workspace directory, you can reduce the amount of work
// gopls has to do to keep your workspace up to date.
ExpandWorkspaceToModule bool `status:"experimental"`
// StandaloneTags specifies a set of build constraints that identify
// individual Go source files that make up the entire main package of an
// executable.
//
// A common example of standalone main files is the convention of using the
// directive `//go:build ignore` to denote files that are not intended to be
// included in any package, for example because they are invoked directly by
// the developer using `go run`.
//
// Gopls considers a file to be a standalone main file if and only if it has
// package name "main" and has a build directive of the exact form
// "//go:build tag" or "// +build tag", where tag is among the list of tags
// configured by this setting. Notably, if the build constraint is more
// complicated than a simple tag (such as the composite constraint
// `//go:build tag && go1.18`), the file is not considered to be a standalone
// main file.
//
// This setting is only supported when gopls is built with Go 1.16 or later.
StandaloneTags []string
}
// Note: UIOptions must be comparable with reflect.DeepEqual.
type UIOptions struct {
DocumentationOptions
CompletionOptions
NavigationOptions
DiagnosticOptions
InlayHintOptions
// Codelenses overrides the enabled/disabled state of each of gopls'
// sources of [Code Lenses](codelenses.md).
//
// 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[CodeLensSource]bool
// SemanticTokens controls whether the LSP server will send
// semantic tokens to the client.
SemanticTokens bool `status:"experimental"`
// NoSemanticString turns off the sending of the semantic token 'string'
NoSemanticString bool `status:"experimental"`
// NoSemanticNumber turns off the sending of the semantic token 'number'
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
// 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"`
// ExperimentalPostfixCompletions enables artificial method snippets
// such as "someSlice.sort!".
ExperimentalPostfixCompletions bool `status:"experimental"`
// CompleteFunctionCalls enables function call completion.
//
// When completing a statement, or when a function return type matches the
// expected of the expression being completed, completion may suggest call
// expressions (i.e. may include parentheses).
CompleteFunctionCalls bool
}
// Note: DocumentationOptions must be comparable with reflect.DeepEqual.
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 is the base URL for links to Go package
// documentation returned by LSP operations such as Hover and
// DocumentLinks and in the CodeDescription field of each
// Diagnostic.
//
// 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.
//
// Modules matching the GOPRIVATE environment variable will not have
// documentation links in hover.
LinkTarget string
// LinksInHover controls the presence of documentation links in hover markdown.
LinksInHover LinksInHoverEnum
}
// LinksInHoverEnum has legal values:
//
// - `false`, for no links;
// - `true`, for links to the `linkTarget` domain; or
// - `"gopls"`, for links to gopls' internal documentation viewer.
//
// Note: this type has special logic in loadEnums in generate.go.
// Be sure to reflect enum and doc changes there!
type LinksInHoverEnum int
const (
LinksInHover_None LinksInHoverEnum = iota
LinksInHover_LinkTarget
LinksInHover_Gopls
)
// MarshalJSON implements the json.Marshaler interface, so that the default
// values are formatted correctly in documentation. (See [Options.setOne] for
// the flexible custom unmarshalling behavior).
func (l LinksInHoverEnum) MarshalJSON() ([]byte, error) {
switch l {
case LinksInHover_None:
return []byte("false"), nil
case LinksInHover_LinkTarget:
return []byte("true"), nil
case LinksInHover_Gopls:
return []byte(`"gopls"`), nil
default:
return nil, fmt.Errorf("invalid LinksInHover value %d", l)
}
}
// Note: FormattingOptions must be comparable with reflect.DeepEqual.
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.
//
// It is used when tidying imports (during an LSP Organize
// Imports request) or when inserting new ones (for example,
// during completion); an LSP Formatting request merely sorts the
// existing imports.
Local string
// Gofumpt indicates if we should run gofumpt formatting.
Gofumpt bool
}
// Note: DiagnosticOptions must be comparable with reflect.DeepEqual.
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 in
// [analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
//
// Example Usage:
//
// ```json5
// ...
// "analyses": {
// "unreachable": false, // Disable the unreachable analyzer.
// "unusedvariable": true // Enable the unusedvariable analyzer.
// }
// ...
// ```
Analyses map[string]bool
// Staticcheck enables additional analyses from staticcheck.io.
// These analyses are documented on
// [Staticcheck's website](https://staticcheck.io/docs/checks/).
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"`
// Vulncheck enables vulnerability scanning.
Vulncheck VulncheckMode `status:"experimental"`
// DiagnosticsDelay 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"`.
DiagnosticsDelay time.Duration `status:"advanced"`
// DiagnosticsTrigger controls when to run diagnostics.
DiagnosticsTrigger DiagnosticsTrigger `status:"experimental"`
// AnalysisProgressReporting controls whether gopls sends progress
// notifications when construction of its index of analysis facts is taking a
// long time. Cancelling these notifications will cancel the indexing task,
// though it will restart after the next change in the workspace.
//
// When a package is opened for the first time and heavyweight analyses such as
// staticcheck are enabled, it can take a while to construct the index of
// analysis facts for all its dependencies. The index is cached in the
// filesystem, so subsequent analysis should be faster.
AnalysisProgressReporting bool
}
type InlayHintOptions struct {
// Hints specify inlay hints that users want to see. A full list of hints
// that gopls uses can be found in
// [inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
Hints map[InlayHint]bool `status:"experimental"`
}
// An InlayHint identifies a category of hint that may be
// independently requested through the "hints" setting.
type InlayHint string
// This is the source from which gopls/doc/inlayHints.md is generated.
const (
// ParameterNames controls inlay hints for parameter names:
// ```go
// parseInt(/* str: */ "123", /* radix: */ 8)
// ```
ParameterNames InlayHint = "parameterNames"
// AssignVariableTypes controls inlay hints for variable types in assign statements:
// ```go
// i/* int*/, j/* int*/ := 0, len(r)-1
// ```
AssignVariableTypes InlayHint = "assignVariableTypes"
// ConstantValues controls inlay hints for constant values:
// ```go
// const (
// KindNone Kind = iota/* = 0*/
// KindPrint/* = 1*/
// KindPrintf/* = 2*/
// KindErrorf/* = 3*/
// )
// ```
ConstantValues InlayHint = "constantValues"
// RangeVariableTypes controls inlay hints for variable types in range statements:
// ```go
// for k/* int*/, v/* string*/ := range []string{} {
// fmt.Println(k, v)
// }
// ```
RangeVariableTypes InlayHint = "rangeVariableTypes"
// CompositeLiteralTypes controls inlay hints for composite literal types:
// ```go
// for _, c := range []struct {
// in, want string
// }{
// /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"},
// }
// ```
CompositeLiteralTypes InlayHint = "compositeLiteralTypes"
// CompositeLiteralFieldNames inlay hints for composite literal field names:
// ```go
// {/*in: */"Hello, world", /*want: */"dlrow ,olleH"}
// ```
CompositeLiteralFieldNames InlayHint = "compositeLiteralFields"
// FunctionTypeParameters inlay hints for implicit type parameters on generic functions:
// ```go
// myFoo/*[int, string]*/(1, "hello")
// ```
FunctionTypeParameters InlayHint = "functionTypeParameters"
)
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"`
// SymbolScope controls which packages are searched for workspace/symbol
// requests. When the scope is "workspace", gopls searches only workspace
// packages. When the scope is "all", gopls searches all loaded packages,
// including dependencies and the standard library.
SymbolScope SymbolScope
}
// UserOptions holds custom Gopls configuration (not part of the LSP) that is
// modified by the client.
//
// UserOptions must be comparable with reflect.DeepEqual.
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.
func (u *UserOptions) EnvSlice() []string {
var result []string
for k, v := range u.Env {
result = append(result, fmt.Sprintf("%v=%v", k, v))
}
return result
}
// SetEnvSlice sets Env from a slice of k=v strings.
func (u *UserOptions) SetEnvSlice(env []string) {
u.Env = map[string]string{}
for _, kv := range env {
split := strings.SplitN(kv, "=", 2)
if len(split) != 2 {
continue
}
u.Env[split[0]] = split[1]
}
}
// 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
// by the user.
//
// TODO(rfindley): even though these settings are not intended for
// modification, some of them should be surfaced in our documentation.
type InternalOptions struct {
// VerboseWorkDoneProgress controls whether the LSP server should send
// progress reports for all work done outside the scope of an RPC.
// Used by the regression tests.
VerboseWorkDoneProgress bool
// The following options were previously available to users, but they
// really shouldn't be configured by anyone other than "power users".
// CompletionDocumentation enables documentation with completion results.
CompletionDocumentation bool
// CompleteUnimported enables completion for packages that you do not
// currently import.
CompleteUnimported bool
// DeepCompletion enables the ability to return completions from deep
// inside relevant entities, rather than just the locally accessible ones.
//
// Consider this example:
//
// ```go
// package main
//
// import "fmt"
//
// type wrapString struct {
// str string
// }
//
// func main() {
// x := wrapString{"hello world"}
// fmt.Printf(<>)
// }
// ```
//
// At the location of the `<>` in this program, deep completion would suggest
// the result `x.str`.
DeepCompletion bool
// ShowBugReports causes a message to be shown when the first bug is reported
// on the server.
// This option applies only during initialization.
ShowBugReports bool
// SubdirWatchPatterns configures the file watching glob patterns registered
// by gopls.
//
// Some clients (namely VS Code) do not send workspace/didChangeWatchedFile
// notifications for files contained in a directory when that directory is
// deleted:
// https://github.com/microsoft/vscode/issues/109754
//
// In this case, gopls would miss important notifications about deleted
// packages. To work around this, gopls registers a watch pattern for each
// directory containing Go files.
//
// Unfortunately, other clients experience performance problems with this
// many watch patterns, so there is no single behavior that works well for
// all clients.
//
// The "subdirWatchPatterns" setting allows configuring this behavior. Its
// default value of "auto" attempts to guess the correct behavior based on
// the client name. We'd love to avoid this specialization, but as described
// above there is no single value that works for all clients.
//
// If any LSP client does not behave well with the default value (for
// example, if like VS Code it drops file notifications), please file an
// issue.
SubdirWatchPatterns SubdirWatchPatterns
// ReportAnalysisProgressAfter sets the duration for gopls to wait before starting
// progress reporting for ongoing go/analysis passes.
//
// It is intended to be used for testing only.
ReportAnalysisProgressAfter time.Duration
// TelemetryPrompt controls whether gopls prompts about enabling Go telemetry.
//
// Once the prompt is answered, gopls doesn't ask again, but TelemetryPrompt
// can prevent the question from ever being asked in the first place.
TelemetryPrompt bool
// LinkifyShowMessage controls whether the client wants gopls
// to linkify links in showMessage. e.g. [go.dev](https://go.dev).
LinkifyShowMessage bool
// IncludeReplaceInWorkspace controls whether locally replaced modules in a
// go.mod file are treated like workspace modules.
// Or in other words, if a go.mod file with local replaces behaves like a
// go.work file.
IncludeReplaceInWorkspace bool
// ZeroConfig enables the zero-config algorithm for workspace layout,
// dynamically creating build configurations for different modules,
// directories, and GOOS/GOARCH combinations to cover open files.
ZeroConfig bool
// PullDiagnostics enables support for pull diagnostics.
//
// TODO(rfindley): make pull diagnostics robust, and remove this option,
// allowing pull diagnostics by default.
PullDiagnostics bool
// AddTestSourceCodeAction enables support for adding test as a source code
// action.
// TODO(hxjiang): remove this option once the feature is implemented.
AddTestSourceCodeAction bool
}
type SubdirWatchPatterns string
const (
SubdirWatchPatternsOn SubdirWatchPatterns = "on"
SubdirWatchPatternsOff SubdirWatchPatterns = "off"
SubdirWatchPatternsAuto SubdirWatchPatterns = "auto"
)
type ImportShortcut string
const (
BothShortcuts ImportShortcut = "Both"
LinkShortcut ImportShortcut = "Link"
DefinitionShortcut ImportShortcut = "Definition"
)
func (s ImportShortcut) ShowLinks() bool {
return s == BothShortcuts || s == LinkShortcut
}
func (s ImportShortcut) ShowDefinition() bool {
return s == BothShortcuts || s == DefinitionShortcut
}
type Matcher string
const (
Fuzzy Matcher = "Fuzzy"
CaseInsensitive Matcher = "CaseInsensitive"
CaseSensitive Matcher = "CaseSensitive"
)
// A SymbolMatcher controls the matching of symbols for workspace/symbol
// requests.
type SymbolMatcher string
const (
SymbolFuzzy SymbolMatcher = "Fuzzy"
SymbolFastFuzzy SymbolMatcher = "FastFuzzy"
SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
SymbolCaseSensitive SymbolMatcher = "CaseSensitive"
)
// A SymbolStyle controls the formatting of symbols in workspace/symbol results.
type SymbolStyle string
const (
// PackageQualifiedSymbols is package qualified symbols i.e.
// "pkg.Foo.Field".
PackageQualifiedSymbols SymbolStyle = "Package"
// FullyQualifiedSymbols is fully qualified symbols, i.e.
// "path/to/pkg.Foo.Field".
FullyQualifiedSymbols SymbolStyle = "Full"
// DynamicSymbols uses whichever qualifier results in the highest scoring
// match for the given symbol query. Here a "qualifier" is any "/" or "."
// delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
// just "Foo.Field".
DynamicSymbols SymbolStyle = "Dynamic"
)
// A SymbolScope controls the search scope for workspace/symbol requests.
type SymbolScope string
const (
// WorkspaceSymbolScope matches symbols in workspace packages only.
WorkspaceSymbolScope SymbolScope = "workspace"
// AllSymbolScope matches symbols in any loaded package, including
// dependencies.
AllSymbolScope SymbolScope = "all"
)
type HoverKind string
const (
SingleLine HoverKind = "SingleLine"
NoDocumentation HoverKind = "NoDocumentation"
SynopsisDocumentation HoverKind = "SynopsisDocumentation"
FullDocumentation HoverKind = "FullDocumentation"
// Structured is an experimental setting that returns a structured hover format.
// This format separates the signature from the documentation, so that the client
// can do more manipulation of these fields.
//
// This should only be used by clients that support this behavior.
Structured HoverKind = "Structured"
)
type VulncheckMode string
const (
// Disable vulnerability analysis.
ModeVulncheckOff VulncheckMode = "Off"
// In Imports mode, `gopls` will report vulnerabilities that affect packages
// directly and indirectly used by the analyzed main module.
ModeVulncheckImports VulncheckMode = "Imports"
// TODO: VulncheckRequire, VulncheckCallgraph
)
type DiagnosticsTrigger string
const (
// Trigger diagnostics on file edit and save. (default)
DiagnosticsOnEdit DiagnosticsTrigger = "Edit"
// Trigger diagnostics only on file save. Events like initial workspace load
// or configuration change will still trigger diagnostics.
DiagnosticsOnSave DiagnosticsTrigger = "Save"
// TODO: support "Manual"?
)
// Set updates *options based on the provided JSON value:
// null, bool, string, number, array, or object.
// On failure, it returns one or more non-nil errors.
func (o *Options) Set(value any) (errors []error) {
switch value := value.(type) {
case nil:
case map[string]any:
seen := make(map[string]struct{})
for name, value := range value {
// Use only the last segment of a dotted name such as
// ui.navigation.symbolMatcher. The other segments
// are discarded, even without validation (!).
// (They are supported to enable hierarchical names
// in the VS Code graphical configuration UI.)
split := strings.Split(name, ".")
name = split[len(split)-1]
if _, ok := seen[name]; ok {
errors = append(errors, fmt.Errorf("duplicate value for %s", name))
}
seen[name] = struct{}{}
if err := o.setOne(name, value); err != nil {
err := fmt.Errorf("setting option %q: %w", name, err)
errors = append(errors, err)
}
}
default:
errors = append(errors, fmt.Errorf("invalid options type %T (want JSON null or object)", value))
}
return errors
}
func (o *Options) ForClientCapabilities(clientInfo *protocol.ClientInfo, caps protocol.ClientCapabilities) {
if clientInfo != nil {
o.ClientInfo = *clientInfo
}
if caps.Workspace.WorkspaceEdit != nil {
o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations
}
// Check if the client supports snippets in completion items.
if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
o.InsertTextFormat = protocol.SnippetTextFormat
}
o.InsertReplaceSupported = caps.TextDocument.Completion.CompletionItem.InsertReplaceSupport
// Check if the client supports configuration messages.
o.ConfigurationSupported = caps.Workspace.Configuration
o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
o.DynamicRegistrationSemanticTokensSupported = caps.TextDocument.SemanticTokens.DynamicRegistration
o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
o.RelativePatternsSupported = caps.Workspace.DidChangeWatchedFiles.RelativePatternSupport
// Check which types of content format are supported by this client.
if hover := caps.TextDocument.Hover; hover != nil && len(hover.ContentFormat) > 0 {
o.PreferredContentFormat = hover.ContentFormat[0]
}
// Check if the client supports only line folding.
if fr := caps.TextDocument.FoldingRange; fr != nil {
o.LineFoldingOnly = fr.LineFoldingOnly
}
// Check if the client supports hierarchical document symbols.
o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
// Client's semantic tokens
o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes
o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers
// we don't need Requests, as we support full functionality
// we don't need Formats, as there is only one, for now
// Check if the client supports diagnostic related information.
o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation
// Check if the client completion support includes tags (preferred) or deprecation
if caps.TextDocument.Completion.CompletionItem.TagSupport != nil &&
caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil {
o.CompletionTags = true
} else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport {
o.CompletionDeprecated = true
}
// Check if the client supports code actions resolving.
if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil {
o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties
}
}
var codec = frob.CodecFor[*Options]()
func (o *Options) Clone() *Options {
data := codec.Encode(o)
var clone *Options
codec.Decode(data, &clone)
return clone
}
// validateDirectoryFilter validates if the filter string
// - is not empty
// - start with either + or -
// - doesn't contain currently unsupported glob operators: *, ?
func validateDirectoryFilter(ifilter string) (string, error) {
filter := fmt.Sprint(ifilter)
if filter == "" || (filter[0] != '+' && filter[0] != '-') {
return "", fmt.Errorf("invalid filter %v, must start with + or -", filter)
}
segs := strings.Split(filter[1:], "/")
unsupportedOps := [...]string{"?", "*"}
for _, seg := range segs {
if seg != "**" {
for _, op := range unsupportedOps {
if strings.Contains(seg, op) {
return "", fmt.Errorf("invalid filter %v, operator %v not supported. If you want to have this operator supported, consider filing an issue.", filter, op)
}
}
}
}
return strings.TrimRight(filepath.FromSlash(filter), "/"), nil
}
// setOne updates a field of o based on the name and value.
// It returns an error if the value was invalid or duplicate.
// It is the caller's responsibility to augment the error with 'name'.
func (o *Options) setOne(name string, value any) error {
switch name {
case "env":
env, ok := value.(map[string]any)
if !ok {
return fmt.Errorf("invalid type %T (want JSON object)", value)
}
if o.Env == nil {
o.Env = make(map[string]string)
}
for k, v := range env {
// For historic compatibility, we accept int too (e.g. CGO_ENABLED=1).
switch v.(type) {
case string, int:
o.Env[k] = fmt.Sprint(v)
default:
return fmt.Errorf("invalid map value %T (want string)", v)
}
}
case "buildFlags":
return setStringSlice(&o.BuildFlags, value)
case "directoryFilters":
filterStrings, err := asStringSlice(value)
if err != nil {
return err
}
var filters []string
for _, filterStr := range filterStrings {
filter, err := validateDirectoryFilter(filterStr)
if err != nil {
return err
}
filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
}
o.DirectoryFilters = filters
case "completionDocumentation":
return setBool(&o.CompletionDocumentation, value)
case "usePlaceholders":
return setBool(&o.UsePlaceholders, value)
case "deepCompletion":
return setBool(&o.DeepCompletion, value)
case "completeUnimported":
return setBool(&o.CompleteUnimported, value)
case "addTestSourceCodeAction":
return setBool(&o.AddTestSourceCodeAction, value)
case "completionBudget":
return setDuration(&o.CompletionBudget, value)
case "matcher":
return setEnum(&o.Matcher, value,
Fuzzy,
CaseSensitive,
CaseInsensitive)
case "symbolMatcher":
return setEnum(&o.SymbolMatcher, value,
SymbolFuzzy,
SymbolFastFuzzy,
SymbolCaseInsensitive,
SymbolCaseSensitive)
case "symbolStyle":
return setEnum(&o.SymbolStyle, value,
FullyQualifiedSymbols,
PackageQualifiedSymbols,
DynamicSymbols)
case "symbolScope":
return setEnum(&o.SymbolScope, value,
WorkspaceSymbolScope,
AllSymbolScope)
case "hoverKind":
return setEnum(&o.HoverKind, value,
NoDocumentation,
SingleLine,
SynopsisDocumentation,
FullDocumentation,
Structured)
case "linkTarget":
return setString(&o.LinkTarget, value)
case "linksInHover":
switch value {
case false:
o.LinksInHover = LinksInHover_None
case true:
o.LinksInHover = LinksInHover_LinkTarget
case "gopls":
o.LinksInHover = LinksInHover_Gopls
default:
return fmt.Errorf(`invalid value %s; expect false, true, or "gopls"`,
value)
}
case "importShortcut":
return setEnum(&o.ImportShortcut, value,
BothShortcuts,
LinkShortcut,
DefinitionShortcut)
case "analyses":
if err := setBoolMap(&o.Analyses, value); err != nil {
return err
}
if o.Analyses["fieldalignment"] {
return deprecatedError("the 'fieldalignment' analyzer was removed in gopls/v0.17.0; instead, hover over struct fields to see size/offset information (https://go.dev/issue/66861)")
}
case "hints":
return setBoolMap(&o.Hints, value)
case "annotations":
return setAnnotationMap(&o.Annotations, value)
case "vulncheck":
return setEnum(&o.Vulncheck, value,
ModeVulncheckOff,
ModeVulncheckImports)
case "codelenses", "codelens":
lensOverrides, err := asBoolMap[CodeLensSource](value)
if err != nil {
return err
}
if o.Codelenses == nil {
o.Codelenses = make(map[CodeLensSource]bool)
}
o.Codelenses = maps.Clone(o.Codelenses)
for source, enabled := range lensOverrides {
o.Codelenses[source] = enabled
}
if name == "codelens" {
return deprecatedError("codelenses")
}
case "staticcheck":
return setBool(&o.Staticcheck, value)
case "local":
return setString(&o.Local, value)
case "verboseOutput":
return setBool(&o.VerboseOutput, value)
case "verboseWorkDoneProgress":
return setBool(&o.VerboseWorkDoneProgress, value)
case "showBugReports":
return setBool(&o.ShowBugReports, value)
case "gofumpt":
return setBool(&o.Gofumpt, value)
case "completeFunctionCalls":
return setBool(&o.CompleteFunctionCalls, value)
case "semanticTokens":
return setBool(&o.SemanticTokens, value)
case "noSemanticString":
return setBool(&o.NoSemanticString, value)
case "noSemanticNumber":
return setBool(&o.NoSemanticNumber, value)
case "expandWorkspaceToModule":
// See golang/go#63536: we can consider deprecating
// expandWorkspaceToModule, but probably need to change the default
// behavior in that case to *not* expand to the module.
return setBool(&o.ExpandWorkspaceToModule, value)
case "experimentalPostfixCompletions":
return setBool(&o.ExperimentalPostfixCompletions, value)
case "templateExtensions":
switch value := value.(type) {
case []any:
return setStringSlice(&o.TemplateExtensions, value)
case nil:
o.TemplateExtensions = nil
default:
return fmt.Errorf("unexpected type %T (want JSON array of string)", value)
}
case "diagnosticsDelay":
return setDuration(&o.DiagnosticsDelay, value)
case "diagnosticsTrigger":
return setEnum(&o.DiagnosticsTrigger, value,
DiagnosticsOnEdit,
DiagnosticsOnSave)
case "analysisProgressReporting":
return setBool(&o.AnalysisProgressReporting, value)
case "allowImplicitNetworkAccess":
return deprecatedError("")
case "standaloneTags":
return setStringSlice(&o.StandaloneTags, value)
case "subdirWatchPatterns":
return setEnum(&o.SubdirWatchPatterns, value,
SubdirWatchPatternsOn,
SubdirWatchPatternsOff,
SubdirWatchPatternsAuto)
case "reportAnalysisProgressAfter":
return setDuration(&o.ReportAnalysisProgressAfter, value)
case "telemetryPrompt":
return setBool(&o.TelemetryPrompt, value)
case "linkifyShowMessage":
return setBool(&o.LinkifyShowMessage, value)
case "includeReplaceInWorkspace":
return setBool(&o.IncludeReplaceInWorkspace, value)
case "zeroConfig":
return setBool(&o.ZeroConfig, value)
case "pullDiagnostics":
return setBool(&o.PullDiagnostics, value)
// deprecated and renamed settings
//
// These should never be deleted: there is essentially no cost
// to providing a better error message indefinitely; it's not
// as if we would ever want to recycle the name of a setting.
// renamed
case "experimentalDisabledAnalyses":
return deprecatedError("analyses")
case "disableDeepCompletion":
return deprecatedError("deepCompletion")
case "disableFuzzyMatching":
return deprecatedError("fuzzyMatching")
case "wantCompletionDocumentation":
return deprecatedError("completionDocumentation")
case "wantUnimportedCompletions":
return deprecatedError("completeUnimported")
case "fuzzyMatching":
return deprecatedError("matcher")
case "caseSensitiveCompletion":
return deprecatedError("matcher")
case "experimentalDiagnosticsDelay":
return deprecatedError("diagnosticsDelay")
// deprecated
case "memoryMode":
return deprecatedError("")
case "tempModFile":
return deprecatedError("")
case "experimentalWorkspaceModule":
return deprecatedError("")
case "experimentalTemplateSupport":
return deprecatedError("")
case "experimentalWatchedFileDelay":
return deprecatedError("")
case "experimentalPackageCacheKey":
return deprecatedError("")
case "allowModfileModifications":
return deprecatedError("")
case "allExperiments":
// golang/go#65548: this setting is a no-op, but we fail don't report it as
// deprecated, since the nightly VS Code injects it.
//
// If, in the future, VS Code stops injecting this, we could theoretically
// report an error here, but it also seems harmless to keep ignoring this
// setting forever.
case "experimentalUseInvalidMetadata":
return deprecatedError("")
case "newDiff":
return deprecatedError("")
case "wantSuggestedFixes":
return deprecatedError("")
case "noIncrementalSync":
return deprecatedError("")
case "watchFileChanges":
return deprecatedError("")
case "go-diff":
return deprecatedError("")
default:
return fmt.Errorf("unexpected setting")
}
return nil
}
// A SoftError is an error that does not affect the functionality of gopls.
type SoftError struct {
msg string
}
func (e *SoftError) Error() string {
return e.msg
}
// softErrorf reports a soft error related to the current option.
func softErrorf(format string, args ...any) error {
return &SoftError{fmt.Sprintf(format, args...)}
}
// deprecatedError reports the current setting as deprecated.
// The optional replacement is suggested to the user.
func deprecatedError(replacement string) error {
msg := fmt.Sprintf("this setting is deprecated")
if replacement != "" {
msg = fmt.Sprintf("%s, use %q instead", msg, replacement)
}
return &SoftError{msg}
}
// setT() and asT() helpers: the setT forms write to the 'dest *T'
// variable only on success, to reduce boilerplate in Option.set.
func setBool(dest *bool, value any) error {
b, err := asBool(value)
if err != nil {
return err
}
*dest = b
return nil
}
func asBool(value any) (bool, error) {
b, ok := value.(bool)
if !ok {
return false, fmt.Errorf("invalid type %T (want bool)", value)
}
return b, nil
}
func setDuration(dest *time.Duration, value any) error {
str, err := asString(value)
if err != nil {
return err
}
parsed, err := time.ParseDuration(str)
if err != nil {
return err
}
*dest = parsed
return nil
}
func setAnnotationMap(dest *map[Annotation]bool, value any) error {
all, err := asBoolMap[string](value)
if err != nil {
return err
}
if all == nil {
return nil
}
// Default to everything enabled by default.
m := make(map[Annotation]bool)
for k, enabled := range all {
var a Annotation
if err := setEnum(&a, k,
Nil,
Escape,
Inline,
Bounds); err != nil {
// In case of an error, process any legacy values.
switch k {
case "noEscape":
m[Escape] = false
return fmt.Errorf(`"noEscape" is deprecated, set "Escape: false" instead`)
case "noNilcheck":
m[Nil] = false
return fmt.Errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`)
case "noInline":
m[Inline] = false
return fmt.Errorf(`"noInline" is deprecated, set "Inline: false" instead`)
case "noBounds":
m[Bounds] = false
return fmt.Errorf(`"noBounds" is deprecated, set "Bounds: false" instead`)
default:
return err
}
}
m[a] = enabled
}
*dest = m
return nil
}
func setBoolMap[K ~string](dest *map[K]bool, value any) error {
m, err := asBoolMap[K](value)
if err != nil {
return err
}
*dest = m
return nil
}
func asBoolMap[K ~string](value any) (map[K]bool, error) {
all, ok := value.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid type %T (want JSON object)", value)
}
m := make(map[K]bool)
for a, enabled := range all {
b, ok := enabled.(bool)
if !ok {
return nil, fmt.Errorf("invalid type %T for object field %q", enabled, a)
}
m[K(a)] = b
}
return m, nil
}
func setString(dest *string, value any) error {
str, err := asString(value)
if err != nil {
return err
}
*dest = str
return nil
}
func asString(value any) (string, error) {
str, ok := value.(string)
if !ok {
return "", fmt.Errorf("invalid type %T (want string)", value)
}
return str, nil
}
func setStringSlice(dest *[]string, value any) error {
slice, err := asStringSlice(value)
if err != nil {
return err
}
*dest = slice
return nil
}
func asStringSlice(value any) ([]string, error) {
array, ok := value.([]any)
if !ok {
return nil, fmt.Errorf("invalid type %T (want JSON array of string)", value)
}
var slice []string
for _, elem := range array {
str, ok := elem.(string)
if !ok {
return nil, fmt.Errorf("invalid array element type %T (want string)", elem)
}
slice = append(slice, str)
}
return slice, nil
}
func setEnum[S ~string](dest *S, value any, options ...S) error {
enum, err := asEnum(value, options...)
if err != nil {
return err
}
*dest = enum
return nil
}
func asEnum[S ~string](value any, options ...S) (S, error) {
str, err := asString(value)
if err != nil {
return "", err
}
for _, opt := range options {
if strings.EqualFold(str, string(opt)) {
return opt, nil
}
}
return "", fmt.Errorf("invalid option %q for enum", str)
}