| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package cache |
| |
| import ( |
| "bytes" |
| "context" |
| |
| "golang.org/x/tools/internal/lsp/source" |
| "golang.org/x/tools/internal/span" |
| errors "golang.org/x/xerrors" |
| ) |
| |
| type overlay struct { |
| session *session |
| uri span.URI |
| data []byte |
| hash string |
| version float64 |
| kind source.FileKind |
| |
| // sameContentOnDisk is true if a file has been saved on disk, |
| // and therefore does not need to be part of the overlay sent to go/packages. |
| sameContentOnDisk bool |
| } |
| |
| func (o *overlay) FileSystem() source.FileSystem { |
| return o.session |
| } |
| |
| func (o *overlay) Identity() source.FileIdentity { |
| return source.FileIdentity{ |
| URI: o.uri, |
| Identifier: o.hash, |
| Version: o.version, |
| Kind: o.kind, |
| } |
| } |
| func (o *overlay) Read(ctx context.Context) ([]byte, string, error) { |
| return o.data, o.hash, nil |
| } |
| |
| func (s *session) setOverlay(uri span.URI, version float64, data []byte) error { |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| o, ok := s.overlays[uri] |
| if !ok { |
| return errors.Errorf("setting overlay for unopened file %s", uri) |
| } |
| s.overlays[uri] = &overlay{ |
| session: s, |
| uri: uri, |
| kind: o.kind, |
| data: data, |
| hash: hashContents(data), |
| version: version, |
| } |
| return nil |
| } |
| |
| func (s *session) closeOverlay(uri span.URI) error { |
| s.openFiles.Delete(uri) |
| |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| _, ok := s.overlays[uri] |
| if !ok { |
| return errors.Errorf("closing unopened overlay %s", uri) |
| } |
| delete(s.overlays, uri) |
| return nil |
| } |
| |
| func (s *session) openOverlay(ctx context.Context, uri span.URI, languageID string, version float64, data []byte) error { |
| kind := source.DetectLanguage(languageID, uri.Filename()) |
| if kind == source.UnknownKind { |
| return errors.Errorf("openOverlay: unknown file kind for %s", uri) |
| } |
| |
| s.openFiles.Store(uri, true) |
| |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| s.overlays[uri] = &overlay{ |
| session: s, |
| uri: uri, |
| kind: kind, |
| data: data, |
| hash: hashContents(data), |
| version: version, |
| } |
| // If the file is on disk, check if its content is the same as the overlay. |
| if _, hash, err := s.cache.GetFile(uri, kind).Read(ctx); err == nil { |
| if hash == s.overlays[uri].hash { |
| s.overlays[uri].sameContentOnDisk = true |
| } |
| } |
| return nil |
| } |
| |
| func (s *session) saveOverlay(uri span.URI, version float64, data []byte) error { |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| o, ok := s.overlays[uri] |
| if !ok { |
| return errors.Errorf("saveOverlay: unopened overlay %s", uri) |
| } |
| if o.version != version { |
| return errors.Errorf("saveOverlay: saving %s at version %v, currently at %v", uri, version, o.version) |
| } |
| if data != nil { |
| if !bytes.Equal(o.data, data) { |
| return errors.Errorf("saveOverlay: overlay %s changed on save", uri) |
| } |
| o.data = data |
| } |
| o.sameContentOnDisk = true |
| o.version = version |
| |
| return nil |
| } |
| |
| func (s *session) readOverlay(uri span.URI) *overlay { |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| // We might have the content saved in an overlay. |
| if overlay, ok := s.overlays[uri]; ok { |
| return overlay |
| } |
| return nil |
| } |
| |
| func (s *session) buildOverlay() map[string][]byte { |
| s.overlayMu.Lock() |
| defer s.overlayMu.Unlock() |
| |
| overlays := make(map[string][]byte) |
| for uri, overlay := range s.overlays { |
| // TODO(rstambler): Make sure not to send overlays outside of the current view. |
| if overlay.sameContentOnDisk { |
| continue |
| } |
| overlays[uri.Filename()] = overlay.data |
| } |
| return overlays |
| } |