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 }