Skip to content

Commit d9dbc12

Browse files
committed
gopls: add WorkspaceFiles option
WorkspaceFiles allows an end-user to specify a set of files which, when modified, will trigger a full reload of any views currently open in a session. This is especially important for users who use custom GOPACKAGESDRIVERS, as previously, you were forced to restart the language server in order to get up-to-date diagnostics in some certain instances. Change-Id: Iba7a6137cb0b88a59318217a9a28d079100192a4
1 parent a6adab9 commit d9dbc12

File tree

9 files changed

+85
-31
lines changed

9 files changed

+85
-31
lines changed

gopls/doc/settings.md

+11
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ This setting is only supported when gopls is built with Go 1.16 or later.
143143

144144
Default: `["ignore"]`.
145145

146+
<a id='workspaceFiles'></a>
147+
### `workspaceFiles []string`
148+
149+
workspaceFiles configures the set of globs that match files defining the logical build of the current workspace.
150+
Any on-disk changes to any files matching a glob specified here will trigger a reload of the workspace.
151+
152+
This setting need only be customized in environments with a custom GOPACKAGESDRIVER. By default, this will look
153+
for changes to every go.mod / go.work file in your workspace.
154+
155+
Default: `["**/*.{mod,work}"]`.
156+
146157
<a id='formatting'></a>
147158
## Formatting
148159

gopls/internal/cache/session.go

+24-27
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"errors"
1010
"fmt"
11+
"maps"
1112
"os"
1213
"path/filepath"
1314
"slices"
@@ -773,6 +774,25 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
773774
// changed on disk.
774775
checkViews := false
775776

777+
// Hack: collect folders from existing views.
778+
// TODO(golang/go#57979): we really should track folders independent of
779+
// Views, but since we always have a default View for each folder, this
780+
// works for now.
781+
var folders []*Folder // preserve folder order
782+
workspaceFileGlobsSet := make(map[string]bool)
783+
seen := make(map[*Folder]unit)
784+
for _, v := range s.views {
785+
if _, ok := seen[v.folder]; ok {
786+
continue
787+
}
788+
seen[v.folder] = unit{}
789+
folders = append(folders, v.folder)
790+
for _, glob := range v.folder.Options.WorkspaceFiles {
791+
workspaceFileGlobsSet[glob] = true
792+
}
793+
}
794+
workspaceFileGlobs := slices.Collect(maps.Keys(workspaceFileGlobsSet))
795+
776796
changed := make(map[protocol.DocumentURI]file.Handle)
777797
for _, c := range modifications {
778798
fh := mustReadFile(ctx, s, c.URI)
@@ -783,12 +803,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
783803
checkViews = true
784804
}
785805

786-
// Any on-disk change to a go.work or go.mod file causes recomputing views.
787-
//
788-
// TODO(rfindley): go.work files need not be named "go.work" -- we need to
789-
// check each view's source to handle the case of an explicit GOWORK value.
790-
// Write a test that fails, and fix this.
791-
if (isGoWork(c.URI) || isGoMod(c.URI)) && (c.Action == file.Save || c.OnDisk) {
806+
if isWorkspaceFile(c.URI, workspaceFileGlobs) && (c.Action == file.Save || c.OnDisk) {
792807
checkViews = true
793808
}
794809

@@ -815,20 +830,6 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif
815830
}
816831

817832
if checkViews {
818-
// Hack: collect folders from existing views.
819-
// TODO(golang/go#57979): we really should track folders independent of
820-
// Views, but since we always have a default View for each folder, this
821-
// works for now.
822-
var folders []*Folder // preserve folder order
823-
seen := make(map[*Folder]unit)
824-
for _, v := range s.views {
825-
if _, ok := seen[v.folder]; ok {
826-
continue
827-
}
828-
seen[v.folder] = unit{}
829-
folders = append(folders, v.folder)
830-
}
831-
832833
var openFiles []protocol.DocumentURI
833834
for _, o := range s.Overlays() {
834835
openFiles = append(openFiles, o.URI())
@@ -1084,12 +1085,12 @@ func (b brokenFile) Content() ([]byte, error) { return nil, b.err }
10841085
// keep the server's state up to date.
10851086
//
10861087
// This set includes
1087-
// 1. all go.mod and go.work files in the workspace; and
1088+
// 1. all files defined by the WorkspaceFiles option in BuildOptions (to support custom GOPACKAGESDRIVERS); and
10881089
// 2. for each Snapshot, its modules (or directory for ad-hoc views). In
10891090
// module mode, this is the set of active modules (and for VS Code, all
10901091
// workspace directories within them, due to golang/go#42348).
10911092
//
1092-
// The watch for workspace go.work and go.mod files in (1) is sufficient to
1093+
// The watch for workspace files in (1) is sufficient to
10931094
// capture changes to the repo structure that may affect the set of views.
10941095
// Whenever this set changes, we reload the workspace and invalidate memoized
10951096
// files.
@@ -1114,12 +1115,8 @@ func (b brokenFile) Content() ([]byte, error) { return nil, b.err }
11141115
func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit {
11151116
s.viewMu.Lock()
11161117
defer s.viewMu.Unlock()
1117-
11181118
// Always watch files that may change the set of views.
1119-
patterns := map[protocol.RelativePattern]unit{
1120-
{Pattern: "**/*.{mod,work}"}: {},
1121-
}
1122-
1119+
patterns := map[protocol.RelativePattern]unit{}
11231120
for _, view := range s.views {
11241121
snapshot, release, err := view.Snapshot()
11251122
if err != nil {

gopls/internal/cache/snapshot.go

+9
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,10 @@ func (s *Snapshot) fileWatchingGlobPatterns() map[protocol.RelativePattern]unit
799799
patterns[workPattern] = unit{}
800800
}
801801

802+
for _, glob := range s.Options().WorkspaceFiles {
803+
patterns[protocol.RelativePattern{Pattern: glob}] = unit{}
804+
}
805+
802806
extensions := "go,mod,sum,work"
803807
for _, ext := range s.Options().TemplateExtensions {
804808
extensions += "," + ext
@@ -1560,6 +1564,11 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f
15601564
reinit = true
15611565
break
15621566
}
1567+
1568+
if isWorkspaceFile(mod.URI, s.view.workspaceFiles) && (mod.Action == file.Save || mod.OnDisk) {
1569+
reinit = true
1570+
break
1571+
}
15631572
}
15641573

15651574
// Collect observed file handles for changed URIs from the old snapshot, if

gopls/internal/cache/view.go

+4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ type viewDefinition struct {
182182

183183
// envOverlay holds additional environment to apply to this viewDefinition.
184184
envOverlay map[string]string
185+
186+
// workspaceFiles are the set of files that will trigger this view to be re-initialized
187+
workspaceFiles []string
185188
}
186189

187190
// definition implements the viewDefiner interface.
@@ -830,6 +833,7 @@ func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile fil
830833

831834
def := new(viewDefinition)
832835
def.folder = folder
836+
def.workspaceFiles = folder.Options.WorkspaceFiles
833837

834838
if forFile != nil && fileKind(forFile) == file.Go {
835839
// If the file has GOOS/GOARCH build constraints that

gopls/internal/cache/workspace.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"golang.org/x/mod/modfile"
1414
"golang.org/x/tools/gopls/internal/file"
1515
"golang.org/x/tools/gopls/internal/protocol"
16+
"golang.org/x/tools/gopls/internal/test/integration/fake/glob"
1617
)
1718

1819
// isGoWork reports if uri is a go.work file.
@@ -60,9 +61,18 @@ func localModFiles(relativeTo string, goWorkOrModPaths []string) map[protocol.Do
6061
return modFiles
6162
}
6263

63-
// isGoMod reports if uri is a go.mod file.
64-
func isGoMod(uri protocol.DocumentURI) bool {
65-
return filepath.Base(uri.Path()) == "go.mod"
64+
func isWorkspaceFile(uri protocol.DocumentURI, workspaceFiles []string) bool {
65+
for _, workspaceFile := range workspaceFiles {
66+
g, err := glob.Parse(workspaceFile)
67+
if err != nil {
68+
continue
69+
}
70+
71+
if g.Match(uri.Path()) {
72+
return true
73+
}
74+
}
75+
return false
6676
}
6777

6878
// goModModules returns the URIs of "workspace" go.mod files defined by a

gopls/internal/cmd/capabilities_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func TestCapabilities(t *testing.T) {
152152
}
153153
// The item's TextEdit must be a pointer, as VS Code considers TextEdits
154154
// that don't contain the cursor position to be invalid.
155-
var textEdit = item.TextEdit.Value
155+
textEdit := item.TextEdit.Value
156156
switch textEdit.(type) {
157157
case protocol.TextEdit, protocol.InsertReplaceEdit:
158158
default:

gopls/internal/doc/api.json

+13
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@
9292
"Status": "",
9393
"Hierarchy": "build"
9494
},
95+
{
96+
"Name": "workspaceFiles",
97+
"Type": "[]string",
98+
"Doc": "workspaceFiles configures the set of globs that match files defining the logical build of the current workspace.\nAny on-disk changes to any files matching a glob specified here will trigger a reload of the workspace.\n\nThis setting need only be customized in environments with a custom GOPACKAGESDRIVER. By default, this will look\nfor changes to every go.mod / go.work file in your workspace.\n",
99+
"EnumKeys": {
100+
"ValueType": "",
101+
"Keys": null
102+
},
103+
"EnumValues": null,
104+
"Default": "[\"**/*.{mod,work}\"]",
105+
"Status": "",
106+
"Hierarchy": "build"
107+
},
95108
{
96109
"Name": "hoverKind",
97110
"Type": "enum",

gopls/internal/settings/default.go

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func DefaultOptions(overrides ...func(*Options)) *Options {
8686
DirectoryFilters: []string{"-**/node_modules"},
8787
TemplateExtensions: []string{},
8888
StandaloneTags: []string{"ignore"},
89+
WorkspaceFiles: []string{"**/*.{mod,work}"},
8990
},
9091
UIOptions: UIOptions{
9192
DiagnosticOptions: DiagnosticOptions{

gopls/internal/settings/settings.go

+9
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ type BuildOptions struct {
155155
//
156156
// This setting is only supported when gopls is built with Go 1.16 or later.
157157
StandaloneTags []string
158+
159+
// WorkspaceFiles configures the set of globs that match files defining the logical build of the current workspace.
160+
// Any on-disk changes to any files matching a glob specified here will trigger a reload of the workspace.
161+
//
162+
// This setting need only be customized in environments with a custom GOPACKAGESDRIVER. By default, this will look
163+
// for changes to every go.mod / go.work file in your workspace.
164+
WorkspaceFiles []string
158165
}
159166

160167
// Note: UIOptions must be comparable with reflect.DeepEqual.
@@ -979,6 +986,8 @@ func (o *Options) setOne(name string, value any) error {
979986
}
980987
o.DirectoryFilters = filters
981988

989+
case "workspaceFiles":
990+
return setStringSlice(&o.BuildOptions.WorkspaceFiles, value)
982991
case "completionDocumentation":
983992
return setBool(&o.CompletionDocumentation, value)
984993
case "usePlaceholders":

0 commit comments

Comments
 (0)