internal/lsp: remove source.FileContent
On FileHandle Read now just returns the data hash and error This makes it more obvious that you should handle the error, rather than hiding it all in a struct. We also change the way we get and return content, the main source.File constructs now hold a FileHandle that then updates on invalidation Change-Id: I20be1b995355e948244342130eafec056df10081 Reviewed-on: https://go-review.googlesource.com/c/tools/+/180417 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
596a85b56b
commit
4d9ae51c24
|
@ -41,16 +41,14 @@ func (h *nativeFileHandle) Identity() source.FileIdentity {
|
||||||
return h.identity
|
return h.identity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *nativeFileHandle) Read(ctx context.Context) *source.FileContent {
|
func (h *nativeFileHandle) Read(ctx context.Context) ([]byte, string, error) {
|
||||||
r := &source.FileContent{}
|
|
||||||
filename, err := h.identity.URI.Filename()
|
filename, err := h.identity.URI.Filename()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Error = err
|
return nil, "", err
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
r.Data, r.Error = ioutil.ReadFile(filename)
|
data, err := ioutil.ReadFile(filename)
|
||||||
if r.Error != nil {
|
if err != nil {
|
||||||
r.Hash = hashContents(r.Data)
|
return nil, "", err
|
||||||
}
|
}
|
||||||
return r
|
return data, hashContents(data), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
|
@ -28,10 +29,10 @@ type fileBase struct {
|
||||||
uris []span.URI
|
uris []span.URI
|
||||||
fname string
|
fname string
|
||||||
|
|
||||||
view *view
|
view *view
|
||||||
fh source.FileHandle
|
handleMu sync.Mutex
|
||||||
fc *source.FileContent
|
handle source.FileHandle
|
||||||
token *token.File
|
token *token.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func basename(filename string) string {
|
func basename(filename string) string {
|
||||||
|
@ -51,32 +52,16 @@ func (f *fileBase) View() source.View {
|
||||||
return f.view
|
return f.view
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content returns the contents of the file, reading it from file system if needed.
|
// Content returns a handle for the contents of the file.
|
||||||
func (f *fileBase) Content(ctx context.Context) *source.FileContent {
|
func (f *fileBase) Handle(ctx context.Context) source.FileHandle {
|
||||||
f.view.mu.Lock()
|
f.handleMu.Lock()
|
||||||
defer f.view.mu.Unlock()
|
defer f.handleMu.Unlock()
|
||||||
|
if f.handle == nil {
|
||||||
f.read(ctx)
|
f.handle = f.view.Session().GetFile(f.URI())
|
||||||
return f.fc
|
}
|
||||||
|
return f.handle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileBase) FileSet() *token.FileSet {
|
func (f *fileBase) FileSet() *token.FileSet {
|
||||||
return f.view.Session().Cache().FileSet()
|
return f.view.Session().Cache().FileSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
// read is the internal part of GetContent. It assumes that the caller is
|
|
||||||
// holding the mutex of the file's view.
|
|
||||||
func (f *fileBase) read(ctx context.Context) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
f.fc = &source.FileContent{Error: err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
oldFH := f.fh
|
|
||||||
f.fh = f.view.Session().GetFile(f.URI())
|
|
||||||
// do we already have the right contents?
|
|
||||||
if f.fc != nil && f.fh.Identity() == oldFH.Identity() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// update the contents
|
|
||||||
f.fc = f.fh.Read(ctx)
|
|
||||||
}
|
|
||||||
|
|
|
@ -90,11 +90,11 @@ func (v *view) reparseImports(ctx context.Context, f *goFile) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Get file content in case we don't already have it.
|
// Get file content in case we don't already have it.
|
||||||
f.read(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if f.fc.Error != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
parsed, _ := parser.ParseFile(f.FileSet(), f.filename(), f.fc.Data, parser.ImportsOnly)
|
parsed, _ := parser.ParseFile(f.FileSet(), f.filename(), data, parser.ImportsOnly)
|
||||||
if parsed == nil {
|
if parsed == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,11 @@ func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*a
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't have a cached AST for this file, so we read its content and parse it.
|
// We don't have a cached AST for this file, so we read its content and parse it.
|
||||||
gof.read(imp.ctx)
|
data, _, err := gof.Handle(imp.ctx).Read(imp.ctx)
|
||||||
if gof.fc.Error != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
src := gof.fc.Data
|
src := data
|
||||||
if src == nil {
|
if src == nil {
|
||||||
parsed[i], errors[i] = nil, fmt.Errorf("no source for %v", filename)
|
parsed[i], errors[i] = nil, fmt.Errorf("no source for %v", filename)
|
||||||
return
|
return
|
||||||
|
|
|
@ -40,7 +40,8 @@ type session struct {
|
||||||
type overlay struct {
|
type overlay struct {
|
||||||
session *session
|
session *session
|
||||||
uri span.URI
|
uri span.URI
|
||||||
content source.FileContent
|
data []byte
|
||||||
|
hash string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) Shutdown(ctx context.Context) {
|
func (s *session) Shutdown(ctx context.Context) {
|
||||||
|
@ -213,10 +214,8 @@ func (s *session) SetOverlay(uri span.URI, data []byte) {
|
||||||
s.overlays[uri] = &overlay{
|
s.overlays[uri] = &overlay{
|
||||||
session: s,
|
session: s,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
content: source.FileContent{
|
data: data,
|
||||||
Data: data,
|
hash: hashContents(data),
|
||||||
Hash: hashContents(data),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,11 +236,8 @@ func (s *session) buildOverlay() map[string][]byte {
|
||||||
|
|
||||||
overlays := make(map[string][]byte)
|
overlays := make(map[string][]byte)
|
||||||
for uri, overlay := range s.overlays {
|
for uri, overlay := range s.overlays {
|
||||||
if overlay.content.Error != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if filename, err := uri.Filename(); err == nil {
|
if filename, err := uri.Filename(); err == nil {
|
||||||
overlays[filename] = overlay.content.Data
|
overlays[filename] = overlay.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return overlays
|
return overlays
|
||||||
|
@ -254,12 +250,12 @@ func (o *overlay) FileSystem() source.FileSystem {
|
||||||
func (o *overlay) Identity() source.FileIdentity {
|
func (o *overlay) Identity() source.FileIdentity {
|
||||||
return source.FileIdentity{
|
return source.FileIdentity{
|
||||||
URI: o.uri,
|
URI: o.uri,
|
||||||
Version: o.content.Hash,
|
Version: o.hash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *overlay) Read(ctx context.Context) *source.FileContent {
|
func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
|
||||||
return &o.content
|
return o.data, o.hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type debugSession struct{ *session }
|
type debugSession struct{ *session }
|
||||||
|
@ -287,9 +283,9 @@ func (s debugSession) Files() []*debug.File {
|
||||||
seen[overlay.uri] = f
|
seen[overlay.uri] = f
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
f.Data = string(overlay.content.Data)
|
f.Data = string(overlay.data)
|
||||||
f.Error = overlay.content.Error
|
f.Error = nil
|
||||||
f.Hash = overlay.content.Hash
|
f.Hash = overlay.hash
|
||||||
}
|
}
|
||||||
sort.Slice(files, func(i int, j int) bool {
|
sort.Slice(files, func(i int, j int) bool {
|
||||||
return files[i].URI < files[j].URI
|
return files[i].URI < files[j].URI
|
||||||
|
@ -301,13 +297,13 @@ func (s debugSession) File(hash string) *debug.File {
|
||||||
s.overlayMu.Lock()
|
s.overlayMu.Lock()
|
||||||
defer s.overlayMu.Unlock()
|
defer s.overlayMu.Unlock()
|
||||||
for _, overlay := range s.overlays {
|
for _, overlay := range s.overlays {
|
||||||
if overlay.content.Hash == hash {
|
if overlay.hash == hash {
|
||||||
return &debug.File{
|
return &debug.File{
|
||||||
Session: s,
|
Session: s,
|
||||||
URI: overlay.uri,
|
URI: overlay.uri,
|
||||||
Data: string(overlay.content.Data),
|
Data: string(overlay.data),
|
||||||
Error: overlay.content.Error,
|
Error: nil,
|
||||||
Hash: overlay.content.Hash,
|
Hash: overlay.hash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,7 +233,7 @@ func (f *goFile) invalidate() {
|
||||||
if f.pkg != nil {
|
if f.pkg != nil {
|
||||||
f.view.remove(f.pkg.pkgPath, map[string]struct{}{})
|
f.view.remove(f.pkg.pkgPath, map[string]struct{}{})
|
||||||
}
|
}
|
||||||
f.fc = nil
|
f.handle = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove invalidates a package and its reverse dependencies in the view's
|
// remove invalidates a package and its reverse dependencies in the view's
|
||||||
|
|
|
@ -202,8 +202,8 @@ func pointToSpan(ctx context.Context, v View, spn span.Span) span.Span {
|
||||||
v.Session().Logger().Errorf(ctx, "Could not find tokens for diagnostic: %v", spn.URI())
|
v.Session().Logger().Errorf(ctx, "Could not find tokens for diagnostic: %v", spn.URI())
|
||||||
return spn
|
return spn
|
||||||
}
|
}
|
||||||
fc := diagFile.Content(ctx)
|
data, _, err := diagFile.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
v.Session().Logger().Errorf(ctx, "Could not find content for diagnostic: %v", spn.URI())
|
v.Session().Logger().Errorf(ctx, "Could not find content for diagnostic: %v", spn.URI())
|
||||||
return spn
|
return spn
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ func pointToSpan(ctx context.Context, v View, spn span.Span) span.Span {
|
||||||
}
|
}
|
||||||
start := s.Start()
|
start := s.Start()
|
||||||
offset := start.Offset()
|
offset := start.Offset()
|
||||||
width := bytes.IndexAny(fc.Data[offset:], " \n,():;[]")
|
width := bytes.IndexAny(data[offset:], " \n,():;[]")
|
||||||
if width <= 0 {
|
if width <= 0 {
|
||||||
return spn
|
return spn
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,15 +56,15 @@ func hasParseErrors(errors []packages.Error) bool {
|
||||||
|
|
||||||
// Imports formats a file using the goimports tool.
|
// Imports formats a file using the goimports tool.
|
||||||
func Imports(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) {
|
func Imports(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) {
|
||||||
fc := f.Content(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
return nil, fc.Error
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken(ctx)
|
tok := f.GetToken(ctx)
|
||||||
if tok == nil {
|
if tok == nil {
|
||||||
return nil, fmt.Errorf("no token file for %s", f.URI())
|
return nil, fmt.Errorf("no token file for %s", f.URI())
|
||||||
}
|
}
|
||||||
formatted, err := imports.Process(tok.Name(), fc.Data, nil)
|
formatted, err := imports.Process(tok.Name(), data, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -72,12 +72,12 @@ func Imports(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
|
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
|
||||||
fc := file.Content(ctx)
|
data, _, err := file.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
file.View().Session().Logger().Errorf(ctx, "Cannot compute text edits: %v", fc.Error)
|
file.View().Session().Logger().Errorf(ctx, "Cannot compute text edits: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
u := diff.SplitLines(string(fc.Data))
|
u := diff.SplitLines(string(data))
|
||||||
f := diff.SplitLines(formatted)
|
f := diff.SplitLines(formatted)
|
||||||
return DiffToEdits(file.URI(), diff.Operations(u, f))
|
return DiffToEdits(file.URI(), diff.Operations(u, f))
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,12 +296,12 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(edits)
|
ops := source.EditsToDiff(edits)
|
||||||
fc := f.Content(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(fc.Data)), ops), "")
|
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
|
||||||
if gofmted != got {
|
if gofmted != got {
|
||||||
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
||||||
}
|
}
|
||||||
|
@ -337,12 +337,12 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(edits)
|
ops := source.EditsToDiff(edits)
|
||||||
fc := f.Content(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(fc.Data)), ops), "")
|
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
|
||||||
if goimported != got {
|
if goimported != got {
|
||||||
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,6 @@ import (
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileContent is returned from FileSystem implementation to represent the
|
|
||||||
// contents of a file.
|
|
||||||
type FileContent struct {
|
|
||||||
Data []byte
|
|
||||||
Error error
|
|
||||||
Hash string
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileIdentity uniquely identifies a file at a version from a FileSystem.
|
// FileIdentity uniquely identifies a file at a version from a FileSystem.
|
||||||
type FileIdentity struct {
|
type FileIdentity struct {
|
||||||
URI span.URI
|
URI span.URI
|
||||||
|
@ -35,14 +27,14 @@ type FileIdentity struct {
|
||||||
// FileHandle represents a handle to a specific version of a single file from
|
// FileHandle represents a handle to a specific version of a single file from
|
||||||
// a specific file system.
|
// a specific file system.
|
||||||
type FileHandle interface {
|
type FileHandle interface {
|
||||||
// FileSystem returns the file system this handle was aquired from.
|
// FileSystem returns the file system this handle was acquired from.
|
||||||
FileSystem() FileSystem
|
FileSystem() FileSystem
|
||||||
// Return the Identity for the file.
|
// Return the Identity for the file.
|
||||||
Identity() FileIdentity
|
Identity() FileIdentity
|
||||||
// Read reads the contents of a file and returns it.
|
// Read reads the contents of a file and returns it along with its hash
|
||||||
// If the file is not available, the returned FileContent will have no
|
// value.
|
||||||
// data and an error.
|
// If the file is not available, retruns a nil slice and an error.
|
||||||
Read(ctx context.Context) *FileContent
|
Read(ctx context.Context) ([]byte, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileSystem is the interface to something that provides file contents.
|
// FileSystem is the interface to something that provides file contents.
|
||||||
|
@ -161,7 +153,7 @@ type View interface {
|
||||||
type File interface {
|
type File interface {
|
||||||
URI() span.URI
|
URI() span.URI
|
||||||
View() View
|
View() View
|
||||||
Content(ctx context.Context) *FileContent
|
Handle(ctx context.Context) FileHandle
|
||||||
FileSet() *token.FileSet
|
FileSet() *token.FileSet
|
||||||
GetToken(ctx context.Context) *token.File
|
GetToken(ctx context.Context) *token.File
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,11 +66,10 @@ func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTex
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := span.NewURI(params.TextDocument.URI)
|
uri := span.NewURI(params.TextDocument.URI)
|
||||||
fc := s.session.GetFile(uri).Read(ctx)
|
content, _, err := s.session.GetFile(uri).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||||
}
|
}
|
||||||
content := fc.Data
|
|
||||||
fset := s.session.Cache().FileSet()
|
fset := s.session.Cache().FileSet()
|
||||||
filename, err := uri.Filename()
|
filename, err := uri.Filename()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -22,11 +22,11 @@ func getSourceFile(ctx context.Context, v source.View, uri span.URI) (source.Fil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
fc := f.Content(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if fc.Error != nil {
|
if err != nil {
|
||||||
return nil, nil, fc.Error
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
m := protocol.NewColumnMapper(f.URI(), filename, f.FileSet(), f.GetToken(ctx), fc.Data)
|
m := protocol.NewColumnMapper(f.URI(), filename, f.FileSet(), f.GetToken(ctx), data)
|
||||||
|
|
||||||
return f, m, nil
|
return f, m, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue