зеркало из https://github.com/golang/tools.git
internal/lsp: add cache for type information
This change adds an additional cache for type information, which here is just a *packages.Package for each package. The metadata cache maintains the import graph, which allows us to easily determine when a package X (and therefore any other package that imports X) should be invalidated. Additionally, rather than performing content changes as they happen, we queue up content changes and apply them the next time that any type information is requested. Updates golang/go#30309 Change-Id: Iaf569f641f84ce69b0c0d5bdabbaa85635eeb8bf Reviewed-on: https://go-review.googlesource.com/c/tools/+/165438 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Родитель
b40df0fb21
Коммит
00c44ba9c1
|
@ -1,6 +1,7 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
|
@ -17,19 +18,29 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/source"
|
||||
)
|
||||
|
||||
func (v *View) parse(uri source.URI) error {
|
||||
func (v *View) parse(ctx context.Context, uri source.URI) error {
|
||||
v.mcache.mu.Lock()
|
||||
defer v.mcache.mu.Unlock()
|
||||
|
||||
// Apply any queued-up content changes.
|
||||
if err := v.applyContentChanges(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := v.files[uri]
|
||||
|
||||
// This should never happen.
|
||||
if f == nil {
|
||||
return fmt.Errorf("no file for %v", uri)
|
||||
}
|
||||
|
||||
imp, err := v.checkMetadata(f)
|
||||
if err != nil {
|
||||
// If the package for the file has not been invalidated by the application
|
||||
// of the pending changes, there is no need to continue.
|
||||
if f.isPopulated() {
|
||||
return nil
|
||||
}
|
||||
// Check if the file's imports have changed. If they have, update the
|
||||
// metadata by calling packages.Load.
|
||||
if err := v.checkMetadata(ctx, f); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.meta == nil {
|
||||
|
@ -37,10 +48,10 @@ func (v *View) parse(uri source.URI) error {
|
|||
}
|
||||
// Start prefetching direct imports.
|
||||
for importPath := range f.meta.children {
|
||||
go imp.Import(importPath)
|
||||
go v.Import(importPath)
|
||||
}
|
||||
// Type-check package.
|
||||
pkg, err := imp.typeCheck(f.meta.pkgPath)
|
||||
pkg, err := v.typeCheck(f.meta.pkgPath)
|
||||
if pkg == nil || pkg.Types == nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,29 +86,12 @@ func (v *View) cachePackage(pkg *packages.Package) {
|
|||
}
|
||||
}
|
||||
|
||||
type importer struct {
|
||||
mu sync.Mutex
|
||||
entries map[string]*entry
|
||||
|
||||
*View
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
pkg *packages.Package
|
||||
err error
|
||||
ready chan struct{}
|
||||
}
|
||||
|
||||
func (v *View) checkMetadata(f *File) (*importer, error) {
|
||||
func (v *View) checkMetadata(ctx context.Context, f *File) error {
|
||||
filename, err := f.URI.Filename()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
imp := &importer{
|
||||
View: v,
|
||||
entries: make(map[string]*entry),
|
||||
}
|
||||
if v.reparseImports(f, filename) {
|
||||
if v.reparseImports(ctx, f, filename) {
|
||||
cfg := v.Config
|
||||
cfg.Mode = packages.LoadImports
|
||||
pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", filename))
|
||||
|
@ -105,7 +99,7 @@ func (v *View) checkMetadata(f *File) (*importer, error) {
|
|||
if err == nil {
|
||||
err = fmt.Errorf("no packages found for %s", filename)
|
||||
}
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
// If the package comes back with errors from `go list`, don't bother
|
||||
|
@ -113,23 +107,23 @@ func (v *View) checkMetadata(f *File) (*importer, error) {
|
|||
for _, err := range pkg.Errors {
|
||||
switch err.Kind {
|
||||
case packages.UnknownError, packages.ListError:
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
v.link(pkg.PkgPath, pkg, nil)
|
||||
}
|
||||
}
|
||||
return imp, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// reparseImports reparses a file's import declarations to determine if they
|
||||
// have changed.
|
||||
func (v *View) reparseImports(f *File, filename string) bool {
|
||||
func (v *View) reparseImports(ctx context.Context, f *File, filename string) bool {
|
||||
if f.meta == nil {
|
||||
return true
|
||||
}
|
||||
// Get file content in case we don't already have it?
|
||||
f.read()
|
||||
f.read(ctx)
|
||||
parsed, _ := parser.ParseFile(v.Config.Fset, filename, f.content, parser.ImportsOnly)
|
||||
if parsed == nil {
|
||||
return true
|
||||
|
@ -186,23 +180,23 @@ func (v *View) link(pkgPath string, pkg *packages.Package, parent *metadata) *me
|
|||
return m
|
||||
}
|
||||
|
||||
func (imp *importer) Import(pkgPath string) (*types.Package, error) {
|
||||
imp.mu.Lock()
|
||||
e, ok := imp.entries[pkgPath]
|
||||
func (v *View) Import(pkgPath string) (*types.Package, error) {
|
||||
v.pcache.mu.Lock()
|
||||
e, ok := v.pcache.packages[pkgPath]
|
||||
if ok {
|
||||
// cache hit
|
||||
imp.mu.Unlock()
|
||||
v.pcache.mu.Unlock()
|
||||
// wait for entry to become ready
|
||||
<-e.ready
|
||||
} else {
|
||||
// cache miss
|
||||
e = &entry{ready: make(chan struct{})}
|
||||
imp.entries[pkgPath] = e
|
||||
imp.mu.Unlock()
|
||||
v.pcache.packages[pkgPath] = e
|
||||
v.pcache.mu.Unlock()
|
||||
|
||||
// This goroutine becomes responsible for populating
|
||||
// the entry and broadcasting its readiness.
|
||||
e.pkg, e.err = imp.typeCheck(pkgPath)
|
||||
e.pkg, e.err = v.typeCheck(pkgPath)
|
||||
close(e.ready)
|
||||
}
|
||||
if e.err != nil {
|
||||
|
@ -211,8 +205,8 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
|
|||
return e.pkg.Types, nil
|
||||
}
|
||||
|
||||
func (imp *importer) typeCheck(pkgPath string) (*packages.Package, error) {
|
||||
meta, ok := imp.mcache.packages[pkgPath]
|
||||
func (v *View) typeCheck(pkgPath string) (*packages.Package, error) {
|
||||
meta, ok := v.mcache.packages[pkgPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no metadata for %v", pkgPath)
|
||||
}
|
||||
|
@ -229,7 +223,7 @@ func (imp *importer) typeCheck(pkgPath string) (*packages.Package, error) {
|
|||
PkgPath: meta.pkgPath,
|
||||
CompiledGoFiles: meta.files,
|
||||
Imports: make(map[string]*packages.Package),
|
||||
Fset: imp.Config.Fset,
|
||||
Fset: v.Config.Fset,
|
||||
Types: typ,
|
||||
TypesInfo: &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
|
@ -243,24 +237,35 @@ func (imp *importer) typeCheck(pkgPath string) (*packages.Package, error) {
|
|||
TypesSizes: &types.StdSizes{},
|
||||
}
|
||||
appendError := func(err error) {
|
||||
imp.appendPkgError(pkg, err)
|
||||
v.appendPkgError(pkg, err)
|
||||
}
|
||||
files, errs := imp.parseFiles(pkg.CompiledGoFiles)
|
||||
files, errs := v.parseFiles(meta.files)
|
||||
for _, err := range errs {
|
||||
appendError(err)
|
||||
}
|
||||
pkg.Syntax = files
|
||||
cfg := &types.Config{
|
||||
Error: appendError,
|
||||
Importer: imp,
|
||||
Importer: v,
|
||||
}
|
||||
check := types.NewChecker(cfg, imp.Config.Fset, pkg.Types, pkg.TypesInfo)
|
||||
check := types.NewChecker(cfg, v.Config.Fset, pkg.Types, pkg.TypesInfo)
|
||||
check.Files(pkg.Syntax)
|
||||
|
||||
// Set imports of package to correspond to cached packages. This is
|
||||
// necessary for go/analysis, but once we merge its approach with the
|
||||
// current caching system, we can eliminate this.
|
||||
v.pcache.mu.Lock()
|
||||
for importPath := range meta.children {
|
||||
if importEntry, ok := v.pcache.packages[importPath]; ok {
|
||||
pkg.Imports[importPath] = importEntry.pkg
|
||||
}
|
||||
}
|
||||
v.pcache.mu.Unlock()
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func (imp *importer) appendPkgError(pkg *packages.Package, err error) {
|
||||
func (v *View) appendPkgError(pkg *packages.Package, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
@ -283,7 +288,7 @@ func (imp *importer) appendPkgError(pkg *packages.Package, err error) {
|
|||
}
|
||||
case types.Error:
|
||||
errs = append(errs, packages.Error{
|
||||
Pos: imp.Config.Fset.Position(err.Pos).String(),
|
||||
Pos: v.Config.Fset.Position(err.Pos).String(),
|
||||
Msg: err.Msg,
|
||||
Kind: packages.TypeError,
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
|
@ -27,44 +28,51 @@ type File struct {
|
|||
}
|
||||
|
||||
// GetContent returns the contents of the file, reading it from file system if needed.
|
||||
func (f *File) GetContent() []byte {
|
||||
func (f *File) GetContent(ctx context.Context) []byte {
|
||||
f.view.mu.Lock()
|
||||
defer f.view.mu.Unlock()
|
||||
f.read()
|
||||
|
||||
if ctx.Err() == nil {
|
||||
f.read(ctx)
|
||||
}
|
||||
|
||||
return f.content
|
||||
}
|
||||
|
||||
func (f *File) GetFileSet() *token.FileSet {
|
||||
func (f *File) GetFileSet(ctx context.Context) *token.FileSet {
|
||||
return f.view.Config.Fset
|
||||
}
|
||||
|
||||
func (f *File) GetToken() *token.File {
|
||||
func (f *File) GetToken(ctx context.Context) *token.File {
|
||||
f.view.mu.Lock()
|
||||
defer f.view.mu.Unlock()
|
||||
if f.token == nil {
|
||||
if err := f.view.parse(f.URI); err != nil {
|
||||
|
||||
if f.token == nil || len(f.view.contentChanges) > 0 {
|
||||
if err := f.view.parse(ctx, f.URI); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return f.token
|
||||
}
|
||||
|
||||
func (f *File) GetAST() *ast.File {
|
||||
func (f *File) GetAST(ctx context.Context) *ast.File {
|
||||
f.view.mu.Lock()
|
||||
defer f.view.mu.Unlock()
|
||||
if f.ast == nil {
|
||||
if err := f.view.parse(f.URI); err != nil {
|
||||
|
||||
if f.ast == nil || len(f.view.contentChanges) > 0 {
|
||||
if err := f.view.parse(ctx, f.URI); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return f.ast
|
||||
}
|
||||
|
||||
func (f *File) GetPackage() *packages.Package {
|
||||
func (f *File) GetPackage(ctx context.Context) *packages.Package {
|
||||
f.view.mu.Lock()
|
||||
defer f.view.mu.Unlock()
|
||||
if f.pkg == nil {
|
||||
if err := f.view.parse(f.URI); err != nil {
|
||||
|
||||
if f.pkg == nil || len(f.view.contentChanges) > 0 {
|
||||
if err := f.view.parse(ctx, f.URI); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +81,19 @@ func (f *File) GetPackage() *packages.Package {
|
|||
|
||||
// read is the internal part of GetContent. It assumes that the caller is
|
||||
// holding the mutex of the file's view.
|
||||
func (f *File) read() {
|
||||
func (f *File) read(ctx context.Context) {
|
||||
if f.content != nil {
|
||||
return
|
||||
if len(f.view.contentChanges) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
f.view.mcache.mu.Lock()
|
||||
err := f.view.applyContentChanges(ctx)
|
||||
f.view.mcache.mu.Unlock()
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// We don't know the content yet, so read it.
|
||||
filename, err := f.URI.Filename()
|
||||
|
@ -88,3 +106,8 @@ func (f *File) read() {
|
|||
}
|
||||
f.content = content
|
||||
}
|
||||
|
||||
// isPopulated returns true if all of the computed fields of the file are set.
|
||||
func (f *File) isPopulated() bool {
|
||||
return f.ast != nil && f.token != nil && f.pkg != nil && f.meta != nil && f.imports != nil
|
||||
}
|
||||
|
|
|
@ -17,6 +17,14 @@ type View struct {
|
|||
// mu protects all mutable state of the view.
|
||||
mu sync.Mutex
|
||||
|
||||
// backgroundCtx is the current context used by background tasks initiated
|
||||
// by the view.
|
||||
backgroundCtx context.Context
|
||||
|
||||
// cancel is called when all action being performed by the current view
|
||||
// should be stopped.
|
||||
cancel context.CancelFunc
|
||||
|
||||
// Config is the configuration used for the view's interaction with the
|
||||
// go/packages API. It is shared across all views.
|
||||
Config packages.Config
|
||||
|
@ -24,9 +32,18 @@ type View struct {
|
|||
// files caches information for opened files in a view.
|
||||
files map[source.URI]*File
|
||||
|
||||
// contentChanges saves the content changes for a given state of the view.
|
||||
// When type information is requested by the view, all of the dirty changes
|
||||
// are applied, potentially invalidating some data in the caches. The
|
||||
// closures in the dirty slice assume that their caller is holding the
|
||||
// view's mutex.
|
||||
contentChanges map[source.URI]func()
|
||||
|
||||
// mcache caches metadata for the packages of the opened files in a view.
|
||||
mcache *metadataCache
|
||||
|
||||
pcache *packageCache
|
||||
|
||||
analysisCache *source.AnalysisCache
|
||||
}
|
||||
|
||||
|
@ -41,16 +58,42 @@ type metadata struct {
|
|||
parents, children map[string]bool
|
||||
}
|
||||
|
||||
type packageCache struct {
|
||||
mu sync.Mutex
|
||||
packages map[string]*entry
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
pkg *packages.Package
|
||||
err error
|
||||
ready chan struct{} // closed to broadcast ready condition
|
||||
}
|
||||
|
||||
func NewView(config *packages.Config) *View {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &View{
|
||||
Config: *config,
|
||||
files: make(map[source.URI]*File),
|
||||
backgroundCtx: ctx,
|
||||
cancel: cancel,
|
||||
Config: *config,
|
||||
files: make(map[source.URI]*File),
|
||||
contentChanges: make(map[source.URI]func()),
|
||||
mcache: &metadataCache{
|
||||
packages: make(map[string]*metadata),
|
||||
},
|
||||
pcache: &packageCache{
|
||||
packages: make(map[string]*entry),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) BackgroundContext() context.Context {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
return v.backgroundCtx
|
||||
}
|
||||
|
||||
func (v *View) FileSet() *token.FileSet {
|
||||
return v.Config.Fset
|
||||
}
|
||||
|
@ -60,45 +103,57 @@ func (v *View) GetAnalysisCache() *source.AnalysisCache {
|
|||
return v.analysisCache
|
||||
}
|
||||
|
||||
func (v *View) copyView() *View {
|
||||
return &View{
|
||||
Config: v.Config,
|
||||
files: make(map[source.URI]*File),
|
||||
mcache: v.mcache,
|
||||
}
|
||||
}
|
||||
|
||||
// SetContent sets the overlay contents for a file. A nil content value will
|
||||
// remove the file from the active set and revert it to its on-disk contents.
|
||||
func (v *View) SetContent(ctx context.Context, uri source.URI, content []byte) (source.View, error) {
|
||||
// SetContent sets the overlay contents for a file.
|
||||
func (v *View) SetContent(ctx context.Context, uri source.URI, content []byte) error {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
newView := v.copyView()
|
||||
// Cancel all still-running previous requests, since they would be
|
||||
// operating on stale data.
|
||||
v.cancel()
|
||||
v.backgroundCtx, v.cancel = context.WithCancel(context.Background())
|
||||
|
||||
for fURI, f := range v.files {
|
||||
newView.files[fURI] = &File{
|
||||
URI: fURI,
|
||||
view: newView,
|
||||
active: f.active,
|
||||
content: f.content,
|
||||
ast: f.ast,
|
||||
token: f.token,
|
||||
pkg: f.pkg,
|
||||
meta: f.meta,
|
||||
imports: f.imports,
|
||||
}
|
||||
v.contentChanges[uri] = func() {
|
||||
v.applyContentChange(uri, content)
|
||||
}
|
||||
|
||||
f := newView.getFile(uri)
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyContentChanges applies all of the changed content stored in the view.
|
||||
// It is assumed that the caller has locked both the view's and the mcache's
|
||||
// mutexes.
|
||||
func (v *View) applyContentChanges(ctx context.Context) error {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
v.pcache.mu.Lock()
|
||||
defer v.pcache.mu.Unlock()
|
||||
|
||||
for uri, change := range v.contentChanges {
|
||||
change()
|
||||
delete(v.contentChanges, uri)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setContent applies a content update for a given file. It assumes that the
|
||||
// caller is holding the view's mutex.
|
||||
func (v *View) applyContentChange(uri source.URI, content []byte) {
|
||||
f := v.getFile(uri)
|
||||
f.content = content
|
||||
|
||||
// Resetting the contents invalidates the ast, token, and pkg fields.
|
||||
// TODO(rstambler): Should we recompute these here?
|
||||
f.ast = nil
|
||||
f.token = nil
|
||||
f.pkg = nil
|
||||
|
||||
// We might need to update the overlay.
|
||||
// Remove the package and all of its reverse dependencies from the cache.
|
||||
if f.pkg != nil {
|
||||
v.remove(f.pkg.PkgPath)
|
||||
}
|
||||
|
||||
switch {
|
||||
case f.active && content == nil:
|
||||
// The file was active, so we need to forget its content.
|
||||
|
@ -114,17 +169,40 @@ func (v *View) SetContent(ctx context.Context, uri source.URI, content []byte) (
|
|||
f.view.Config.Overlay[filename] = f.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newView, nil
|
||||
// remove invalidates a package and its reverse dependencies in the view's
|
||||
// package cache. It is assumed that the caller has locked both the mutexes
|
||||
// of both the mcache and the pcache.
|
||||
func (v *View) remove(pkgPath string) {
|
||||
m, ok := v.mcache.packages[pkgPath]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for parentPkgPath := range m.parents {
|
||||
v.remove(parentPkgPath)
|
||||
}
|
||||
// All of the files in the package may also be holding a pointer to the
|
||||
// invalidated package.
|
||||
for _, filename := range m.files {
|
||||
if f, ok := v.files[source.ToURI(filename)]; ok {
|
||||
f.pkg = nil
|
||||
}
|
||||
}
|
||||
delete(v.pcache.packages, pkgPath)
|
||||
}
|
||||
|
||||
// GetFile 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) GetFile(ctx context.Context, uri source.URI) (source.File, error) {
|
||||
v.mu.Lock()
|
||||
f := v.getFile(uri)
|
||||
v.mu.Unlock()
|
||||
return f, nil
|
||||
defer v.mu.Unlock()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
return v.getFile(uri), nil
|
||||
}
|
||||
|
||||
// getFile is the unlocked internal implementation of GetFile.
|
||||
|
|
|
@ -65,7 +65,7 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := tok.Pos(from.Start.Offset)
|
||||
if !pos.IsValid() {
|
||||
return fmt.Errorf("invalid position %v", from.Start.Offset)
|
||||
|
@ -80,9 +80,9 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
|
|||
var result interface{}
|
||||
switch d.query.Emulate {
|
||||
case "":
|
||||
result, err = buildDefinition(view, ident)
|
||||
result, err = buildDefinition(ctx, view, ident)
|
||||
case emulateGuru:
|
||||
result, err = buildGuruDefinition(view, ident)
|
||||
result, err = buildGuruDefinition(ctx, view, ident)
|
||||
default:
|
||||
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
|
||||
}
|
||||
|
@ -105,8 +105,8 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
||||
content, err := ident.Hover(nil)
|
||||
func buildDefinition(ctx context.Context, view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
||||
content, err := ident.Hover(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -116,9 +116,9 @@ func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definitio
|
|||
}, nil
|
||||
}
|
||||
|
||||
func buildGuruDefinition(view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
|
||||
func buildGuruDefinition(ctx context.Context, view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
|
||||
loc := newLocation(view.FileSet(), ident.Declaration.Range)
|
||||
pkg := ident.File.GetPackage()
|
||||
pkg := ident.File.GetPackage(ctx)
|
||||
// guru does not support ranges
|
||||
loc.End = loc.Start
|
||||
// Behavior that attempts to match the expected output for guru. For an example
|
||||
|
|
|
@ -21,6 +21,10 @@ func (s *server) cacheAndDiagnose(ctx context.Context, uri protocol.DocumentURI,
|
|||
return // handle error?
|
||||
}
|
||||
go func() {
|
||||
ctx := s.view.BackgroundContext()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
reports, err := source.Diagnostics(ctx, s.view, sourceURI)
|
||||
if err != nil {
|
||||
return // handle error?
|
||||
|
@ -35,16 +39,7 @@ func (s *server) cacheAndDiagnose(ctx context.Context, uri protocol.DocumentURI,
|
|||
}
|
||||
|
||||
func (s *server) setContent(ctx context.Context, uri source.URI, content []byte) error {
|
||||
v, err := s.view.SetContent(ctx, uri, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.viewMu.Lock()
|
||||
s.view = v
|
||||
s.viewMu.Unlock()
|
||||
|
||||
return nil
|
||||
return s.view.SetContent(ctx, uri, content)
|
||||
}
|
||||
|
||||
func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) []protocol.Diagnostic {
|
||||
|
|
|
@ -17,7 +17,7 @@ func formatRange(ctx context.Context, v source.View, uri protocol.DocumentURI, r
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
var r source.Range
|
||||
if rng == nil {
|
||||
r.Start = tok.Pos(0)
|
||||
|
@ -29,15 +29,15 @@ func formatRange(ctx context.Context, v source.View, uri protocol.DocumentURI, r
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toProtocolEdits(f, edits), nil
|
||||
return toProtocolEdits(ctx, f, edits), nil
|
||||
}
|
||||
|
||||
func toProtocolEdits(f source.File, edits []source.TextEdit) []protocol.TextEdit {
|
||||
func toProtocolEdits(ctx context.Context, f source.File, edits []source.TextEdit) []protocol.TextEdit {
|
||||
if edits == nil {
|
||||
return nil
|
||||
}
|
||||
tok := f.GetToken()
|
||||
content := f.GetContent()
|
||||
tok := f.GetToken(ctx)
|
||||
content := f.GetContent(ctx)
|
||||
// When a file ends with an empty line, the newline character is counted
|
||||
// as part of the previous line. This causes the formatter to insert
|
||||
// another unnecessary newline on each formatting. We handle this case by
|
||||
|
|
|
@ -20,7 +20,7 @@ func organizeImports(ctx context.Context, v source.View, uri protocol.DocumentUR
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
r := source.Range{
|
||||
Start: tok.Pos(0),
|
||||
End: tok.Pos(tok.Size()),
|
||||
|
@ -29,5 +29,5 @@ func organizeImports(ctx context.Context, v source.View, uri protocol.DocumentUR
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toProtocolEdits(f, edits), nil
|
||||
return toProtocolEdits(ctx, f, edits), nil
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ func (f formats) test(t *testing.T, s *server) {
|
|||
})
|
||||
}
|
||||
}
|
||||
split := strings.SplitAfter(string(f.GetContent()), "\n")
|
||||
split := strings.SplitAfter(string(f.GetContent(context.Background())), "\n")
|
||||
got := strings.Join(diff.ApplyEdits(split, ops), "")
|
||||
if gofmted != got {
|
||||
t.Errorf("format failed for %s: expected '%v', got '%v'", filename, gofmted, got)
|
||||
|
|
|
@ -36,7 +36,7 @@ func fromProtocolLocation(ctx context.Context, v *cache.View, loc protocol.Locat
|
|||
if err != nil {
|
||||
return source.Range{}, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
return fromProtocolRange(tok, loc.Range), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ type server struct {
|
|||
textDocumentSyncKind protocol.TextDocumentSyncKind
|
||||
|
||||
viewMu sync.Mutex
|
||||
view source.View
|
||||
view *cache.View
|
||||
}
|
||||
|
||||
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
|
||||
|
@ -233,7 +233,7 @@ func (s *server) applyChanges(ctx context.Context, params *protocol.DidChangeTex
|
|||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||
}
|
||||
|
||||
content := file.GetContent()
|
||||
content := file.GetContent(ctx)
|
||||
for _, change := range params.ContentChanges {
|
||||
start := bytesOffset(content, change.Range.Start)
|
||||
if start == -1 {
|
||||
|
@ -307,7 +307,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
items, prefix, err := source.Completion(ctx, f, pos)
|
||||
if err != nil {
|
||||
|
@ -332,13 +332,13 @@ func (s *server) Hover(ctx context.Context, params *protocol.TextDocumentPositio
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := ident.Hover(nil)
|
||||
content, err := ident.Hover(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -361,7 +361,7 @@ func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumen
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
info, err := source.SignatureHelp(ctx, f, pos)
|
||||
if err != nil {
|
||||
|
@ -379,7 +379,7 @@ func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||
if err != nil {
|
||||
|
@ -397,7 +397,7 @@ func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocume
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok := f.GetToken()
|
||||
tok := f.GetToken(ctx)
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||
if err != nil {
|
||||
|
|
|
@ -47,8 +47,8 @@ type finder func(types.Object, float64, []CompletionItem) []CompletionItem
|
|||
// completion. For instance, some clients may tolerate imperfect matches as
|
||||
// valid completion results, since users may make typos.
|
||||
func Completion(ctx context.Context, f File, pos token.Pos) (items []CompletionItem, prefix string, err error) {
|
||||
file := f.GetAST()
|
||||
pkg := f.GetPackage()
|
||||
file := f.GetAST(ctx)
|
||||
pkg := f.GetPackage(ctx)
|
||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||
if path == nil {
|
||||
return nil, "", fmt.Errorf("cannot find node enclosing position")
|
||||
|
|
|
@ -48,10 +48,10 @@ func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier
|
|||
return result, err
|
||||
}
|
||||
|
||||
func (i *IdentifierInfo) Hover(q types.Qualifier) (string, error) {
|
||||
func (i *IdentifierInfo) Hover(ctx context.Context, q types.Qualifier) (string, error) {
|
||||
if q == nil {
|
||||
fAST := i.File.GetAST()
|
||||
pkg := i.File.GetPackage()
|
||||
fAST := i.File.GetAST(ctx)
|
||||
pkg := i.File.GetPackage(ctx)
|
||||
q = qualifier(fAST, pkg.Types, pkg.TypesInfo)
|
||||
}
|
||||
return types.ObjectString(i.Declaration.Object, q), nil
|
||||
|
@ -59,8 +59,8 @@ func (i *IdentifierInfo) Hover(q types.Qualifier) (string, error) {
|
|||
|
||||
// identifier checks a single position for a potential identifier.
|
||||
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
|
||||
fAST := f.GetAST()
|
||||
pkg := f.GetPackage()
|
||||
fAST := f.GetAST(ctx)
|
||||
pkg := f.GetPackage(ctx)
|
||||
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
|
||||
result := &IdentifierInfo{
|
||||
File: f,
|
||||
|
|
|
@ -58,7 +58,7 @@ func Diagnostics(ctx context.Context, v View, uri URI) (map[string][]Diagnostic,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg := f.GetPackage()
|
||||
pkg := f.GetPackage(ctx)
|
||||
// Prepare the reports we will send for this package.
|
||||
reports := make(map[string][]Diagnostic)
|
||||
for _, filename := range pkg.CompiledGoFiles {
|
||||
|
@ -87,8 +87,8 @@ func Diagnostics(ctx context.Context, v View, uri URI) (map[string][]Diagnostic,
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
diagTok := diagFile.GetToken()
|
||||
end, err := identifierEnd(diagFile.GetContent(), pos.Line, pos.Column)
|
||||
diagTok := diagFile.GetToken(ctx)
|
||||
end, err := identifierEnd(diagFile.GetContent(ctx), pos.Line, pos.Column)
|
||||
// Don't set a range if it's anything other than a type error.
|
||||
if err != nil || diag.Kind != packages.TypeError {
|
||||
end = 0
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
|
||||
// Format formats a file with a given range.
|
||||
func Format(ctx context.Context, f File, rng Range) ([]TextEdit, error) {
|
||||
fAST := f.GetAST()
|
||||
fAST := f.GetAST(ctx)
|
||||
path, exact := astutil.PathEnclosingInterval(fAST, rng.Start, rng.End)
|
||||
if !exact || len(path) == 0 {
|
||||
return nil, fmt.Errorf("no exact AST node matching the specified range")
|
||||
|
@ -47,26 +47,26 @@ func Format(ctx context.Context, f File, rng Range) ([]TextEdit, error) {
|
|||
// of Go used to build the LSP server will determine how it formats code.
|
||||
// This should be acceptable for all users, who likely be prompted to rebuild
|
||||
// the LSP server on each Go release.
|
||||
fset := f.GetFileSet()
|
||||
fset := f.GetFileSet(ctx)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := format.Node(buf, fset, node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return computeTextEdits(f, buf.String()), nil
|
||||
return computeTextEdits(ctx, f, buf.String()), nil
|
||||
}
|
||||
|
||||
// Imports formats a file using the goimports tool.
|
||||
func Imports(ctx context.Context, f File, rng Range) ([]TextEdit, error) {
|
||||
formatted, err := imports.Process(f.GetToken().Name(), f.GetContent(), nil)
|
||||
formatted, err := imports.Process(f.GetToken(ctx).Name(), f.GetContent(ctx), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return computeTextEdits(f, string(formatted)), nil
|
||||
return computeTextEdits(ctx, f, string(formatted)), nil
|
||||
}
|
||||
|
||||
func computeTextEdits(file File, formatted string) (edits []TextEdit) {
|
||||
u := strings.SplitAfter(string(file.GetContent()), "\n")
|
||||
tok := file.GetToken()
|
||||
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
|
||||
u := strings.SplitAfter(string(file.GetContent(ctx)), "\n")
|
||||
tok := file.GetToken(ctx)
|
||||
f := strings.SplitAfter(formatted, "\n")
|
||||
for _, op := range diff.Operations(u, f) {
|
||||
start := lineStart(tok, op.I1+1)
|
||||
|
|
|
@ -25,8 +25,8 @@ type ParameterInformation struct {
|
|||
}
|
||||
|
||||
func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) {
|
||||
fAST := f.GetAST()
|
||||
pkg := f.GetPackage()
|
||||
fAST := f.GetAST(ctx)
|
||||
pkg := f.GetPackage(ctx)
|
||||
|
||||
// Find a call expression surrounding the query position.
|
||||
var callExpr *ast.CallExpr
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
// package does not directly access the file system.
|
||||
type View interface {
|
||||
GetFile(ctx context.Context, uri URI) (File, error)
|
||||
SetContent(ctx context.Context, uri URI, content []byte) (View, error)
|
||||
SetContent(ctx context.Context, uri URI, content []byte) error
|
||||
GetAnalysisCache() *AnalysisCache
|
||||
FileSet() *token.FileSet
|
||||
}
|
||||
|
@ -27,11 +27,11 @@ type View interface {
|
|||
// building blocks for most queries. Users of the source package can abstract
|
||||
// the loading of packages into their own caching systems.
|
||||
type File interface {
|
||||
GetAST() *ast.File
|
||||
GetFileSet() *token.FileSet
|
||||
GetPackage() *packages.Package
|
||||
GetToken() *token.File
|
||||
GetContent() []byte
|
||||
GetAST(ctx context.Context) *ast.File
|
||||
GetFileSet(ctx context.Context) *token.FileSet
|
||||
GetPackage(ctx context.Context) *packages.Package
|
||||
GetToken(ctx context.Context) *token.File
|
||||
GetContent(ctx context.Context) []byte
|
||||
}
|
||||
|
||||
// Range represents a start and end position.
|
||||
|
|
Загрузка…
Ссылка в новой задаче