cuelang.org/go@v0.13.0/internal/golangorgx/gopls/settings/settings.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package settings
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strings"
    14  	"time"
    15  
    16  	"cuelang.org/go/internal/golangorgx/gopls/file"
    17  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    18  	"golang.org/x/tools/go/analysis"
    19  )
    20  
    21  type Annotation string
    22  
    23  const (
    24  	// Nil controls nil checks.
    25  	Nil Annotation = "nil"
    26  
    27  	// Escape controls diagnostics about escape choices.
    28  	Escape Annotation = "escape"
    29  
    30  	// Inline controls diagnostics about inlining choices.
    31  	Inline Annotation = "inline"
    32  
    33  	// Bounds controls bounds checking diagnostics.
    34  	Bounds Annotation = "bounds"
    35  )
    36  
    37  // Options holds various configuration that affects Gopls execution, organized
    38  // by the nature or origin of the settings.
    39  type Options struct {
    40  	ClientOptions
    41  	ServerOptions
    42  	UserOptions
    43  	InternalOptions
    44  	Hooks
    45  }
    46  
    47  // IsAnalyzerEnabled reports whether an analyzer with the given name is
    48  // enabled.
    49  //
    50  // TODO(rfindley): refactor to simplify this function. We no longer need the
    51  // different categories of analyzer.
    52  func (opts *Options) IsAnalyzerEnabled(name string) bool {
    53  	for _, amap := range []map[string]*Analyzer{opts.DefaultAnalyzers, opts.StaticcheckAnalyzers} {
    54  		for _, analyzer := range amap {
    55  			if analyzer.Analyzer.Name == name && analyzer.IsEnabled(opts) {
    56  				return true
    57  			}
    58  		}
    59  	}
    60  	return false
    61  }
    62  
    63  // ClientOptions holds LSP-specific configuration that is provided by the
    64  // client.
    65  type ClientOptions struct {
    66  	ClientInfo                                 *protocol.ClientInfo
    67  	InsertTextFormat                           protocol.InsertTextFormat
    68  	ConfigurationSupported                     bool
    69  	DynamicConfigurationSupported              bool
    70  	DynamicRegistrationSemanticTokensSupported bool
    71  	DynamicWatchedFilesSupported               bool
    72  	RelativePatternsSupported                  bool
    73  	PreferredContentFormat                     protocol.MarkupKind
    74  	LineFoldingOnly                            bool
    75  	HierarchicalDocumentSymbolSupport          bool
    76  	SemanticTypes                              []string
    77  	SemanticMods                               []string
    78  	RelatedInformationSupported                bool
    79  	CompletionTags                             bool
    80  	CompletionDeprecated                       bool
    81  	SupportedResourceOperations                []protocol.ResourceOperationKind
    82  	CodeActionResolveOptions                   []string
    83  }
    84  
    85  // ServerOptions holds LSP-specific configuration that is provided by the
    86  // server.
    87  type ServerOptions struct {
    88  	SupportedCodeActions map[file.Kind]map[protocol.CodeActionKind]bool
    89  	SupportedCommands    []string
    90  }
    91  
    92  type BuildOptions struct {
    93  	// BuildFlags is the set of flags passed on to the build system when invoked.
    94  	// It is applied to queries like `go list`, which is used when discovering files.
    95  	// The most common use is to set `-tags`.
    96  	BuildFlags []string
    97  
    98  	// Env adds environment variables to external commands run by `gopls`, most notably `go list`.
    99  	Env map[string]string
   100  
   101  	// DirectoryFilters can be used to exclude unwanted directories from the
   102  	// workspace. By default, all directories are included. Filters are an
   103  	// operator, `+` to include and `-` to exclude, followed by a path prefix
   104  	// relative to the workspace folder. They are evaluated in order, and
   105  	// the last filter that applies to a path controls whether it is included.
   106  	// The path prefix can be empty, so an initial `-` excludes everything.
   107  	//
   108  	// DirectoryFilters also supports the `**` operator to match 0 or more directories.
   109  	//
   110  	// Examples:
   111  	//
   112  	// Exclude node_modules at current depth: `-node_modules`
   113  	//
   114  	// Exclude node_modules at any depth: `-**/node_modules`
   115  	//
   116  	// Include only project_a: `-` (exclude everything), `+project_a`
   117  	//
   118  	// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
   119  	DirectoryFilters []string
   120  
   121  	// ExpandWorkspaceToModule determines which packages are considered
   122  	// "workspace packages" when the workspace is using modules.
   123  	//
   124  	// Workspace packages affect the scope of workspace-wide operations. Notably,
   125  	// gopls diagnoses all packages considered to be part of the workspace after
   126  	// every keystroke, so by setting "ExpandWorkspaceToModule" to false, and
   127  	// opening a nested workspace directory, you can reduce the amount of work
   128  	// gopls has to do to keep your workspace up to date.
   129  	ExpandWorkspaceToModule bool `status:"experimental"`
   130  
   131  	// AllowModfileModifications disables -mod=readonly, allowing imports from
   132  	// out-of-scope modules. This option will eventually be removed.
   133  	AllowModfileModifications bool `status:"experimental"`
   134  
   135  	// AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module
   136  	// downloads rather than requiring user action. This option will eventually
   137  	// be removed.
   138  	AllowImplicitNetworkAccess bool `status:"experimental"`
   139  
   140  	// StandaloneTags specifies a set of build constraints that identify
   141  	// individual Go source files that make up the entire main package of an
   142  	// executable.
   143  	//
   144  	// A common example of standalone main files is the convention of using the
   145  	// directive `//go:build ignore` to denote files that are not intended to be
   146  	// included in any package, for example because they are invoked directly by
   147  	// the developer using `go run`.
   148  	//
   149  	// Gopls considers a file to be a standalone main file if and only if it has
   150  	// package name "main" and has a build directive of the exact form
   151  	// "//go:build tag" or "// +build tag", where tag is among the list of tags
   152  	// configured by this setting. Notably, if the build constraint is more
   153  	// complicated than a simple tag (such as the composite constraint
   154  	// `//go:build tag && go1.18`), the file is not considered to be a standalone
   155  	// main file.
   156  	//
   157  	// This setting is only supported when gopls is built with Go 1.16 or later.
   158  	StandaloneTags []string
   159  }
   160  
   161  type UIOptions struct {
   162  	DocumentationOptions
   163  	CompletionOptions
   164  	NavigationOptions
   165  	DiagnosticOptions
   166  	InlayHintOptions
   167  
   168  	// Codelenses overrides the enabled/disabled state of code lenses. See the
   169  	// "Code Lenses" section of the
   170  	// [Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)
   171  	// for the list of supported lenses.
   172  	//
   173  	// Example Usage:
   174  	//
   175  	// ```json5
   176  	// "gopls": {
   177  	// ...
   178  	//   "codelenses": {
   179  	//     "generate": false,  // Don't show the `go generate` lens.
   180  	//     "gc_details": true  // Show a code lens toggling the display of gc's choices.
   181  	//   }
   182  	// ...
   183  	// }
   184  	// ```
   185  	Codelenses map[string]bool
   186  
   187  	// SemanticTokens controls whether the LSP server will send
   188  	// semantic tokens to the client.
   189  	SemanticTokens bool `status:"experimental"`
   190  
   191  	// NoSemanticString turns off the sending of the semantic token 'string'
   192  	NoSemanticString bool `status:"experimental"`
   193  
   194  	// NoSemanticNumber  turns off the sending of the semantic token 'number'
   195  	NoSemanticNumber bool `status:"experimental"`
   196  }
   197  
   198  type CompletionOptions struct {
   199  	// Placeholders enables placeholders for function parameters or struct
   200  	// fields in completion responses.
   201  	UsePlaceholders bool
   202  
   203  	// CompletionBudget is the soft latency goal for completion requests. Most
   204  	// requests finish in a couple milliseconds, but in some cases deep
   205  	// completions can take much longer. As we use up our budget we
   206  	// dynamically reduce the search scope to ensure we return timely
   207  	// results. Zero means unlimited.
   208  	CompletionBudget time.Duration `status:"debug"`
   209  
   210  	// Matcher sets the algorithm that is used when calculating completion
   211  	// candidates.
   212  	Matcher Matcher `status:"advanced"`
   213  
   214  	// ExperimentalPostfixCompletions enables artificial method snippets
   215  	// such as "someSlice.sort!".
   216  	ExperimentalPostfixCompletions bool `status:"experimental"`
   217  
   218  	// CompleteFunctionCalls enables function call completion.
   219  	//
   220  	// When completing a statement, or when a function return type matches the
   221  	// expected of the expression being completed, completion may suggest call
   222  	// expressions (i.e. may include parentheses).
   223  	CompleteFunctionCalls bool
   224  }
   225  
   226  type DocumentationOptions struct {
   227  	// HoverKind controls the information that appears in the hover text.
   228  	// SingleLine and Structured are intended for use only by authors of editor plugins.
   229  	HoverKind HoverKind
   230  
   231  	// LinkTarget controls where documentation links go.
   232  	// It might be one of:
   233  	//
   234  	// * `"godoc.org"`
   235  	// * `"pkg.go.dev"`
   236  	//
   237  	// If company chooses to use its own `godoc.org`, its address can be used as well.
   238  	//
   239  	// Modules matching the GOPRIVATE environment variable will not have
   240  	// documentation links in hover.
   241  	LinkTarget string
   242  
   243  	// LinksInHover toggles the presence of links to documentation in hover.
   244  	LinksInHover bool
   245  }
   246  
   247  type FormattingOptions struct {
   248  	// Local is the equivalent of the `goimports -local` flag, which puts
   249  	// imports beginning with this string after third-party packages. It should
   250  	// be the prefix of the import path whose imports should be grouped
   251  	// separately.
   252  	Local string
   253  
   254  	// Gofumpt indicates if we should run gofumpt formatting.
   255  	Gofumpt bool
   256  }
   257  
   258  type DiagnosticOptions struct {
   259  	// Analyses specify analyses that the user would like to enable or disable.
   260  	// A map of the names of analysis passes that should be enabled/disabled.
   261  	// A full list of analyzers that gopls uses can be found in
   262  	// [analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
   263  	//
   264  	// Example Usage:
   265  	//
   266  	// ```json5
   267  	// ...
   268  	// "analyses": {
   269  	//   "unreachable": false, // Disable the unreachable analyzer.
   270  	//   "unusedvariable": true  // Enable the unusedvariable analyzer.
   271  	// }
   272  	// ...
   273  	// ```
   274  	Analyses map[string]bool
   275  
   276  	// Staticcheck enables additional analyses from staticcheck.io.
   277  	// These analyses are documented on
   278  	// [Staticcheck's website](https://staticcheck.io/docs/checks/).
   279  	Staticcheck bool `status:"experimental"`
   280  
   281  	// Annotations specifies the various kinds of optimization diagnostics
   282  	// that should be reported by the gc_details command.
   283  	Annotations map[Annotation]bool `status:"experimental"`
   284  
   285  	// DiagnosticsDelay controls the amount of time that gopls waits
   286  	// after the most recent file modification before computing deep diagnostics.
   287  	// Simple diagnostics (parsing and type-checking) are always run immediately
   288  	// on recently modified packages.
   289  	//
   290  	// This option must be set to a valid duration string, for example `"250ms"`.
   291  	DiagnosticsDelay time.Duration `status:"advanced"`
   292  
   293  	// DiagnosticsTrigger controls when to run diagnostics.
   294  	DiagnosticsTrigger DiagnosticsTrigger `status:"experimental"`
   295  
   296  	// AnalysisProgressReporting controls whether gopls sends progress
   297  	// notifications when construction of its index of analysis facts is taking a
   298  	// long time. Cancelling these notifications will cancel the indexing task,
   299  	// though it will restart after the next change in the workspace.
   300  	//
   301  	// When a package is opened for the first time and heavyweight analyses such as
   302  	// staticcheck are enabled, it can take a while to construct the index of
   303  	// analysis facts for all its dependencies. The index is cached in the
   304  	// filesystem, so subsequent analysis should be faster.
   305  	AnalysisProgressReporting bool
   306  }
   307  
   308  type InlayHintOptions struct {
   309  	// Hints specify inlay hints that users want to see. A full list of hints
   310  	// that gopls uses can be found in
   311  	// [inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
   312  	Hints map[string]bool `status:"experimental"`
   313  }
   314  
   315  type NavigationOptions struct {
   316  	// ImportShortcut specifies whether import statements should link to
   317  	// documentation or go to definitions.
   318  	ImportShortcut ImportShortcut
   319  
   320  	// SymbolMatcher sets the algorithm that is used when finding workspace symbols.
   321  	SymbolMatcher SymbolMatcher `status:"advanced"`
   322  
   323  	// SymbolStyle controls how symbols are qualified in symbol responses.
   324  	//
   325  	// Example Usage:
   326  	//
   327  	// ```json5
   328  	// "gopls": {
   329  	// ...
   330  	//   "symbolStyle": "Dynamic",
   331  	// ...
   332  	// }
   333  	// ```
   334  	SymbolStyle SymbolStyle `status:"advanced"`
   335  
   336  	// SymbolScope controls which packages are searched for workspace/symbol
   337  	// requests. The default value, "workspace", searches only workspace
   338  	// packages. The legacy behavior, "all", causes all loaded packages to be
   339  	// searched, including dependencies; this is more expensive and may return
   340  	// unwanted results.
   341  	SymbolScope SymbolScope
   342  }
   343  
   344  // UserOptions holds custom Gopls configuration (not part of the LSP) that is
   345  // modified by the client.
   346  type UserOptions struct {
   347  	BuildOptions
   348  	UIOptions
   349  	FormattingOptions
   350  
   351  	// VerboseOutput enables additional debug logging.
   352  	VerboseOutput bool `status:"debug"`
   353  }
   354  
   355  // EnvSlice returns Env as a slice of k=v strings.
   356  func (u *UserOptions) EnvSlice() []string {
   357  	var result []string
   358  	for k, v := range u.Env {
   359  		result = append(result, fmt.Sprintf("%v=%v", k, v))
   360  	}
   361  	return result
   362  }
   363  
   364  // SetEnvSlice sets Env from a slice of k=v strings.
   365  func (u *UserOptions) SetEnvSlice(env []string) {
   366  	u.Env = map[string]string{}
   367  	for _, kv := range env {
   368  		split := strings.SplitN(kv, "=", 2)
   369  		if len(split) != 2 {
   370  			continue
   371  		}
   372  		u.Env[split[0]] = split[1]
   373  	}
   374  }
   375  
   376  // Hooks contains configuration that is provided to the Gopls command by the
   377  // main package.
   378  type Hooks struct {
   379  	// LicensesText holds third party licenses for software used by gopls.
   380  	LicensesText string
   381  
   382  	// Whether staticcheck is supported.
   383  	StaticcheckSupported bool
   384  
   385  	// URLRegexp is used to find potential URLs in comments/strings.
   386  	//
   387  	// Not all matches are shown to the user: if the matched URL is not detected
   388  	// as valid, it will be skipped.
   389  	URLRegexp *regexp.Regexp
   390  
   391  	// GofumptFormat allows the gopls module to wire-in a call to
   392  	// gofumpt/format.Source. langVersion and modulePath are used for some
   393  	// Gofumpt formatting rules -- see the Gofumpt documentation for details.
   394  	GofumptFormat func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error)
   395  
   396  	DefaultAnalyzers     map[string]*Analyzer
   397  	StaticcheckAnalyzers map[string]*Analyzer
   398  }
   399  
   400  // InternalOptions contains settings that are not intended for use by the
   401  // average user. These may be settings used by tests or outdated settings that
   402  // will soon be deprecated. Some of these settings may not even be configurable
   403  // by the user.
   404  //
   405  // TODO(rfindley): even though these settings are not intended for
   406  // modification, some of them should be surfaced in our documentation.
   407  type InternalOptions struct {
   408  	// VerboseWorkDoneProgress controls whether the LSP server should send
   409  	// progress reports for all work done outside the scope of an RPC.
   410  	// Used by the regression tests.
   411  	VerboseWorkDoneProgress bool
   412  
   413  	// The following options were previously available to users, but they
   414  	// really shouldn't be configured by anyone other than "power users".
   415  
   416  	// CompletionDocumentation enables documentation with completion results.
   417  	CompletionDocumentation bool
   418  
   419  	// CompleteUnimported enables completion for packages that you do not
   420  	// currently import.
   421  	CompleteUnimported bool
   422  
   423  	// DeepCompletion enables the ability to return completions from deep
   424  	// inside relevant entities, rather than just the locally accessible ones.
   425  	//
   426  	// Consider this example:
   427  	//
   428  	// ```go
   429  	// package main
   430  	//
   431  	// import "fmt"
   432  	//
   433  	// type wrapString struct {
   434  	//     str string
   435  	// }
   436  	//
   437  	// func main() {
   438  	//     x := wrapString{"hello world"}
   439  	//     fmt.Printf(<>)
   440  	// }
   441  	// ```
   442  	//
   443  	// At the location of the `<>` in this program, deep completion would suggest
   444  	// the result `x.str`.
   445  	DeepCompletion bool
   446  
   447  	// ShowBugReports causes a message to be shown when the first bug is reported
   448  	// on the server.
   449  	// This option applies only during initialization.
   450  	ShowBugReports bool
   451  
   452  	// SubdirWatchPatterns configures the file watching glob patterns registered
   453  	// by gopls.
   454  	//
   455  	// Some clients (namely VS Code) do not send workspace/didChangeWatchedFile
   456  	// notifications for files contained in a directory when that directory is
   457  	// deleted:
   458  	// https://github.com/microsoft/vscode/issues/109754
   459  	//
   460  	// In this case, gopls would miss important notifications about deleted
   461  	// packages. To work around this, gopls registers a watch pattern for each
   462  	// directory containing Go files.
   463  	//
   464  	// Unfortunately, other clients experience performance problems with this
   465  	// many watch patterns, so there is no single behavior that works well for
   466  	// all clients.
   467  	//
   468  	// The "subdirWatchPatterns" setting allows configuring this behavior. Its
   469  	// default value of "auto" attempts to guess the correct behavior based on
   470  	// the client name. We'd love to avoid this specialization, but as described
   471  	// above there is no single value that works for all clients.
   472  	//
   473  	// If any LSP client does not behave well with the default value (for
   474  	// example, if like VS Code it drops file notifications), please file an
   475  	// issue.
   476  	SubdirWatchPatterns SubdirWatchPatterns
   477  
   478  	// ReportAnalysisProgressAfter sets the duration for gopls to wait before starting
   479  	// progress reporting for ongoing go/analysis passes.
   480  	//
   481  	// It is intended to be used for testing only.
   482  	ReportAnalysisProgressAfter time.Duration
   483  
   484  	// TelemetryPrompt controls whether gopls prompts about enabling Go telemetry.
   485  	//
   486  	// Once the prompt is answered, gopls doesn't ask again, but TelemetryPrompt
   487  	// can prevent the question from ever being asked in the first place.
   488  	TelemetryPrompt bool
   489  
   490  	// LinkifyShowMessage controls whether the client wants gopls
   491  	// to linkify links in showMessage. e.g. [go.dev](https://go.dev).
   492  	LinkifyShowMessage bool
   493  
   494  	// IncludeReplaceInWorkspace controls whether locally replaced modules in a
   495  	// go.mod file are treated like workspace modules.
   496  	// Or in other words, if a go.mod file with local replaces behaves like a
   497  	// go.work file.
   498  	IncludeReplaceInWorkspace bool
   499  
   500  	// ZeroConfig enables the zero-config algorithm for workspace layout,
   501  	// dynamically creating build configurations for different modules,
   502  	// directories, and GOOS/GOARCH combinations to cover open files.
   503  	ZeroConfig bool
   504  }
   505  
   506  type SubdirWatchPatterns string
   507  
   508  const (
   509  	SubdirWatchPatternsOn   SubdirWatchPatterns = "on"
   510  	SubdirWatchPatternsOff  SubdirWatchPatterns = "off"
   511  	SubdirWatchPatternsAuto SubdirWatchPatterns = "auto"
   512  )
   513  
   514  type ImportShortcut string
   515  
   516  const (
   517  	BothShortcuts      ImportShortcut = "Both"
   518  	LinkShortcut       ImportShortcut = "Link"
   519  	DefinitionShortcut ImportShortcut = "Definition"
   520  )
   521  
   522  func (s ImportShortcut) ShowLinks() bool {
   523  	return s == BothShortcuts || s == LinkShortcut
   524  }
   525  
   526  func (s ImportShortcut) ShowDefinition() bool {
   527  	return s == BothShortcuts || s == DefinitionShortcut
   528  }
   529  
   530  type Matcher string
   531  
   532  const (
   533  	Fuzzy           Matcher = "Fuzzy"
   534  	CaseInsensitive Matcher = "CaseInsensitive"
   535  	CaseSensitive   Matcher = "CaseSensitive"
   536  )
   537  
   538  // A SymbolMatcher controls the matching of symbols for workspace/symbol
   539  // requests.
   540  type SymbolMatcher string
   541  
   542  const (
   543  	SymbolFuzzy           SymbolMatcher = "Fuzzy"
   544  	SymbolFastFuzzy       SymbolMatcher = "FastFuzzy"
   545  	SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
   546  	SymbolCaseSensitive   SymbolMatcher = "CaseSensitive"
   547  )
   548  
   549  // A SymbolStyle controls the formatting of symbols in workspace/symbol results.
   550  type SymbolStyle string
   551  
   552  const (
   553  	// PackageQualifiedSymbols is package qualified symbols i.e.
   554  	// "pkg.Foo.Field".
   555  	PackageQualifiedSymbols SymbolStyle = "Package"
   556  	// FullyQualifiedSymbols is fully qualified symbols, i.e.
   557  	// "path/to/pkg.Foo.Field".
   558  	FullyQualifiedSymbols SymbolStyle = "Full"
   559  	// DynamicSymbols uses whichever qualifier results in the highest scoring
   560  	// match for the given symbol query. Here a "qualifier" is any "/" or "."
   561  	// delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
   562  	// just "Foo.Field".
   563  	DynamicSymbols SymbolStyle = "Dynamic"
   564  )
   565  
   566  // A SymbolScope controls the search scope for workspace/symbol requests.
   567  type SymbolScope string
   568  
   569  const (
   570  	// WorkspaceSymbolScope matches symbols in workspace packages only.
   571  	WorkspaceSymbolScope SymbolScope = "workspace"
   572  	// AllSymbolScope matches symbols in any loaded package, including
   573  	// dependencies.
   574  	AllSymbolScope SymbolScope = "all"
   575  )
   576  
   577  type HoverKind string
   578  
   579  const (
   580  	SingleLine            HoverKind = "SingleLine"
   581  	NoDocumentation       HoverKind = "NoDocumentation"
   582  	SynopsisDocumentation HoverKind = "SynopsisDocumentation"
   583  	FullDocumentation     HoverKind = "FullDocumentation"
   584  
   585  	// Structured is an experimental setting that returns a structured hover format.
   586  	// This format separates the signature from the documentation, so that the client
   587  	// can do more manipulation of these fields.
   588  	//
   589  	// This should only be used by clients that support this behavior.
   590  	Structured HoverKind = "Structured"
   591  )
   592  
   593  type DiagnosticsTrigger string
   594  
   595  const (
   596  	// Trigger diagnostics on file edit and save. (default)
   597  	DiagnosticsOnEdit DiagnosticsTrigger = "Edit"
   598  	// Trigger diagnostics only on file save. Events like initial workspace load
   599  	// or configuration change will still trigger diagnostics.
   600  	DiagnosticsOnSave DiagnosticsTrigger = "Save"
   601  	// TODO: support "Manual"?
   602  )
   603  
   604  type OptionResults []OptionResult
   605  
   606  type OptionResult struct {
   607  	Name  string
   608  	Value any
   609  	Error error
   610  }
   611  
   612  func SetOptions(options *Options, opts any) OptionResults {
   613  	var results OptionResults
   614  	switch opts := opts.(type) {
   615  	case nil:
   616  	case map[string]any:
   617  		// If the user's settings contains "allExperiments", set that first,
   618  		// and then let them override individual settings independently.
   619  		var enableExperiments bool
   620  		for name, value := range opts {
   621  			if b, ok := value.(bool); name == "allExperiments" && ok && b {
   622  				enableExperiments = true
   623  				options.EnableAllExperiments()
   624  			}
   625  		}
   626  		seen := map[string]struct{}{}
   627  		for name, value := range opts {
   628  			results = append(results, options.set(name, value, seen))
   629  		}
   630  		// Finally, enable any experimental features that are specified in
   631  		// maps, which allows users to individually toggle them on or off.
   632  		if enableExperiments {
   633  			options.enableAllExperimentMaps()
   634  		}
   635  	default:
   636  		results = append(results, OptionResult{
   637  			Value: opts,
   638  			Error: fmt.Errorf("Invalid options type %T", opts),
   639  		})
   640  	}
   641  	return results
   642  }
   643  
   644  func (o *Options) ForClientCapabilities(clientName *protocol.ClientInfo, caps protocol.ClientCapabilities) {
   645  	o.ClientInfo = clientName
   646  	// Check if the client supports snippets in completion items.
   647  	if caps.Workspace.WorkspaceEdit != nil {
   648  		o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations
   649  	}
   650  	if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
   651  		o.InsertTextFormat = protocol.SnippetTextFormat
   652  	}
   653  	// Check if the client supports configuration messages.
   654  	o.ConfigurationSupported = caps.Workspace.Configuration
   655  	o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
   656  	o.DynamicRegistrationSemanticTokensSupported = caps.TextDocument.SemanticTokens.DynamicRegistration
   657  	o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
   658  	o.RelativePatternsSupported = caps.Workspace.DidChangeWatchedFiles.RelativePatternSupport
   659  
   660  	// Check which types of content format are supported by this client.
   661  	if hover := caps.TextDocument.Hover; hover != nil && len(hover.ContentFormat) > 0 {
   662  		o.PreferredContentFormat = hover.ContentFormat[0]
   663  	}
   664  	// Check if the client supports only line folding.
   665  
   666  	if fr := caps.TextDocument.FoldingRange; fr != nil {
   667  		o.LineFoldingOnly = fr.LineFoldingOnly
   668  	}
   669  	// Check if the client supports hierarchical document symbols.
   670  	o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
   671  
   672  	// Client's semantic tokens
   673  	o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes
   674  	o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers
   675  	// we don't need Requests, as we support full functionality
   676  	// we don't need Formats, as there is only one, for now
   677  
   678  	// Check if the client supports diagnostic related information.
   679  	o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation
   680  	// Check if the client completion support includes tags (preferred) or deprecation
   681  	if caps.TextDocument.Completion.CompletionItem.TagSupport != nil &&
   682  		caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil {
   683  		o.CompletionTags = true
   684  	} else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport {
   685  		o.CompletionDeprecated = true
   686  	}
   687  
   688  	// Check if the client supports code actions resolving.
   689  	if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil {
   690  		o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties
   691  	}
   692  }
   693  
   694  func (o *Options) Clone() *Options {
   695  	// TODO(rfindley): has this function gone stale? It appears that there are
   696  	// settings that are incorrectly cloned here (?).
   697  	result := &Options{
   698  		ClientOptions:   o.ClientOptions,
   699  		InternalOptions: o.InternalOptions,
   700  		Hooks: Hooks{
   701  			StaticcheckSupported: o.StaticcheckSupported,
   702  			GofumptFormat:        o.GofumptFormat,
   703  			URLRegexp:            o.URLRegexp,
   704  		},
   705  		ServerOptions: o.ServerOptions,
   706  		UserOptions:   o.UserOptions,
   707  	}
   708  	// Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
   709  	// and UserOptions can be modified.
   710  	copyStringMap := func(src map[string]bool) map[string]bool {
   711  		dst := make(map[string]bool)
   712  		for k, v := range src {
   713  			dst[k] = v
   714  		}
   715  		return dst
   716  	}
   717  	result.Analyses = copyStringMap(o.Analyses)
   718  	result.Codelenses = copyStringMap(o.Codelenses)
   719  
   720  	copySlice := func(src []string) []string {
   721  		dst := make([]string, len(src))
   722  		copy(dst, src)
   723  		return dst
   724  	}
   725  	result.SetEnvSlice(o.EnvSlice())
   726  	result.BuildFlags = copySlice(o.BuildFlags)
   727  	result.DirectoryFilters = copySlice(o.DirectoryFilters)
   728  	result.StandaloneTags = copySlice(o.StandaloneTags)
   729  
   730  	copyAnalyzerMap := func(src map[string]*Analyzer) map[string]*Analyzer {
   731  		dst := make(map[string]*Analyzer)
   732  		for k, v := range src {
   733  			dst[k] = v
   734  		}
   735  		return dst
   736  	}
   737  	result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
   738  	result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
   739  	return result
   740  }
   741  
   742  func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer, enabled bool, severity protocol.DiagnosticSeverity) {
   743  	o.StaticcheckAnalyzers[a.Name] = &Analyzer{
   744  		Analyzer: a,
   745  		Enabled:  enabled,
   746  		Severity: severity,
   747  	}
   748  }
   749  
   750  // EnableAllExperiments turns on all of the experimental "off-by-default"
   751  // features offered by gopls. Any experimental features specified in maps
   752  // should be enabled in enableAllExperimentMaps.
   753  func (o *Options) EnableAllExperiments() {
   754  	o.SemanticTokens = true
   755  }
   756  
   757  func (o *Options) enableAllExperimentMaps() {
   758  }
   759  
   760  // validateDirectoryFilter validates if the filter string
   761  // - is not empty
   762  // - start with either + or -
   763  // - doesn't contain currently unsupported glob operators: *, ?
   764  func validateDirectoryFilter(ifilter string) (string, error) {
   765  	filter := fmt.Sprint(ifilter)
   766  	if filter == "" || (filter[0] != '+' && filter[0] != '-') {
   767  		return "", fmt.Errorf("invalid filter %v, must start with + or -", filter)
   768  	}
   769  	segs := strings.Split(filter[1:], "/")
   770  	unsupportedOps := [...]string{"?", "*"}
   771  	for _, seg := range segs {
   772  		if seg != "**" {
   773  			for _, op := range unsupportedOps {
   774  				if strings.Contains(seg, op) {
   775  					return "", fmt.Errorf("invalid filter %v, operator %v not supported. If you want to have this operator supported, consider filing an issue.", filter, op)
   776  				}
   777  			}
   778  		}
   779  	}
   780  
   781  	return strings.TrimRight(filepath.FromSlash(filter), "/"), nil
   782  }
   783  
   784  func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
   785  	// Flatten the name in case we get options with a hierarchy.
   786  	split := strings.Split(name, ".")
   787  	name = split[len(split)-1]
   788  
   789  	result := OptionResult{Name: name, Value: value}
   790  	if _, ok := seen[name]; ok {
   791  		result.parseErrorf("duplicate configuration for %s", name)
   792  	}
   793  	seen[name] = struct{}{}
   794  
   795  	switch name {
   796  	case "env":
   797  		menv, ok := value.(map[string]interface{})
   798  		if !ok {
   799  			result.parseErrorf("invalid type %T, expect map", value)
   800  			break
   801  		}
   802  		if o.Env == nil {
   803  			o.Env = make(map[string]string)
   804  		}
   805  		for k, v := range menv {
   806  			o.Env[k] = fmt.Sprint(v)
   807  		}
   808  
   809  	case "buildFlags":
   810  		// TODO(rfindley): use asStringSlice.
   811  		iflags, ok := value.([]interface{})
   812  		if !ok {
   813  			result.parseErrorf("invalid type %T, expect list", value)
   814  			break
   815  		}
   816  		flags := make([]string, 0, len(iflags))
   817  		for _, flag := range iflags {
   818  			flags = append(flags, fmt.Sprintf("%s", flag))
   819  		}
   820  		o.BuildFlags = flags
   821  
   822  	case "directoryFilters":
   823  		// TODO(rfindley): use asStringSlice.
   824  		ifilters, ok := value.([]interface{})
   825  		if !ok {
   826  			result.parseErrorf("invalid type %T, expect list", value)
   827  			break
   828  		}
   829  		var filters []string
   830  		for _, ifilter := range ifilters {
   831  			filter, err := validateDirectoryFilter(fmt.Sprintf("%v", ifilter))
   832  			if err != nil {
   833  				result.parseErrorf("%v", err)
   834  				return result
   835  			}
   836  			filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
   837  		}
   838  		o.DirectoryFilters = filters
   839  
   840  	case "completionDocumentation":
   841  		result.setBool(&o.CompletionDocumentation)
   842  	case "usePlaceholders":
   843  		result.setBool(&o.UsePlaceholders)
   844  	case "deepCompletion":
   845  		result.setBool(&o.DeepCompletion)
   846  	case "completeUnimported":
   847  		result.setBool(&o.CompleteUnimported)
   848  	case "completionBudget":
   849  		result.setDuration(&o.CompletionBudget)
   850  	case "matcher":
   851  		if s, ok := result.asOneOf(
   852  			string(Fuzzy),
   853  			string(CaseSensitive),
   854  			string(CaseInsensitive),
   855  		); ok {
   856  			o.Matcher = Matcher(s)
   857  		}
   858  
   859  	case "symbolMatcher":
   860  		if s, ok := result.asOneOf(
   861  			string(SymbolFuzzy),
   862  			string(SymbolFastFuzzy),
   863  			string(SymbolCaseInsensitive),
   864  			string(SymbolCaseSensitive),
   865  		); ok {
   866  			o.SymbolMatcher = SymbolMatcher(s)
   867  		}
   868  
   869  	case "symbolStyle":
   870  		if s, ok := result.asOneOf(
   871  			string(FullyQualifiedSymbols),
   872  			string(PackageQualifiedSymbols),
   873  			string(DynamicSymbols),
   874  		); ok {
   875  			o.SymbolStyle = SymbolStyle(s)
   876  		}
   877  
   878  	case "symbolScope":
   879  		if s, ok := result.asOneOf(
   880  			string(WorkspaceSymbolScope),
   881  			string(AllSymbolScope),
   882  		); ok {
   883  			o.SymbolScope = SymbolScope(s)
   884  		}
   885  
   886  	case "hoverKind":
   887  		if s, ok := result.asOneOf(
   888  			string(NoDocumentation),
   889  			string(SingleLine),
   890  			string(SynopsisDocumentation),
   891  			string(FullDocumentation),
   892  			string(Structured),
   893  		); ok {
   894  			o.HoverKind = HoverKind(s)
   895  		}
   896  
   897  	case "linkTarget":
   898  		result.setString(&o.LinkTarget)
   899  
   900  	case "linksInHover":
   901  		result.setBool(&o.LinksInHover)
   902  
   903  	case "importShortcut":
   904  		if s, ok := result.asOneOf(string(BothShortcuts), string(LinkShortcut), string(DefinitionShortcut)); ok {
   905  			o.ImportShortcut = ImportShortcut(s)
   906  		}
   907  
   908  	case "analyses":
   909  		result.setBoolMap(&o.Analyses)
   910  
   911  	case "hints":
   912  		result.setBoolMap(&o.Hints)
   913  
   914  	case "annotations":
   915  		result.setAnnotationMap(&o.Annotations)
   916  
   917  	case "codelenses", "codelens":
   918  		var lensOverrides map[string]bool
   919  		result.setBoolMap(&lensOverrides)
   920  		if result.Error == nil {
   921  			if o.Codelenses == nil {
   922  				o.Codelenses = make(map[string]bool)
   923  			}
   924  			for lens, enabled := range lensOverrides {
   925  				o.Codelenses[lens] = enabled
   926  			}
   927  		}
   928  
   929  	case "staticcheck":
   930  		if v, ok := result.asBool(); ok {
   931  			o.Staticcheck = v
   932  			if v && !o.StaticcheckSupported {
   933  				result.Error = fmt.Errorf("applying setting %q: staticcheck is not supported at %s;"+
   934  					" rebuild gopls with a more recent version of Go", result.Name, runtime.Version())
   935  			}
   936  		}
   937  
   938  	case "local":
   939  		result.setString(&o.Local)
   940  
   941  	case "verboseOutput":
   942  		result.setBool(&o.VerboseOutput)
   943  
   944  	case "verboseWorkDoneProgress":
   945  		result.setBool(&o.VerboseWorkDoneProgress)
   946  
   947  	case "showBugReports":
   948  		result.setBool(&o.ShowBugReports)
   949  
   950  	case "gofumpt":
   951  		if v, ok := result.asBool(); ok {
   952  			o.Gofumpt = v
   953  			if v && o.GofumptFormat == nil {
   954  				result.Error = fmt.Errorf("applying setting %q: gofumpt is not supported at %s;"+
   955  					" rebuild gopls with a more recent version of Go", result.Name, runtime.Version())
   956  			}
   957  		}
   958  	case "completeFunctionCalls":
   959  		result.setBool(&o.CompleteFunctionCalls)
   960  
   961  	case "semanticTokens":
   962  		result.setBool(&o.SemanticTokens)
   963  
   964  	case "noSemanticString":
   965  		result.setBool(&o.NoSemanticString)
   966  
   967  	case "noSemanticNumber":
   968  		result.setBool(&o.NoSemanticNumber)
   969  
   970  	case "expandWorkspaceToModule":
   971  		// See golang/go#63536: we can consider deprecating
   972  		// expandWorkspaceToModule, but probably need to change the default
   973  		// behavior in that case to *not* expand to the module.
   974  		result.setBool(&o.ExpandWorkspaceToModule)
   975  
   976  	case "experimentalPostfixCompletions":
   977  		result.setBool(&o.ExperimentalPostfixCompletions)
   978  
   979  	case "diagnosticsDelay":
   980  		result.setDuration(&o.DiagnosticsDelay)
   981  
   982  	case "diagnosticsTrigger":
   983  		if s, ok := result.asOneOf(
   984  			string(DiagnosticsOnEdit),
   985  			string(DiagnosticsOnSave),
   986  		); ok {
   987  			o.DiagnosticsTrigger = DiagnosticsTrigger(s)
   988  		}
   989  
   990  	case "analysisProgressReporting":
   991  		result.setBool(&o.AnalysisProgressReporting)
   992  
   993  	case "allowModfileModifications":
   994  		result.softErrorf("gopls setting \"allowModfileModifications\" is deprecated.\nPlease comment on https://go.dev/issue/65546 if this impacts your workflow.")
   995  		result.setBool(&o.AllowModfileModifications)
   996  
   997  	case "allowImplicitNetworkAccess":
   998  		result.setBool(&o.AllowImplicitNetworkAccess)
   999  
  1000  	case "standaloneTags":
  1001  		result.setStringSlice(&o.StandaloneTags)
  1002  
  1003  	case "allExperiments":
  1004  		// This setting should be handled before all of the other options are
  1005  		// processed, so do nothing here.
  1006  
  1007  	case "subdirWatchPatterns":
  1008  		if s, ok := result.asOneOf(
  1009  			string(SubdirWatchPatternsOn),
  1010  			string(SubdirWatchPatternsOff),
  1011  			string(SubdirWatchPatternsAuto),
  1012  		); ok {
  1013  			o.SubdirWatchPatterns = SubdirWatchPatterns(s)
  1014  		}
  1015  
  1016  	case "reportAnalysisProgressAfter":
  1017  		result.setDuration(&o.ReportAnalysisProgressAfter)
  1018  
  1019  	case "telemetryPrompt":
  1020  		result.setBool(&o.TelemetryPrompt)
  1021  
  1022  	case "linkifyShowMessage":
  1023  		result.setBool(&o.LinkifyShowMessage)
  1024  
  1025  	case "includeReplaceInWorkspace":
  1026  		result.setBool(&o.IncludeReplaceInWorkspace)
  1027  
  1028  	case "zeroConfig":
  1029  		result.setBool(&o.ZeroConfig)
  1030  
  1031  	default:
  1032  		result.unexpected()
  1033  	}
  1034  	return result
  1035  }
  1036  
  1037  // parseErrorf reports an error parsing the current configuration value.
  1038  func (r *OptionResult) parseErrorf(msg string, values ...interface{}) {
  1039  	if false {
  1040  		_ = fmt.Sprintf(msg, values...) // this causes vet to check this like printf
  1041  	}
  1042  	prefix := fmt.Sprintf("parsing setting %q: ", r.Name)
  1043  	r.Error = fmt.Errorf(prefix+msg, values...)
  1044  }
  1045  
  1046  // A SoftError is an error that does not affect the functionality of gopls.
  1047  type SoftError struct {
  1048  	msg string
  1049  }
  1050  
  1051  func (e *SoftError) Error() string {
  1052  	return e.msg
  1053  }
  1054  
  1055  // softErrorf reports a soft error related to the current option.
  1056  func (r *OptionResult) softErrorf(format string, args ...any) {
  1057  	r.Error = &SoftError{fmt.Sprintf(format, args...)}
  1058  }
  1059  
  1060  // unexpected reports that the current setting is not known to gopls.
  1061  func (r *OptionResult) unexpected() {
  1062  	r.Error = fmt.Errorf("unexpected gopls setting %q", r.Name)
  1063  }
  1064  
  1065  func (r *OptionResult) asBool() (bool, bool) {
  1066  	b, ok := r.Value.(bool)
  1067  	if !ok {
  1068  		r.parseErrorf("invalid type %T, expect bool", r.Value)
  1069  		return false, false
  1070  	}
  1071  	return b, true
  1072  }
  1073  
  1074  func (r *OptionResult) setBool(b *bool) {
  1075  	if v, ok := r.asBool(); ok {
  1076  		*b = v
  1077  	}
  1078  }
  1079  
  1080  func (r *OptionResult) setDuration(d *time.Duration) {
  1081  	if v, ok := r.asString(); ok {
  1082  		parsed, err := time.ParseDuration(v)
  1083  		if err != nil {
  1084  			r.parseErrorf("failed to parse duration %q: %v", v, err)
  1085  			return
  1086  		}
  1087  		*d = parsed
  1088  	}
  1089  }
  1090  
  1091  func (r *OptionResult) setBoolMap(bm *map[string]bool) {
  1092  	m := r.asBoolMap()
  1093  	*bm = m
  1094  }
  1095  
  1096  func (r *OptionResult) setAnnotationMap(bm *map[Annotation]bool) {
  1097  	all := r.asBoolMap()
  1098  	if all == nil {
  1099  		return
  1100  	}
  1101  	// Default to everything enabled by default.
  1102  	m := make(map[Annotation]bool)
  1103  	for k, enabled := range all {
  1104  		a, err := asOneOf(
  1105  			k,
  1106  			string(Nil),
  1107  			string(Escape),
  1108  			string(Inline),
  1109  			string(Bounds),
  1110  		)
  1111  		if err != nil {
  1112  			// In case of an error, process any legacy values.
  1113  			switch k {
  1114  			case "noEscape":
  1115  				m[Escape] = false
  1116  				r.parseErrorf(`"noEscape" is deprecated, set "Escape: false" instead`)
  1117  			case "noNilcheck":
  1118  				m[Nil] = false
  1119  				r.parseErrorf(`"noNilcheck" is deprecated, set "Nil: false" instead`)
  1120  			case "noInline":
  1121  				m[Inline] = false
  1122  				r.parseErrorf(`"noInline" is deprecated, set "Inline: false" instead`)
  1123  			case "noBounds":
  1124  				m[Bounds] = false
  1125  				r.parseErrorf(`"noBounds" is deprecated, set "Bounds: false" instead`)
  1126  			default:
  1127  				r.parseErrorf("%v", err)
  1128  			}
  1129  			continue
  1130  		}
  1131  		m[Annotation(a)] = enabled
  1132  	}
  1133  	*bm = m
  1134  }
  1135  
  1136  func (r *OptionResult) asBoolMap() map[string]bool {
  1137  	all, ok := r.Value.(map[string]interface{})
  1138  	if !ok {
  1139  		r.parseErrorf("invalid type %T for map[string]bool option", r.Value)
  1140  		return nil
  1141  	}
  1142  	m := make(map[string]bool)
  1143  	for a, enabled := range all {
  1144  		if e, ok := enabled.(bool); ok {
  1145  			m[a] = e
  1146  		} else {
  1147  			r.parseErrorf("invalid type %T for map key %q", enabled, a)
  1148  			return m
  1149  		}
  1150  	}
  1151  	return m
  1152  }
  1153  
  1154  func (r *OptionResult) asString() (string, bool) {
  1155  	b, ok := r.Value.(string)
  1156  	if !ok {
  1157  		r.parseErrorf("invalid type %T, expect string", r.Value)
  1158  		return "", false
  1159  	}
  1160  	return b, true
  1161  }
  1162  
  1163  func (r *OptionResult) asStringSlice() ([]string, bool) {
  1164  	iList, ok := r.Value.([]interface{})
  1165  	if !ok {
  1166  		r.parseErrorf("invalid type %T, expect list", r.Value)
  1167  		return nil, false
  1168  	}
  1169  	var list []string
  1170  	for _, elem := range iList {
  1171  		s, ok := elem.(string)
  1172  		if !ok {
  1173  			r.parseErrorf("invalid element type %T, expect string", elem)
  1174  			return nil, false
  1175  		}
  1176  		list = append(list, s)
  1177  	}
  1178  	return list, true
  1179  }
  1180  
  1181  func (r *OptionResult) asOneOf(options ...string) (string, bool) {
  1182  	s, ok := r.asString()
  1183  	if !ok {
  1184  		return "", false
  1185  	}
  1186  	s, err := asOneOf(s, options...)
  1187  	if err != nil {
  1188  		r.parseErrorf("%v", err)
  1189  	}
  1190  	return s, err == nil
  1191  }
  1192  
  1193  func asOneOf(str string, options ...string) (string, error) {
  1194  	lower := strings.ToLower(str)
  1195  	for _, opt := range options {
  1196  		if strings.ToLower(opt) == lower {
  1197  			return opt, nil
  1198  		}
  1199  	}
  1200  	return "", fmt.Errorf("invalid option %q for enum", str)
  1201  }
  1202  
  1203  func (r *OptionResult) setString(s *string) {
  1204  	if v, ok := r.asString(); ok {
  1205  		*s = v
  1206  	}
  1207  }
  1208  
  1209  func (r *OptionResult) setStringSlice(s *[]string) {
  1210  	if v, ok := r.asStringSlice(); ok {
  1211  		*s = v
  1212  	}
  1213  }
  1214  
  1215  func analyzers() map[string]*Analyzer {
  1216  	return map[string]*Analyzer{}
  1217  }
  1218  
  1219  func urlRegexp() *regexp.Regexp {
  1220  	// Ensure links are matched as full words, not anywhere.
  1221  	re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`)
  1222  	re.Longest()
  1223  	return re
  1224  }