diff --git a/internal/lsp/format.go b/internal/lsp/format.go index 9bfdb65975..b5ce2f24cc 100644 --- a/internal/lsp/format.go +++ b/internal/lsp/format.go @@ -15,7 +15,7 @@ import ( // formatRange formats a document with a given range. func formatRange(v *source.View, uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) { - data, err := v.ReadActiveFile(uri) + data, err := v.GetFile(uri).Read() if err != nil { return nil, err } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 63c403e169..2199ed358b 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -111,7 +111,7 @@ func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDo } func (s *server) cacheAndDiagnoseFile(ctx context.Context, uri protocol.DocumentURI, text string) { - s.view.SetActiveFileContent(uri, []byte(text)) + s.view.GetFile(uri).SetContent([]byte(text)) go func() { reports, err := diagnostics(s.view, uri) if err == nil { @@ -139,7 +139,7 @@ func (s *server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) e } func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { - s.view.ClearActiveFile(params.TextDocument.URI) + s.view.GetFile(params.TextDocument.URI).SetContent(nil) return nil } diff --git a/internal/lsp/source/file.go b/internal/lsp/source/file.go new file mode 100644 index 0000000000..2d8a4f57c8 --- /dev/null +++ b/internal/lsp/source/file.go @@ -0,0 +1,76 @@ +// 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 source + +import ( + "go/ast" + "go/token" + "io/ioutil" + + "golang.org/x/tools/internal/lsp/protocol" +) + +// File holds all the information we know about a file. +type File struct { + URI protocol.DocumentURI + view *View + active bool + content []byte + ast *ast.File + token *token.File +} + +// SetContent sets the overlay contents for a file. +// Setting it to nil will revert it to the on disk contents, and remove it +// from the active set. +func (f *File) SetContent(content []byte) { + f.view.mu.Lock() + defer f.view.mu.Unlock() + f.content = content + // the ast and token fields are invalid + f.ast = nil + f.token = nil + // and we might need to update the overlay + switch { + case f.active && content == nil: + // we were active, and want to forget the content + f.active = false + if filename, err := FromURI(f.URI); err == nil { + delete(f.view.Config.Overlay, filename) + } + f.content = nil + case content != nil: + // an active overlay, update the map + f.active = true + if filename, err := FromURI(f.URI); err == nil { + f.view.Config.Overlay[filename] = f.content + } + } +} + +// Read returns the contents of the file, reading it from file system if needed. +func (f *File) Read() ([]byte, error) { + f.view.mu.Lock() + defer f.view.mu.Unlock() + return f.read() +} + +// read is the internal part of Read that presumes the lock is already held +func (f *File) read() ([]byte, error) { + if f.content != nil { + return f.content, nil + } + // we don't know the content yet, so read it + filename, err := FromURI(f.URI) + if err != nil { + return nil, err + } + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + f.content = content + return f.content, nil +} diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 27fb61b75a..01332f830c 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -14,67 +14,41 @@ import ( ) type View struct { + mu sync.Mutex // protects all mutable state of the view + Config *packages.Config - activeFilesMu sync.Mutex - activeFiles map[protocol.DocumentURI][]byte - - fset *token.FileSet + files map[protocol.DocumentURI]*File } func NewView() *View { - fset := token.NewFileSet() return &View{ Config: &packages.Config{ Mode: packages.LoadSyntax, - Fset: fset, + Fset: token.NewFileSet(), Tests: true, }, - activeFiles: make(map[protocol.DocumentURI][]byte), - fset: fset, + files: make(map[protocol.DocumentURI]*File), } } -func (v *View) overlay() map[string][]byte { - over := make(map[string][]byte) - - v.activeFilesMu.Lock() - defer v.activeFilesMu.Unlock() - - for uri, content := range v.activeFiles { - filename, err := FromURI(uri) - if err == nil { - over[filename] = content - } +// GetFile returns a File for the given uri. +// It will always succeed, adding the file to the managed set if needed. +func (v *View) GetFile(uri protocol.DocumentURI) *File { + v.mu.Lock() + f, found := v.files[uri] + if !found { + f := &File{URI: uri} + v.files[f.URI] = f } - return over -} - -func (v *View) SetActiveFileContent(uri protocol.DocumentURI, content []byte) { - v.activeFilesMu.Lock() - v.activeFiles[uri] = content - v.activeFilesMu.Unlock() -} - -func (v *View) ReadActiveFile(uri protocol.DocumentURI) ([]byte, error) { - v.activeFilesMu.Lock() - content, ok := v.activeFiles[uri] - v.activeFilesMu.Unlock() - if !ok { - return nil, fmt.Errorf("uri not found: %s", uri) - } - return content, nil -} - -func (v *View) ClearActiveFile(uri protocol.DocumentURI) { - v.activeFilesMu.Lock() - delete(v.activeFiles, uri) - v.activeFilesMu.Unlock() + v.mu.Unlock() + return f } // TypeCheck type-checks the package for the given package path. func (v *View) TypeCheck(uri protocol.DocumentURI) (*packages.Package, error) { - v.Config.Overlay = v.overlay() + v.mu.Lock() + defer v.mu.Unlock() path, err := FromURI(uri) if err != nil { return nil, err