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>
This commit is contained in:
parent
8aaa1484dc
commit
2c3de6a5ae
|
@ -20,6 +20,7 @@ import (
|
|||
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 @@ func New() source.Cache {
|
|||
}
|
||||
|
||||
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})
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
|
@ -14,9 +15,35 @@ import (
|
|||
// 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
|
||||
|
|
|
@ -85,5 +85,5 @@ func (f *fileBase) read(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -31,12 +31,18 @@ type session struct {
|
|||
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 @@ func (s *session) IsOpen(uri span.URI) bool {
|
|||
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 @@ func (s *session) SetOverlay(uri span.URI, data []byte) {
|
|||
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 @@ func (s *session) buildOverlay() map[string][]byte {
|
|||
|
||||
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 @@ func (s debugSession) Files() []*debug.File {
|
|||
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 @@ func (s debugSession) File(hash string) *debug.File {
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,19 +18,37 @@ import (
|
|||
"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
|
||||
|
|
|
@ -66,7 +66,7 @@ func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTex
|
|||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue