internal/lsp: pass options by reference instead of by value
We were previously behaving as though the slice/map values in the
options struct could be modified directly. The options should be cloned
before modification. Also, convert any usage of source.Options to
*source.Options.
Fixes golang/go#39592
Change-Id: Ib39f668bca0fa1038162206bd7793fd2049af576
Reviewed-on: https://go-review.googlesource.com/c/tools/+/254558
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/hooks/analysis.go b/gopls/internal/hooks/analysis.go
index 4eb042d..1ee1557 100644
--- a/gopls/internal/hooks/analysis.go
+++ b/gopls/internal/hooks/analysis.go
@@ -34,10 +34,6 @@
// Always add hooks for all available analyzers, but disable them if the
// user does not have staticcheck enabled (they may enable it later on).
for _, a := range analyzers {
- addStaticcheckAnalyzer(options, a)
+ options.AddStaticcheckAnalyzer(a)
}
}
-
-func addStaticcheckAnalyzer(options *source.Options, a *analysis.Analyzer) {
- options.StaticcheckAnalyzers[a.Name] = source.Analyzer{Analyzer: a, Enabled: true}
-}
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index da23186..5e54c8b 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -115,10 +115,14 @@
func (c *Cache) NewSession(ctx context.Context) *Session {
index := atomic.AddInt64(&sessionIndex, 1)
+ options := source.DefaultOptions().Clone()
+ if c.options != nil {
+ c.options(options)
+ }
s := &Session{
cache: c,
id: strconv.FormatInt(index, 10),
- options: source.DefaultOptions(),
+ options: options,
overlays: make(map[span.URI]*overlay),
gocmdRunner: &gocommand.Runner{},
}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 141fc27..070bc5f 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -27,7 +27,7 @@
cache *Cache
id string
- options source.Options
+ options *source.Options
viewMu sync.Mutex
views []*View
@@ -117,11 +117,11 @@
func (s *Session) ID() string { return s.id }
func (s *Session) String() string { return s.id }
-func (s *Session) Options() source.Options {
+func (s *Session) Options() *source.Options {
return s.options
}
-func (s *Session) SetOptions(options source.Options) {
+func (s *Session) SetOptions(options *source.Options) {
s.options = options
}
@@ -140,7 +140,7 @@
return s.cache
}
-func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, func(), error) {
+func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
view, snapshot, release, err := s.createView(ctx, name, folder, options, 0)
@@ -153,7 +153,7 @@
return view, snapshot, release, nil
}
-func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
+func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
index := atomic.AddInt64(&viewIndex, 1)
// We want a true background context and not a detached context here
// the spans need to be unrelated and no tag values should pollute it.
@@ -197,7 +197,7 @@
}
if v.session.cache.options != nil {
- v.session.cache.options(&v.options)
+ v.session.cache.options(v.options)
}
// Set the module-specific information.
@@ -253,7 +253,7 @@
//
// It assumes that the caller has not yet created the view, and therefore does
// not lock any of the internal data structures before accessing them.
-func (v *View) findWorkspaceModules(ctx context.Context, options source.Options) error {
+func (v *View) findWorkspaceModules(ctx context.Context, options *source.Options) error {
// If the user is intentionally limiting their workspace scope, add their
// folder to the roots and return early.
if !options.ExpandWorkspaceToModule {
@@ -431,7 +431,7 @@
return nil
}
-func (s *Session) updateView(ctx context.Context, view *View, options source.Options) (*View, error) {
+func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
i, err := s.dropView(ctx, view)
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 4ec760f..2ed8d62 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -40,7 +40,7 @@
id string
optionsMu sync.Mutex
- options source.Options
+ options *source.Options
// mu protects most mutable state of the view.
mu sync.Mutex
@@ -278,13 +278,13 @@
return v.folder
}
-func (v *View) Options() source.Options {
+func (v *View) Options() *source.Options {
v.optionsMu.Lock()
defer v.optionsMu.Unlock()
return v.options
}
-func minorOptionsChange(a, b source.Options) bool {
+func minorOptionsChange(a, b *source.Options) bool {
// Check if any of the settings that modify our understanding of files have been changed
mapEnv := func(env []string) map[string]string {
m := make(map[string]string, len(env))
@@ -315,7 +315,7 @@
return true
}
-func (v *View) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
+func (v *View) SetOptions(ctx context.Context, options *source.Options) (source.View, error) {
// no need to rebuild the view if the options were not materially changed
v.optionsMu.Lock()
if minorOptionsChange(v.options, options) {
@@ -804,7 +804,7 @@
v.initializeOnce = &once
}
-func (v *View) setBuildInformation(ctx context.Context, options source.Options) error {
+func (v *View) setBuildInformation(ctx context.Context, options *source.Options) error {
if err := checkPathCase(v.Folder().Filename()); err != nil {
return errors.Errorf("invalid workspace configuration: %w", err)
}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 35fea61..02b5ca7 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -211,9 +211,9 @@
case strings.HasPrefix(app.Remote, "internal@"):
internalMu.Lock()
defer internalMu.Unlock()
- opts := source.DefaultOptions()
+ opts := source.DefaultOptions().Clone()
if app.options != nil {
- app.options(&opts)
+ app.options(opts)
}
key := fmt.Sprintf("%s %v", app.wd, opts)
if c := internalConnections[key]; c != nil {
@@ -271,9 +271,9 @@
params.Capabilities.Workspace.Configuration = true
// Make sure to respect configured options when sending initialize request.
- opts := source.DefaultOptions()
+ opts := source.DefaultOptions().Clone()
if options != nil {
- options(&opts)
+ options(opts)
}
params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index 9f12051..9ce8dca 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -104,7 +104,7 @@
}, nil
}
-func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
+func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options *source.Options) []protocol.CompletionItem {
var (
items = make([]protocol.CompletionItem, 0, len(candidates))
numDeepCompletionsSeen int
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index 7c6df27..55e4e8f 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -123,8 +123,8 @@
t.Fatal(err)
}
original := view.Options()
- modified := original
- options(&modified)
+ modified := view.Options().Clone()
+ options(modified)
view, err = view.SetOptions(r.ctx, modified)
if err != nil {
t.Error(err)
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 397e6b6..af868e7 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -39,7 +39,7 @@
options := s.session.Options()
defer func() { s.session.SetOptions(options) }()
- if err := s.handleOptionResults(ctx, source.SetOptions(&options, params.InitializationOptions)); err != nil {
+ if err := s.handleOptionResults(ctx, source.SetOptions(options, params.InitializationOptions)); err != nil {
return nil, err
}
options.ForClientCapabilities(params.Capabilities)
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index eccc56b..b829001 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -50,8 +50,8 @@
cache := cache.New(ctx, nil)
session := cache.NewSession(ctx)
- options := source.DefaultOptions()
- tests.DefaultOptions(&options)
+ options := source.DefaultOptions().Clone()
+ tests.DefaultOptions(options)
session.SetOptions(options)
options.Env = datum.Config.Env
view, _, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
@@ -64,7 +64,7 @@
// Enable type error analyses for tests.
// TODO(golang/go#38212): Delete this once they are enabled by default.
- tests.EnableAllAnalyzers(view, &options)
+ tests.EnableAllAnalyzers(view, options)
view.SetOptions(ctx, options)
// Only run the -modfile specific tests in module mode with Go 1.14 or above.
diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go
index 8401c61..63d9a69 100644
--- a/internal/lsp/mod/hover.go
+++ b/internal/lsp/mod/hover.go
@@ -104,7 +104,7 @@
}, nil
}
-func formatExplanation(text string, req *modfile.Require, options source.Options, isPrivate bool) string {
+func formatExplanation(text string, req *modfile.Require, options *source.Options, isPrivate bool) string {
text = strings.TrimSuffix(text, "\n")
splt := strings.Split(text, "\n")
length := len(splt)
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 1103cd8..c9d3e63 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -28,8 +28,8 @@
ctx := tests.Context(t)
cache := cache.New(ctx, nil)
session := cache.NewSession(ctx)
- options := source.DefaultOptions()
- tests.DefaultOptions(&options)
+ options := source.DefaultOptions().Clone()
+ tests.DefaultOptions(options)
options.TempModfile = true
options.Env = []string{"GOPACKAGESDRIVER=off", "GOROOT="}
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 9f1b091..5558d7c 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -221,7 +221,7 @@
// meant to provide diagnostics, but rather only suggested fixes.
// Skip these types of errors in diagnostics; we will use their
// suggested fixes when providing code actions.
- if isConvenienceAnalyzer(e.Category) {
+ if isConvenienceAnalyzer(snapshot.View().Options(), e.Category) {
continue
}
// This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
@@ -312,7 +312,7 @@
return false
}
-func isConvenienceAnalyzer(category string) bool {
+func isConvenienceAnalyzer(o *Options, category string) bool {
for _, a := range DefaultOptions().ConvenienceAnalyzers {
if category == a.Analyzer.Name {
return true
diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go
index 4eb4faf..2d85026 100644
--- a/internal/lsp/source/gc_annotations.go
+++ b/internal/lsp/source/gc_annotations.go
@@ -57,7 +57,7 @@
if x == nil {
continue
}
- v = filterDiagnostics(v, &opts)
+ v = filterDiagnostics(v, opts)
reports[x.VersionedFileIdentity()] = v
}
return reports, parseError
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 7e4a10c..b2cba20 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -373,7 +373,7 @@
return &HoverInformation{source: obj, comment: decl.Doc}
}
-func FormatHover(h *HoverInformation, options Options) (string, error) {
+func FormatHover(h *HoverInformation, options *Options) (string, error) {
signature := h.Signature
if signature != "" && options.PreferredContentFormat == protocol.Markdown {
signature = fmt.Sprintf("```go\n%s\n```", signature)
@@ -403,7 +403,7 @@
return "", errors.Errorf("no hover for %v", h.source)
}
-func formatLink(h *HoverInformation, options Options) string {
+func formatLink(h *HoverInformation, options *Options) string {
if !options.LinksInHover || options.LinkTarget == "" || h.Link == "" {
return ""
}
@@ -418,14 +418,14 @@
}
}
-func formatDoc(doc string, options Options) string {
+func formatDoc(doc string, options *Options) string {
if options.PreferredContentFormat == protocol.Markdown {
return CommentToMarkdown(doc)
}
return doc
}
-func formatHover(options Options, x ...string) string {
+func formatHover(options *Options, x ...string) string {
var b strings.Builder
for i, el := range x {
if el != "" {
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index c70c2a5..ffcfb1d 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -9,8 +9,10 @@
"fmt"
"regexp"
"strings"
+ "sync"
"time"
+ "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/asmdecl"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/atomic"
@@ -52,79 +54,87 @@
errors "golang.org/x/xerrors"
)
+var (
+ optionsOnce sync.Once
+ defaultOptions *Options
+)
+
//go:generate go run golang.org/x/tools/cmd/stringer -output enums_string.go -type ImportShortcut,HoverKind,Matcher,SymbolMatcher,SymbolStyle
//go:generate go run golang.org/x/tools/internal/lsp/source/genopts -output options_json.go
// DefaultOptions is the options that are used for Gopls execution independent
// of any externally provided configuration (LSP initialization, command
// invokation, etc.).
-func DefaultOptions() Options {
- var commands []string
- for _, c := range Commands {
- commands = append(commands, c.Name)
- }
- return Options{
- ClientOptions: ClientOptions{
- InsertTextFormat: protocol.PlainTextTextFormat,
- PreferredContentFormat: protocol.Markdown,
- ConfigurationSupported: true,
- DynamicConfigurationSupported: true,
- DynamicWatchedFilesSupported: true,
- LineFoldingOnly: false,
- HierarchicalDocumentSymbolSupport: true,
- },
- ServerOptions: ServerOptions{
- SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
- Go: {
- protocol.SourceFixAll: true,
- protocol.SourceOrganizeImports: true,
- protocol.QuickFix: true,
- protocol.RefactorRewrite: true,
- protocol.RefactorExtract: true,
- },
- Mod: {
- protocol.SourceOrganizeImports: true,
- },
- Sum: {},
+func DefaultOptions() *Options {
+ optionsOnce.Do(func() {
+ var commands []string
+ for _, c := range Commands {
+ commands = append(commands, c.Name)
+ }
+ defaultOptions = &Options{
+ ClientOptions: ClientOptions{
+ InsertTextFormat: protocol.PlainTextTextFormat,
+ PreferredContentFormat: protocol.Markdown,
+ ConfigurationSupported: true,
+ DynamicConfigurationSupported: true,
+ DynamicWatchedFilesSupported: true,
+ LineFoldingOnly: false,
+ HierarchicalDocumentSymbolSupport: true,
},
- SupportedCommands: commands,
- },
- UserOptions: UserOptions{
- HoverKind: FullDocumentation,
- LinkTarget: "pkg.go.dev",
- },
- DebuggingOptions: DebuggingOptions{
- CompletionBudget: 100 * time.Millisecond,
- LiteralCompletions: true,
- },
- ExperimentalOptions: ExperimentalOptions{
- TempModfile: true,
- ExpandWorkspaceToModule: true,
- Codelens: map[string]bool{
- CommandGenerate.Name: true,
- CommandRegenerateCgo.Name: true,
- CommandTidy.Name: true,
- CommandToggleDetails.Name: false,
- CommandUpgradeDependency.Name: true,
- CommandVendor.Name: true,
+ ServerOptions: ServerOptions{
+ SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
+ Go: {
+ protocol.SourceFixAll: true,
+ protocol.SourceOrganizeImports: true,
+ protocol.QuickFix: true,
+ protocol.RefactorRewrite: true,
+ protocol.RefactorExtract: true,
+ },
+ Mod: {
+ protocol.SourceOrganizeImports: true,
+ },
+ Sum: {},
+ },
+ SupportedCommands: commands,
},
- LinksInHover: true,
- CompleteUnimported: true,
- CompletionDocumentation: true,
- DeepCompletion: true,
- Matcher: Fuzzy,
- SymbolMatcher: SymbolFuzzy,
- },
- Hooks: Hooks{
- ComputeEdits: myers.ComputeEdits,
- URLRegexp: urlRegexp(),
- DefaultAnalyzers: defaultAnalyzers(),
- TypeErrorAnalyzers: typeErrorAnalyzers(),
- ConvenienceAnalyzers: convenienceAnalyzers(),
- StaticcheckAnalyzers: map[string]Analyzer{},
- GoDiff: true,
- },
- }
+ UserOptions: UserOptions{
+ HoverKind: FullDocumentation,
+ LinkTarget: "pkg.go.dev",
+ },
+ DebuggingOptions: DebuggingOptions{
+ CompletionBudget: 100 * time.Millisecond,
+ LiteralCompletions: true,
+ },
+ ExperimentalOptions: ExperimentalOptions{
+ TempModfile: true,
+ ExpandWorkspaceToModule: true,
+ Codelens: map[string]bool{
+ CommandGenerate.Name: true,
+ CommandRegenerateCgo.Name: true,
+ CommandTidy.Name: true,
+ CommandToggleDetails.Name: false,
+ CommandUpgradeDependency.Name: true,
+ CommandVendor.Name: true,
+ },
+ LinksInHover: true,
+ CompleteUnimported: true,
+ CompletionDocumentation: true,
+ DeepCompletion: true,
+ Matcher: Fuzzy,
+ SymbolMatcher: SymbolFuzzy,
+ },
+ Hooks: Hooks{
+ ComputeEdits: myers.ComputeEdits,
+ URLRegexp: urlRegexp(),
+ DefaultAnalyzers: defaultAnalyzers(),
+ TypeErrorAnalyzers: typeErrorAnalyzers(),
+ ConvenienceAnalyzers: convenienceAnalyzers(),
+ StaticcheckAnalyzers: map[string]Analyzer{},
+ GoDiff: true,
+ },
+ }
+ })
+ return defaultOptions
}
// Options holds various configuration that affects Gopls execution, organized
@@ -206,11 +216,11 @@
GoDiff bool
ComputeEdits diff.ComputeEdits
URLRegexp *regexp.Regexp
+ GofumptFormat func(ctx context.Context, src []byte) ([]byte, error)
DefaultAnalyzers map[string]Analyzer
TypeErrorAnalyzers map[string]Analyzer
ConvenienceAnalyzers map[string]Analyzer
StaticcheckAnalyzers map[string]Analyzer
- GofumptFormat func(ctx context.Context, src []byte) ([]byte, error)
}
// ExperimentalOptions defines configuration for features under active
@@ -475,6 +485,59 @@
o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
}
+func (o *Options) Clone() *Options {
+ result := &Options{
+ ClientOptions: o.ClientOptions,
+ DebuggingOptions: o.DebuggingOptions,
+ ExperimentalOptions: o.ExperimentalOptions,
+ Hooks: Hooks{
+ GoDiff: o.Hooks.GoDiff,
+ ComputeEdits: o.Hooks.ComputeEdits,
+ GofumptFormat: o.GofumptFormat,
+ URLRegexp: o.URLRegexp,
+ },
+ ServerOptions: o.ServerOptions,
+ UserOptions: o.UserOptions,
+ }
+ // Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
+ // and UserOptions can be modified.
+ copyStringMap := func(src map[string]bool) map[string]bool {
+ dst := make(map[string]bool)
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+ }
+ result.Analyses = copyStringMap(o.Analyses)
+ result.Annotations = copyStringMap(o.Annotations)
+ result.Codelens = copyStringMap(o.Codelens)
+
+ copySlice := func(src []string) []string {
+ dst := make([]string, len(src))
+ copy(dst, src)
+ return dst
+ }
+ result.Env = copySlice(o.Env)
+ result.BuildFlags = copySlice(o.BuildFlags)
+
+ copyAnalyzerMap := func(src map[string]Analyzer) map[string]Analyzer {
+ dst := make(map[string]Analyzer)
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+ }
+ result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
+ result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers)
+ result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers)
+ result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
+ return result
+}
+
+func (options *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) {
+ options.StaticcheckAnalyzers[a.Name] = Analyzer{Analyzer: a, Enabled: true}
+}
+
func (o *Options) set(name string, value interface{}) OptionResult {
result := OptionResult{Name: name, Value: value}
switch name {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 60081a0..ce790bb 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -52,8 +52,8 @@
cache := cache.New(ctx, nil)
session := cache.NewSession(ctx)
- options := source.DefaultOptions()
- tests.DefaultOptions(&options)
+ options := source.DefaultOptions().Clone()
+ tests.DefaultOptions(options)
options.Env = datum.Config.Env
view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
release()
@@ -64,7 +64,7 @@
// Enable type error analyses for tests.
// TODO(golang/go#38212): Delete this once they are enabled by default.
- tests.EnableAllAnalyzers(view, &options)
+ tests.EnableAllAnalyzers(view, options)
view.SetOptions(ctx, options)
var modifications []source.FileModification
for filename, content := range datum.Config.Overlay {
@@ -295,8 +295,8 @@
t.Fatal(err)
}
original := r.view.Options()
- modified := original
- options(&modified)
+ modified := original.Clone()
+ options(modified)
newView, err := r.view.SetOptions(r.ctx, modified)
if newView != r.view {
t.Fatalf("options change unexpectedly created new view")
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 20ee4b5..0ab43fd 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -153,13 +153,13 @@
RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
// Options returns a copy of the Options for this view.
- Options() Options
+ Options() *Options
// SetOptions sets the options of this view to new values.
// Calling this may cause the view to be invalidated and a replacement view
// added to the session. If so the new view will be returned, otherwise the
// original one will be.
- SetOptions(context.Context, Options) (View, error)
+ SetOptions(context.Context, *Options) (View, error)
// Snapshot returns the current snapshot for the view.
Snapshot(ctx context.Context) (Snapshot, func())
@@ -222,7 +222,7 @@
// A session may have many active views at any given time.
type Session interface {
// NewView creates a new View, returning it and its first snapshot.
- NewView(ctx context.Context, name string, folder span.URI, options Options) (View, Snapshot, func(), error)
+ NewView(ctx context.Context, name string, folder span.URI, options *Options) (View, Snapshot, func(), error)
// Cache returns the cache that created this session, for debugging only.
Cache() interface{}
@@ -250,10 +250,10 @@
Overlays() []Overlay
// Options returns a copy of the SessionOptions for this session.
- Options() Options
+ Options() *Options
// SetOptions sets the options of this session to new values.
- SetOptions(Options)
+ SetOptions(*Options)
}
// Overlay is the type for a file held in memory on a session.
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index 82b7b07..6d77d92 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -33,9 +33,8 @@
if state < serverInitialized {
return nil, nil, func() {}, errors.Errorf("addView called before server initialized")
}
-
- options := s.session.Options()
- if err := s.fetchConfig(ctx, name, uri, &options); err != nil {
+ options := s.session.Options().Clone()
+ if err := s.fetchConfig(ctx, name, uri, options); err != nil {
return nil, nil, func() {}, err
}
return s.session.NewView(ctx, name, uri, options)
@@ -44,8 +43,8 @@
func (s *Server) didChangeConfiguration(ctx context.Context, changed interface{}) error {
// go through all the views getting the config
for _, view := range s.session.Views() {
- options := view.Options()
- if err := s.fetchConfig(ctx, view.Name(), view.Folder(), &options); err != nil {
+ options := s.session.Options().Clone()
+ if err := s.fetchConfig(ctx, view.Name(), view.Folder(), options); err != nil {
return err
}
view, err := view.SetOptions(ctx, options)