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