internal/lsp: stop requiring file kind when fetching a file

This change consolidates the FileKind into only the FileHandle.
Previously, it had been set in multiple places, which required users to
pass in a FileKind when fetching a file. This resulted in confusion,
particularly in places when users did not have access to the file kind.

Change-Id: I9e07d7320c46a21d453ffe108d1431a615706a71
Reviewed-on: https://go-review.googlesource.com/c/tools/+/213459
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rebecca Stambler 2020-01-06 18:08:39 -05:00
parent 487733262f
commit dfcf57064e
10 changed files with 147 additions and 171 deletions

View File

@ -44,7 +44,7 @@ func (v *view) buildBuiltinPackage(ctx context.Context) error {
pkg := pkgs[0]
files := make(map[string]*ast.File)
for _, filename := range pkg.GoFiles {
fh := v.session.GetFile(span.FileURI(filename), source.Go)
fh := v.session.GetFile(span.FileURI(filename))
ph := v.session.cache.ParseGoHandle(fh, source.ParseFull)
v.builtin.files = append(v.builtin.files, ph)
file, _, _, err := ph.Parse(ctx)

View File

@ -56,8 +56,8 @@ type fileData struct {
err error
}
func (c *cache) GetFile(uri span.URI, kind source.FileKind) source.FileHandle {
underlying := c.fs.GetFile(uri, kind)
func (c *cache) GetFile(uri span.URI) source.FileHandle {
underlying := c.fs.GetFile(uri)
key := fileKey{
identity: underlying.Identity(),
}

View File

@ -27,11 +27,12 @@ type nativeFileHandle struct {
identity source.FileIdentity
}
func (fs *nativeFileSystem) GetFile(uri span.URI, kind source.FileKind) source.FileHandle {
func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle {
identifier := "DOES NOT EXIST"
if fi, err := os.Stat(uri.Filename()); err == nil {
identifier = fi.ModTime().String()
}
kind := source.DetectLanguage("", uri.Filename())
return &nativeFileHandle{
fs: fs,
identity: source.FileIdentity{

View File

@ -1,49 +0,0 @@
// Copyright 2018 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 (
"go/token"
"path/filepath"
"strings"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)
// fileBase holds the common functionality for all files.
// It is intended to be embedded in the file implementations
type fileBase struct {
uris []span.URI
fname string
kind source.FileKind
view *view
}
func dir(filename string) string {
return strings.ToLower(filepath.Dir(filename))
}
func basename(filename string) string {
return strings.ToLower(filepath.Base(filename))
}
func (f *fileBase) URI() span.URI {
return f.uris[0]
}
func (f *fileBase) filename() string {
return f.fname
}
// View returns the view associated with the file.
func (f *fileBase) View() source.View {
return f.view
}
func (f *fileBase) FileSet() *token.FileSet {
return f.view.Session().Cache().FileSet()
}

View File

@ -41,7 +41,7 @@ func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
return o.text, o.hash, nil
}
func (s *session) updateOverlay(ctx context.Context, c source.FileModification) error {
func (s *session) updateOverlay(ctx context.Context, c source.FileModification) (source.FileKind, error) {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
@ -54,18 +54,18 @@ func (s *session) updateOverlay(ctx context.Context, c source.FileModification)
kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
default:
if !ok {
return errors.Errorf("updateOverlay: modifying unopened overlay %v", c.URI)
return -1, errors.Errorf("updateOverlay: modifying unopened overlay %v", c.URI)
}
kind = o.kind
}
if kind == source.UnknownKind {
return errors.Errorf("updateOverlay: unknown file kind for %s", c.URI)
return -1, errors.Errorf("updateOverlay: unknown file kind for %s", c.URI)
}
// Closing a file just deletes its overlay.
if c.Action == source.Close {
delete(s.overlays, c.URI)
return nil
return kind, nil
}
// If the file is on disk, check if its content is the same as the overlay.
@ -77,15 +77,15 @@ func (s *session) updateOverlay(ctx context.Context, c source.FileModification)
var sameContentOnDisk bool
switch c.Action {
case source.Open:
_, h, err := s.cache.GetFile(c.URI, kind).Read(ctx)
_, h, err := s.cache.GetFile(c.URI).Read(ctx)
sameContentOnDisk = (err == nil && h == hash)
case source.Save:
// Make sure the version and content (if present) is the same.
if o.version != c.Version {
return errors.Errorf("updateOverlay: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
return -1, errors.Errorf("updateOverlay: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
}
if c.Text != nil && o.hash != hash {
return errors.Errorf("updateOverlay: overlay %s changed on save", c.URI)
return -1, errors.Errorf("updateOverlay: overlay %s changed on save", c.URI)
}
sameContentOnDisk = true
}
@ -98,7 +98,7 @@ func (s *session) updateOverlay(ctx context.Context, c source.FileModification)
hash: hash,
sameContentOnDisk: sameContentOnDisk,
}
return nil
return kind, nil
}
func (s *session) readOverlay(uri span.URI) *overlay {

View File

@ -256,7 +256,8 @@ func (s *session) DidModifyFile(ctx context.Context, c source.FileModification)
ctx = telemetry.URI.With(ctx, c.URI)
// Perform the session-specific updates.
if err := s.updateOverlay(ctx, c); err != nil {
kind, err := s.updateOverlay(ctx, c)
if err != nil {
return nil, err
}
@ -265,12 +266,11 @@ func (s *session) DidModifyFile(ctx context.Context, c source.FileModification)
if view.Ignore(c.URI) {
return nil, errors.Errorf("ignored file %v", c.URI)
}
// Set the content for the file, only for didChange and didClose events.
f, err := view.getFileLocked(ctx, c.URI)
if err != nil {
// Make sure to add the file to the view.
if _, err := view.getFileLocked(ctx, c.URI); err != nil {
return nil, err
}
snapshots = append(snapshots, view.invalidateContent(ctx, c.URI, f.kind, c.Action))
snapshots = append(snapshots, view.invalidateContent(ctx, c.URI, kind, c.Action))
}
return snapshots, nil
}
@ -283,12 +283,12 @@ func (s *session) IsOpen(uri span.URI) bool {
return open
}
func (s *session) GetFile(uri span.URI, kind source.FileKind) source.FileHandle {
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.GetFile(uri, kind)
return s.cache.GetFile(uri)
}
func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, action source.FileAction) bool {
@ -296,10 +296,12 @@ func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, action s
if err != nil {
return false
}
f, err := view.getFileLocked(ctx, uri)
if err != nil {
// Make sure that the file is part of the view.
if _, err := view.getFileLocked(ctx, uri); err != nil {
return false
}
view.invalidateContent(ctx, f.URI(), f.kind, action)
// TODO(golang/go#31553): Remove this when this issue has been resolved.
kind := source.DetectLanguage("", uri.Filename())
view.invalidateContent(ctx, uri, kind, action)
return true
}

View File

@ -8,6 +8,8 @@ import (
"context"
"io"
"os"
"path/filepath"
"strings"
"sync"
"golang.org/x/tools/go/analysis"
@ -92,7 +94,7 @@ func (s *snapshot) ModFiles(ctx context.Context) (source.FileHandle, source.File
return nil, nil, err
}
// Go directly to disk to get the correct FileHandle, since we just copied the file without invalidating the snapshot.
tempfh := s.view.Session().Cache().GetFile(span.FileURI(s.view.modfiles.temp), source.Mod)
tempfh := s.view.Session().Cache().GetFile(span.FileURI(s.view.modfiles.temp))
if tempfh == nil {
return nil, nil, errors.Errorf("temporary go.mod filehandle is nil")
}
@ -533,9 +535,7 @@ func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle
s.view.mu.Lock()
defer s.view.mu.Unlock()
// TODO(rstambler): Should there be a version that provides a kind explicitly?
kind := source.DetectLanguage("", uri.Filename())
f, err := s.view.getFile(ctx, uri, kind)
f, err := s.view.getFile(ctx, uri)
if err != nil {
return nil, err
}
@ -547,7 +547,7 @@ func (s *snapshot) getFileHandle(ctx context.Context, f *fileBase) source.FileHa
defer s.mu.Unlock()
if _, ok := s.files[f.URI()]; !ok {
s.files[f.URI()] = s.view.session.GetFile(f.URI(), f.kind)
s.files[f.URI()] = s.view.session.GetFile(f.URI())
}
return s.files[f.URI()]
}
@ -633,7 +633,7 @@ func (s *snapshot) clone(ctx context.Context, withoutURI span.URI, withoutFileKi
result.files[k] = v
}
// Handle the invalidated file; it may have new contents or not exist.
currentFH := s.view.session.GetFile(withoutURI, withoutFileKind)
currentFH := s.view.session.GetFile(withoutURI)
if _, _, err := currentFH.Read(ctx); os.IsNotExist(err) {
delete(result.files, withoutURI)
} else {
@ -680,6 +680,10 @@ func (s *snapshot) clone(ctx context.Context, withoutURI span.URI, withoutFileKi
return result
}
func dir(filename string) string {
return strings.ToLower(filepath.Dir(filename))
}
func (s *snapshot) ID() uint64 {
return s.id
}

View File

@ -96,6 +96,28 @@ type view struct {
initializationError error
}
// fileBase holds the common functionality for all files.
// It is intended to be embedded in the file implementations
type fileBase struct {
uris []span.URI
fname string
view *view
}
func (f *fileBase) URI() span.URI {
return f.uris[0]
}
func (f *fileBase) filename() string {
return f.fname
}
func (f *fileBase) addURI(uri span.URI) int {
f.uris = append(f.uris, uri)
return len(f.uris)
}
// modfiles holds the real and temporary go.mod files that are attributed to a view.
type modfiles struct {
real, temp string
@ -278,7 +300,7 @@ func (v *view) modFilesChanged() bool {
// and modules included by a replace directive. Return true if
// any of these file versions do not match.
for filename, version := range v.modFileVersions {
if version != v.fileVersion(filename, source.Mod) {
if version != v.fileVersion(filename) {
return true
}
}
@ -297,17 +319,101 @@ func (v *view) storeModFileVersions() {
// and modules included by a replace directive in the resolver.
for _, mod := range r.ModsByModPath {
if (mod.Main || mod.Replace != nil) && mod.GoMod != "" {
v.modFileVersions[mod.GoMod] = v.fileVersion(mod.GoMod, source.Mod)
v.modFileVersions[mod.GoMod] = v.fileVersion(mod.GoMod)
}
}
}
func (v *view) fileVersion(filename string, kind source.FileKind) string {
func (v *view) fileVersion(filename string) string {
uri := span.FileURI(filename)
fh := v.session.GetFile(uri, kind)
fh := v.session.GetFile(uri)
return fh.Identity().String()
}
func (v *view) mapFile(uri span.URI, f *fileBase) {
v.filesByURI[uri] = f
if f.addURI(uri) == 1 {
basename := basename(f.filename())
v.filesByBase[basename] = append(v.filesByBase[basename], f)
}
}
func basename(filename string) string {
return strings.ToLower(filepath.Base(filename))
}
// FindFile returns the file if the given URI is already a part of the view.
func (v *view) findFileLocked(ctx context.Context, uri span.URI) *fileBase {
v.mu.Lock()
defer v.mu.Unlock()
f, err := v.findFile(uri)
if err != nil {
return nil
}
return f
}
// getFileLocked returns a File for the given URI. It will always succeed because it
// adds the file to the managed set if needed.
func (v *view) getFileLocked(ctx context.Context, uri span.URI) (*fileBase, error) {
v.mu.Lock()
defer v.mu.Unlock()
return v.getFile(ctx, uri)
}
// getFile is the unlocked internal implementation of GetFile.
func (v *view) getFile(ctx context.Context, uri span.URI) (*fileBase, error) {
f, err := v.findFile(uri)
if err != nil {
return nil, err
} else if f != nil {
return f, nil
}
f = &fileBase{
view: v,
fname: uri.Filename(),
}
v.mapFile(uri, f)
return f, nil
}
// findFile checks the cache for any file matching the given uri.
//
// An error is only returned for an irreparable failure, for example, if the
// filename in question does not exist.
func (v *view) findFile(uri span.URI) (*fileBase, error) {
if f := v.filesByURI[uri]; f != nil {
// a perfect match
return f, nil
}
// no exact match stored, time to do some real work
// check for any files with the same basename
fname := uri.Filename()
basename := basename(fname)
if candidates := v.filesByBase[basename]; candidates != nil {
pathStat, err := os.Stat(fname)
if os.IsNotExist(err) {
return nil, err
}
if err != nil {
return nil, nil // the file may exist, return without an error
}
for _, c := range candidates {
if cStat, err := os.Stat(c.filename()); err == nil {
if os.SameFile(pathStat, cStat) {
// same file, map it
v.mapFile(uri, c)
return c, nil
}
}
}
}
// no file with a matching name was found, it wasn't in our cache
return nil, nil
}
func (v *view) Shutdown(ctx context.Context) {
v.session.removeView(ctx, v)
}
@ -427,94 +533,6 @@ func (v *view) cancelBackground() {
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
}
// FindFile returns the file if the given URI is already a part of the view.
func (v *view) findFileLocked(ctx context.Context, uri span.URI) *fileBase {
v.mu.Lock()
defer v.mu.Unlock()
f, err := v.findFile(uri)
if err != nil {
return nil
}
return f
}
// getFileLocked returns a File for the given URI. It will always succeed because it
// adds the file to the managed set if needed.
func (v *view) getFileLocked(ctx context.Context, uri span.URI) (*fileBase, error) {
v.mu.Lock()
defer v.mu.Unlock()
// TODO(rstambler): Should there be a version that provides a kind explicitly?
kind := source.DetectLanguage("", uri.Filename())
return v.getFile(ctx, uri, kind)
}
// getFile is the unlocked internal implementation of GetFile.
func (v *view) getFile(ctx context.Context, uri span.URI, kind source.FileKind) (*fileBase, error) {
f, err := v.findFile(uri)
if err != nil {
return nil, err
} else if f != nil {
return f, nil
}
f = &fileBase{
view: v,
fname: uri.Filename(),
kind: kind,
}
v.mapFile(uri, f)
return f, nil
}
// findFile checks the cache for any file matching the given uri.
//
// An error is only returned for an irreparable failure, for example, if the
// filename in question does not exist.
func (v *view) findFile(uri span.URI) (*fileBase, error) {
if f := v.filesByURI[uri]; f != nil {
// a perfect match
return f, nil
}
// no exact match stored, time to do some real work
// check for any files with the same basename
fname := uri.Filename()
basename := basename(fname)
if candidates := v.filesByBase[basename]; candidates != nil {
pathStat, err := os.Stat(fname)
if os.IsNotExist(err) {
return nil, err
}
if err != nil {
return nil, nil // the file may exist, return without an error
}
for _, c := range candidates {
if cStat, err := os.Stat(c.filename()); err == nil {
if os.SameFile(pathStat, cStat) {
// same file, map it
v.mapFile(uri, c)
return c, nil
}
}
}
}
// no file with a matching name was found, it wasn't in our cache
return nil, nil
}
func (f *fileBase) addURI(uri span.URI) int {
f.uris = append(f.uris, uri)
return len(f.uris)
}
func (v *view) mapFile(uri span.URI, f *fileBase) {
v.filesByURI[uri] = f
if f.addURI(uri) == 1 {
basename := basename(f.filename())
v.filesByBase[basename] = append(v.filesByBase[basename], f)
}
}
func (v *view) FindPosInPackage(searchpkg source.Package, pos token.Pos) (*ast.File, source.Package, error) {
tok := v.session.cache.fset.File(pos)
if tok == nil {

View File

@ -235,7 +235,7 @@ type Cache interface {
// FileSystem is the interface to something that provides file contents.
type FileSystem interface {
// GetFile returns a handle for the specified file.
GetFile(uri span.URI, kind FileKind) FileHandle
GetFile(uri span.URI) FileHandle
}
// ParseGoHandle represents a handle to the AST for a file.

View File

@ -144,7 +144,7 @@ func (s *Server) changedText(ctx context.Context, uri span.URI, changes []protoc
}
func (s *Server) applyIncrementalChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
content, _, err := s.session.GetFile(uri, source.UnknownKind).Read(ctx)
content, _, err := s.session.GetFile(uri).Read(ctx)
if err != nil {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err)
}