internal/lsp/cache: add finer-grained control of file changes
This change is the first step in centralizing control of modifications
to different files, either within the workspace or outside of it. We add
a source.FileAction type to pass into the internal/lsp/cache package and
handle the difference between opening and creating a file.
Now that we load all packages in a workspace by default, we no longer
need to re-load a file on open. This CL should enable CL 206883 to work
correctly.
Change-Id: I2ddb21ca2dd33720d668066e73283f5629d02867
Reviewed-on: https://go-review.googlesource.com/c/tools/+/206888
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go
index f55a818..39c5a7c 100644
--- a/internal/lsp/cache/gofile.go
+++ b/internal/lsp/cache/gofile.go
@@ -33,25 +33,30 @@
fh := s.Handle(ctx, f)
// Determine if we need to type-check the package.
- m, cphs, load, check := s.shouldCheck(fh)
+ metadata, cphs, load, check := s.shouldCheck(fh)
// We may need to re-load package metadata.
// We only need to this if it has been invalidated, and is therefore unvailable.
if load {
var err error
- m, err = s.load(ctx, source.FileURI(f.URI()))
+ m, err := s.load(ctx, source.FileURI(f.URI()))
if err != nil {
return nil, err
}
// If load has explicitly returned nil metadata and no error,
// it means that we should not re-type-check the packages.
- if m == nil {
+ // Return the cached CheckPackageHandles, if we had them.
+ if m == nil && len(cphs) > 0 {
return cphs, nil
}
+ // If metadata was returned, from the load call, use it.
+ if m != nil {
+ metadata = m
+ }
}
if check {
var results []source.CheckPackageHandle
- for _, m := range m {
+ for _, m := range metadata {
cph, err := s.checkPackageHandle(ctx, m.id, source.ParseFull)
if err != nil {
return nil, err
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 1b8cd35..eb1c85c 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -15,7 +15,6 @@
"sync/atomic"
"golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/span"
@@ -338,7 +337,7 @@
s.overlayMu.Lock()
defer func() {
s.overlayMu.Unlock()
- s.filesWatchMap.Notify(uri, protocol.Changed)
+ s.filesWatchMap.Notify(uri, source.Change)
}()
if data == nil {
@@ -374,7 +373,7 @@
s.overlayMu.Lock()
defer func() {
s.overlayMu.Unlock()
- s.filesWatchMap.Notify(uri, protocol.Created)
+ s.filesWatchMap.Notify(uri, source.Open)
}()
s.overlays[uri] = &overlay{
session: s,
@@ -418,8 +417,8 @@
return overlays
}
-func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, changeType protocol.FileChangeType) bool {
- return s.filesWatchMap.Notify(uri, changeType)
+func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, action source.FileAction) bool {
+ return s.filesWatchMap.Notify(uri, action)
}
func (o *overlay) FileSystem() source.FileSystem {
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index db332dc..dcd95e4 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -7,7 +7,6 @@
"sync"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
@@ -352,7 +351,7 @@
// invalidateContent invalidates the content of a Go file,
// including any position and type information that depends on it.
-func (v *view) invalidateContent(ctx context.Context, f source.File, changeType protocol.FileChangeType) bool {
+func (v *view) invalidateContent(ctx context.Context, f source.File, kind source.FileKind, action source.FileAction) bool {
var (
withoutTypes = make(map[span.URI]struct{})
withoutMetadata = make(map[span.URI]struct{})
@@ -370,8 +369,8 @@
// Get the original FileHandle for the URI, if it exists.
originalFH := v.snapshot.getFile(f.URI())
- switch changeType {
- case protocol.Created:
+ switch action {
+ case source.Create:
// If this is a file we don't yet know about,
// then we do not yet know what packages it should belong to.
// Make a rough estimate of what metadata to invalidate by finding the package IDs
@@ -388,7 +387,6 @@
}
}
}
- // If a file has been explicitly created, make sure that its original file handle is nil.
originalFH = nil
}
@@ -402,7 +400,7 @@
}
// Make sure to clear out the content if there has been a deletion.
- if changeType == protocol.Deleted {
+ if action == source.Delete {
v.session.clearOverlay(f.URI())
}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 155a374..e2a4236 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -376,9 +376,9 @@
fname: uri.Filename(),
kind: kind,
}
- v.session.filesWatchMap.Watch(uri, func(changeType protocol.FileChangeType) bool {
+ v.session.filesWatchMap.Watch(uri, func(action source.FileAction) bool {
ctx := xcontext.Detach(ctx)
- return v.invalidateContent(ctx, f, changeType)
+ return v.invalidateContent(ctx, f, kind, action)
})
v.mapFile(uri, f)
return f, nil
diff --git a/internal/lsp/cache/watcher.go b/internal/lsp/cache/watcher.go
index 53fe4dc..b955e1a 100644
--- a/internal/lsp/cache/watcher.go
+++ b/internal/lsp/cache/watcher.go
@@ -7,12 +7,12 @@
import (
"sync"
- "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/source"
)
type watcher struct {
id uint64
- callback func(changeType protocol.FileChangeType) bool
+ callback func(action source.FileAction) bool
}
type WatchMap struct {
@@ -24,7 +24,8 @@
func NewWatchMap() *WatchMap {
return &WatchMap{watchers: make(map[interface{}][]watcher)}
}
-func (w *WatchMap) Watch(key interface{}, callback func(protocol.FileChangeType) bool) func() {
+
+func (w *WatchMap) Watch(key interface{}, callback func(source.FileAction) bool) func() {
w.mu.Lock()
defer w.mu.Unlock()
id := w.nextID
@@ -49,7 +50,7 @@
}
}
-func (w *WatchMap) Notify(key interface{}, changeType protocol.FileChangeType) bool {
+func (w *WatchMap) Notify(key interface{}, action source.FileAction) bool {
// Make a copy of the watcher callbacks so we don't need to hold
// the mutex during the callbacks (to avoid deadlocks).
w.mu.Lock()
@@ -60,7 +61,7 @@
var result bool
for _, entry := range entriesCopy {
- result = entry.callback(changeType) || result
+ result = entry.callback(action) || result
}
return result
}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index aa1e2a8..16907b5 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -194,7 +194,7 @@
// DidChangeOutOfBand is called when a file under the root folder changes.
// If the file was open in the editor, it returns true.
- DidChangeOutOfBand(ctx context.Context, uri span.URI, change protocol.FileChangeType) bool
+ DidChangeOutOfBand(ctx context.Context, uri span.URI, action FileAction) bool
// Options returns a copy of the SessionOptions for this session.
Options() Options
@@ -203,6 +203,17 @@
SetOptions(Options)
}
+type FileAction int
+
+const (
+ Open = FileAction(iota)
+ Close
+ Change
+ Create
+ Delete
+ UnknownFileAction
+)
+
// View represents a single workspace.
// This is the level at which we maintain configuration like working directory
// and build tags.
diff --git a/internal/lsp/watched_files.go b/internal/lsp/watched_files.go
index b6238e0..10e1a9a 100644
--- a/internal/lsp/watched_files.go
+++ b/internal/lsp/watched_files.go
@@ -23,19 +23,20 @@
if !view.Options().WatchFileChanges {
continue
}
- switch change.Type {
- case protocol.Changed, protocol.Created:
+ action := toFileAction(change.Type)
+ switch action {
+ case source.Change, source.Create:
// If client has this file open, don't do anything.
// The client's contents must remain the source of truth.
if s.session.IsOpen(uri) {
break
}
- if s.session.DidChangeOutOfBand(ctx, uri, change.Type) {
+ if s.session.DidChangeOutOfBand(ctx, uri, action) {
// If we had been tracking the given file,
// recompute diagnostics to reflect updated file contents.
go s.diagnostics(view, uri)
}
- case protocol.Deleted:
+ case source.Delete:
f := view.FindFile(ctx, uri)
// If we have never seen this file before, there is nothing to do.
if f == nil {
@@ -65,7 +66,7 @@
}
// Notify the view of the deletion of the file.
- s.session.DidChangeOutOfBand(ctx, uri, change.Type)
+ s.session.DidChangeOutOfBand(ctx, uri, action)
// If this was the only file in the package, clear its diagnostics.
if otherFile == nil {
@@ -82,3 +83,15 @@
}
return nil
}
+
+func toFileAction(ct protocol.FileChangeType) source.FileAction {
+ switch ct {
+ case protocol.Changed:
+ return source.Change
+ case protocol.Created:
+ return source.Create
+ case protocol.Deleted:
+ return source.Delete
+ }
+ return source.UnknownFileAction
+}