internal/lsp: change file system to allow lazy reads
We split aquiring a "handle" from reading a files contents so that we can do the
former eagerly and the latter lazily.
We also "version" the handles so that the same file at different versions is a
different handle.
Change-Id: I06cc346d4b4c77d784aa454702c54689f2f177e0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/179917
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index 2f0e14d..81a8c77 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -20,6 +20,7 @@
func New() source.Cache {
index := atomic.AddInt64(&cacheIndex, 1)
c := &cache{
+ fs: &nativeFileSystem{},
id: strconv.FormatInt(index, 10),
fset: token.NewFileSet(),
}
@@ -28,19 +29,22 @@
}
type cache struct {
- nativeFileSystem
-
+ fs source.FileSystem
id string
fset *token.FileSet
}
+func (c *cache) GetFile(uri span.URI) source.FileHandle {
+ return c.fs.GetFile(uri)
+}
+
func (c *cache) NewSession(log xlog.Logger) source.Session {
index := atomic.AddInt64(&sessionIndex, 1)
s := &session{
cache: c,
id: strconv.FormatInt(index, 10),
log: log,
- overlays: make(map[span.URI]*source.FileContent),
+ overlays: make(map[span.URI]*overlay),
filesWatchMap: NewWatchMap(),
}
debug.AddSession(debugSession{s})
diff --git a/internal/lsp/cache/external.go b/internal/lsp/cache/external.go
index 4bb03cd..9936344 100644
--- a/internal/lsp/cache/external.go
+++ b/internal/lsp/cache/external.go
@@ -5,6 +5,7 @@
package cache
import (
+ "context"
"io/ioutil"
"golang.org/x/tools/internal/lsp/source"
@@ -14,9 +15,35 @@
// nativeFileSystem implements FileSystem reading from the normal os file system.
type nativeFileSystem struct{}
-func (nativeFileSystem) ReadFile(uri span.URI) *source.FileContent {
- r := &source.FileContent{URI: uri}
- filename, err := uri.Filename()
+// nativeFileHandle implements FileHandle for nativeFileSystem
+type nativeFileHandle struct {
+ fs *nativeFileSystem
+ identity source.FileIdentity
+}
+
+func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle {
+ return &nativeFileHandle{
+ fs: fs,
+ identity: source.FileIdentity{
+ URI: uri,
+ // TODO: decide what the version string is for a native file system
+ // could be the mtime?
+ Version: "",
+ },
+ }
+}
+
+func (h *nativeFileHandle) FileSystem() source.FileSystem {
+ return h.fs
+}
+
+func (h *nativeFileHandle) Identity() source.FileIdentity {
+ return h.identity
+}
+
+func (h *nativeFileHandle) Read(ctx context.Context) *source.FileContent {
+ r := &source.FileContent{}
+ filename, err := h.identity.URI.Filename()
if err != nil {
r.Error = err
return r
diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go
index d7a2b91..424a322 100644
--- a/internal/lsp/cache/file.go
+++ b/internal/lsp/cache/file.go
@@ -85,5 +85,5 @@
}
}
// We don't know the content yet, so read it.
- f.fc = f.view.Session().ReadFile(f.URI())
+ f.fc = f.view.Session().GetFile(f.URI()).Read(ctx)
}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 1b4881b..1d71e0e 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -31,12 +31,18 @@
viewMap map[span.URI]source.View
overlayMu sync.Mutex
- overlays map[span.URI]*source.FileContent
+ overlays map[span.URI]*overlay
openFiles sync.Map
filesWatchMap *WatchMap
}
+type overlay struct {
+ session *session
+ uri span.URI
+ content source.FileContent
+}
+
func (s *session) Shutdown(ctx context.Context) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
@@ -186,12 +192,12 @@
return open
}
-func (s *session) ReadFile(uri span.URI) *source.FileContent {
+func (s *session) GetFile(uri span.URI) source.FileHandle {
if overlay := s.readOverlay(uri); overlay != nil {
return overlay
}
// Fall back to the cache-level file system.
- return s.Cache().ReadFile(uri)
+ return s.Cache().GetFile(uri)
}
func (s *session) SetOverlay(uri span.URI, data []byte) {
@@ -205,14 +211,17 @@
delete(s.overlays, uri)
return
}
- s.overlays[uri] = &source.FileContent{
- URI: uri,
- Data: data,
- Hash: hashContents(data),
+ s.overlays[uri] = &overlay{
+ session: s,
+ uri: uri,
+ content: source.FileContent{
+ Data: data,
+ Hash: hashContents(data),
+ },
}
}
-func (s *session) readOverlay(uri span.URI) *source.FileContent {
+func (s *session) readOverlay(uri span.URI) *overlay {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
@@ -229,16 +238,31 @@
overlays := make(map[string][]byte)
for uri, overlay := range s.overlays {
- if overlay.Error != nil {
+ if overlay.content.Error != nil {
continue
}
if filename, err := uri.Filename(); err == nil {
- overlays[filename] = overlay.Data
+ overlays[filename] = overlay.content.Data
}
}
return overlays
}
+func (o *overlay) FileSystem() source.FileSystem {
+ return o.session
+}
+
+func (o *overlay) Identity() source.FileIdentity {
+ return source.FileIdentity{
+ URI: o.uri,
+ Version: o.content.Hash,
+ }
+}
+
+func (o *overlay) Read(ctx context.Context) *source.FileContent {
+ return &o.content
+}
+
type debugSession struct{ *session }
func (s debugSession) ID() string { return s.id }
@@ -258,15 +282,15 @@
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
- f, ok := seen[overlay.URI]
+ f, ok := seen[overlay.uri]
if !ok {
- f = &debug.File{Session: s, URI: overlay.URI}
- seen[overlay.URI] = f
+ f = &debug.File{Session: s, URI: overlay.uri}
+ seen[overlay.uri] = f
files = append(files, f)
}
- f.Data = string(overlay.Data)
- f.Error = overlay.Error
- f.Hash = overlay.Hash
+ f.Data = string(overlay.content.Data)
+ f.Error = overlay.content.Error
+ f.Hash = overlay.content.Hash
}
sort.Slice(files, func(i int, j int) bool {
return files[i].URI < files[j].URI
@@ -278,13 +302,13 @@
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
- if overlay.Hash == hash {
+ if overlay.content.Hash == hash {
return &debug.File{
Session: s,
- URI: overlay.URI,
- Data: string(overlay.Data),
- Error: overlay.Error,
- Hash: overlay.Hash,
+ URI: overlay.uri,
+ Data: string(overlay.content.Data),
+ Error: overlay.content.Error,
+ Hash: overlay.content.Hash,
}
}
}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 8f7ce8c..ca529cc 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -18,19 +18,37 @@
"golang.org/x/tools/internal/span"
)
-// FileContents is returned from FileSystem implementation to represent the
+// FileContent is returned from FileSystem implementation to represent the
// contents of a file.
type FileContent struct {
- URI span.URI
Data []byte
Error error
Hash string
}
+// FileIdentity uniquely identifies a file at a version from a FileSystem.
+type FileIdentity struct {
+ URI span.URI
+ Version string
+}
+
+// FileHandle represents a handle to a specific version of a single file from
+// a specific file system.
+type FileHandle interface {
+ // FileSystem returns the file system this handle was aquired from.
+ FileSystem() FileSystem
+ // Return the Identity for the file.
+ Identity() FileIdentity
+ // Read reads the contents of a file and returns it.
+ // If the file is not available, the returned FileContent will have no
+ // data and an error.
+ Read(ctx context.Context) *FileContent
+}
+
// FileSystem is the interface to something that provides file contents.
type FileSystem interface {
- // ReadFile reads the contents of a file and returns it.
- ReadFile(uri span.URI) *FileContent
+ // GetFile returns a handle for the specified file.
+ GetFile(uri span.URI) FileHandle
}
// Cache abstracts the core logic of dealing with the environment from the
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 28c636d..73d7451 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -66,7 +66,7 @@
}
uri := span.NewURI(params.TextDocument.URI)
- fc := s.session.ReadFile(uri)
+ fc := s.session.GetFile(uri).Read(ctx)
if fc.Error != nil {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
}