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:
parent
b40df0fb21
commit
00c44ba9c1
|
@ -1,6 +1,7 @@
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
|
@ -17,19 +18,29 @@ import (
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"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()
|
v.mcache.mu.Lock()
|
||||||
defer v.mcache.mu.Unlock()
|
defer v.mcache.mu.Unlock()
|
||||||
|
|
||||||
|
// Apply any queued-up content changes.
|
||||||
|
if err := v.applyContentChanges(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
f := v.files[uri]
|
f := v.files[uri]
|
||||||
|
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return fmt.Errorf("no file for %v", uri)
|
return fmt.Errorf("no file for %v", uri)
|
||||||
}
|
}
|
||||||
|
// If the package for the file has not been invalidated by the application
|
||||||
imp, err := v.checkMetadata(f)
|
// of the pending changes, there is no need to continue.
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
if f.meta == nil {
|
if f.meta == nil {
|
||||||
|
@ -37,10 +48,10 @@ func (v *View) parse(uri source.URI) error {
|
||||||
}
|
}
|
||||||
// Start prefetching direct imports.
|
// Start prefetching direct imports.
|
||||||
for importPath := range f.meta.children {
|
for importPath := range f.meta.children {
|
||||||
go imp.Import(importPath)
|
go v.Import(importPath)
|
||||||
}
|
}
|
||||||
// Type-check package.
|
// Type-check package.
|
||||||
pkg, err := imp.typeCheck(f.meta.pkgPath)
|
pkg, err := v.typeCheck(f.meta.pkgPath)
|
||||||
if pkg == nil || pkg.Types == nil {
|
if pkg == nil || pkg.Types == nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -75,29 +86,12 @@ func (v *View) cachePackage(pkg *packages.Package) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type importer struct {
|
func (v *View) checkMetadata(ctx context.Context, f *File) error {
|
||||||
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) {
|
|
||||||
filename, err := f.URI.Filename()
|
filename, err := f.URI.Filename()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
imp := &importer{
|
if v.reparseImports(ctx, f, filename) {
|
||||||
View: v,
|
|
||||||
entries: make(map[string]*entry),
|
|
||||||
}
|
|
||||||
if v.reparseImports(f, filename) {
|
|
||||||
cfg := v.Config
|
cfg := v.Config
|
||||||
cfg.Mode = packages.LoadImports
|
cfg.Mode = packages.LoadImports
|
||||||
pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", filename))
|
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 {
|
if err == nil {
|
||||||
err = fmt.Errorf("no packages found for %s", filename)
|
err = fmt.Errorf("no packages found for %s", filename)
|
||||||
}
|
}
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
// If the package comes back with errors from `go list`, don't bother
|
// 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 {
|
for _, err := range pkg.Errors {
|
||||||
switch err.Kind {
|
switch err.Kind {
|
||||||
case packages.UnknownError, packages.ListError:
|
case packages.UnknownError, packages.ListError:
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.link(pkg.PkgPath, pkg, nil)
|
v.link(pkg.PkgPath, pkg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return imp, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// reparseImports reparses a file's import declarations to determine if they
|
// reparseImports reparses a file's import declarations to determine if they
|
||||||
// have changed.
|
// 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 {
|
if f.meta == nil {
|
||||||
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()
|
f.read(ctx)
|
||||||
parsed, _ := parser.ParseFile(v.Config.Fset, filename, f.content, parser.ImportsOnly)
|
parsed, _ := parser.ParseFile(v.Config.Fset, filename, f.content, parser.ImportsOnly)
|
||||||
if parsed == nil {
|
if parsed == nil {
|
||||||
return true
|
return true
|
||||||
|
@ -186,23 +180,23 @@ func (v *View) link(pkgPath string, pkg *packages.Package, parent *metadata) *me
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imp *importer) Import(pkgPath string) (*types.Package, error) {
|
func (v *View) Import(pkgPath string) (*types.Package, error) {
|
||||||
imp.mu.Lock()
|
v.pcache.mu.Lock()
|
||||||
e, ok := imp.entries[pkgPath]
|
e, ok := v.pcache.packages[pkgPath]
|
||||||
if ok {
|
if ok {
|
||||||
// cache hit
|
// cache hit
|
||||||
imp.mu.Unlock()
|
v.pcache.mu.Unlock()
|
||||||
// wait for entry to become ready
|
// wait for entry to become ready
|
||||||
<-e.ready
|
<-e.ready
|
||||||
} else {
|
} else {
|
||||||
// cache miss
|
// cache miss
|
||||||
e = &entry{ready: make(chan struct{})}
|
e = &entry{ready: make(chan struct{})}
|
||||||
imp.entries[pkgPath] = e
|
v.pcache.packages[pkgPath] = e
|
||||||
imp.mu.Unlock()
|
v.pcache.mu.Unlock()
|
||||||
|
|
||||||
// This goroutine becomes responsible for populating
|
// This goroutine becomes responsible for populating
|
||||||
// the entry and broadcasting its readiness.
|
// the entry and broadcasting its readiness.
|
||||||
e.pkg, e.err = imp.typeCheck(pkgPath)
|
e.pkg, e.err = v.typeCheck(pkgPath)
|
||||||
close(e.ready)
|
close(e.ready)
|
||||||
}
|
}
|
||||||
if e.err != nil {
|
if e.err != nil {
|
||||||
|
@ -211,8 +205,8 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
|
||||||
return e.pkg.Types, nil
|
return e.pkg.Types, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imp *importer) typeCheck(pkgPath string) (*packages.Package, error) {
|
func (v *View) typeCheck(pkgPath string) (*packages.Package, error) {
|
||||||
meta, ok := imp.mcache.packages[pkgPath]
|
meta, ok := v.mcache.packages[pkgPath]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no metadata for %v", pkgPath)
|
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,
|
PkgPath: meta.pkgPath,
|
||||||
CompiledGoFiles: meta.files,
|
CompiledGoFiles: meta.files,
|
||||||
Imports: make(map[string]*packages.Package),
|
Imports: make(map[string]*packages.Package),
|
||||||
Fset: imp.Config.Fset,
|
Fset: v.Config.Fset,
|
||||||
Types: typ,
|
Types: typ,
|
||||||
TypesInfo: &types.Info{
|
TypesInfo: &types.Info{
|
||||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||||
|
@ -243,24 +237,35 @@ func (imp *importer) typeCheck(pkgPath string) (*packages.Package, error) {
|
||||||
TypesSizes: &types.StdSizes{},
|
TypesSizes: &types.StdSizes{},
|
||||||
}
|
}
|
||||||
appendError := func(err error) {
|
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 {
|
for _, err := range errs {
|
||||||
appendError(err)
|
appendError(err)
|
||||||
}
|
}
|
||||||
pkg.Syntax = files
|
pkg.Syntax = files
|
||||||
cfg := &types.Config{
|
cfg := &types.Config{
|
||||||
Error: appendError,
|
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)
|
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
|
return pkg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imp *importer) appendPkgError(pkg *packages.Package, err error) {
|
func (v *View) appendPkgError(pkg *packages.Package, err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -283,7 +288,7 @@ func (imp *importer) appendPkgError(pkg *packages.Package, err error) {
|
||||||
}
|
}
|
||||||
case types.Error:
|
case types.Error:
|
||||||
errs = append(errs, packages.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,
|
Msg: err.Msg,
|
||||||
Kind: packages.TypeError,
|
Kind: packages.TypeError,
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -27,44 +28,51 @@ type File struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContent returns the contents of the file, reading it from file system if needed.
|
// 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()
|
f.view.mu.Lock()
|
||||||
defer f.view.mu.Unlock()
|
defer f.view.mu.Unlock()
|
||||||
f.read()
|
|
||||||
|
if ctx.Err() == nil {
|
||||||
|
f.read(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
return f.content
|
return f.content
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) GetFileSet() *token.FileSet {
|
func (f *File) GetFileSet(ctx context.Context) *token.FileSet {
|
||||||
return f.view.Config.Fset
|
return f.view.Config.Fset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) GetToken() *token.File {
|
func (f *File) GetToken(ctx context.Context) *token.File {
|
||||||
f.view.mu.Lock()
|
f.view.mu.Lock()
|
||||||
defer f.view.mu.Unlock()
|
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 nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f.token
|
return f.token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) GetAST() *ast.File {
|
func (f *File) GetAST(ctx context.Context) *ast.File {
|
||||||
f.view.mu.Lock()
|
f.view.mu.Lock()
|
||||||
defer f.view.mu.Unlock()
|
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 nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f.ast
|
return f.ast
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) GetPackage() *packages.Package {
|
func (f *File) GetPackage(ctx context.Context) *packages.Package {
|
||||||
f.view.mu.Lock()
|
f.view.mu.Lock()
|
||||||
defer f.view.mu.Unlock()
|
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
|
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
|
// read is the internal part of GetContent. It assumes that the caller is
|
||||||
// holding the mutex of the file's view.
|
// holding the mutex of the file's view.
|
||||||
func (f *File) read() {
|
func (f *File) read(ctx context.Context) {
|
||||||
if f.content != nil {
|
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.
|
// We don't know the content yet, so read it.
|
||||||
filename, err := f.URI.Filename()
|
filename, err := f.URI.Filename()
|
||||||
|
@ -88,3 +106,8 @@ func (f *File) read() {
|
||||||
}
|
}
|
||||||
f.content = content
|
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 protects all mutable state of the view.
|
||||||
mu sync.Mutex
|
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
|
// Config is the configuration used for the view's interaction with the
|
||||||
// go/packages API. It is shared across all views.
|
// go/packages API. It is shared across all views.
|
||||||
Config packages.Config
|
Config packages.Config
|
||||||
|
@ -24,9 +32,18 @@ type View struct {
|
||||||
// files caches information for opened files in a view.
|
// files caches information for opened files in a view.
|
||||||
files map[source.URI]*File
|
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 caches metadata for the packages of the opened files in a view.
|
||||||
mcache *metadataCache
|
mcache *metadataCache
|
||||||
|
|
||||||
|
pcache *packageCache
|
||||||
|
|
||||||
analysisCache *source.AnalysisCache
|
analysisCache *source.AnalysisCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,16 +58,42 @@ type metadata struct {
|
||||||
parents, children map[string]bool
|
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 {
|
func NewView(config *packages.Config) *View {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &View{
|
return &View{
|
||||||
Config: *config,
|
backgroundCtx: ctx,
|
||||||
files: make(map[source.URI]*File),
|
cancel: cancel,
|
||||||
|
Config: *config,
|
||||||
|
files: make(map[source.URI]*File),
|
||||||
|
contentChanges: make(map[source.URI]func()),
|
||||||
mcache: &metadataCache{
|
mcache: &metadataCache{
|
||||||
packages: make(map[string]*metadata),
|
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 {
|
func (v *View) FileSet() *token.FileSet {
|
||||||
return v.Config.Fset
|
return v.Config.Fset
|
||||||
}
|
}
|
||||||
|
@ -60,45 +103,57 @@ func (v *View) GetAnalysisCache() *source.AnalysisCache {
|
||||||
return v.analysisCache
|
return v.analysisCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) copyView() *View {
|
// SetContent sets the overlay contents for a file.
|
||||||
return &View{
|
func (v *View) SetContent(ctx context.Context, uri source.URI, content []byte) error {
|
||||||
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) {
|
|
||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
defer v.mu.Unlock()
|
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 {
|
v.contentChanges[uri] = func() {
|
||||||
newView.files[fURI] = &File{
|
v.applyContentChange(uri, content)
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
f.content = content
|
||||||
|
|
||||||
// Resetting the contents invalidates the ast, token, and pkg fields.
|
// TODO(rstambler): Should we recompute these here?
|
||||||
f.ast = nil
|
f.ast = nil
|
||||||
f.token = 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 {
|
switch {
|
||||||
case f.active && content == nil:
|
case f.active && content == nil:
|
||||||
// The file was active, so we need to forget its content.
|
// 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
|
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
|
// GetFile returns a File for the given URI. It will always succeed because it
|
||||||
// adds the file to the managed set if needed.
|
// adds the file to the managed set if needed.
|
||||||
func (v *View) GetFile(ctx context.Context, uri source.URI) (source.File, error) {
|
func (v *View) GetFile(ctx context.Context, uri source.URI) (source.File, error) {
|
||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
f := v.getFile(uri)
|
defer v.mu.Unlock()
|
||||||
v.mu.Unlock()
|
|
||||||
return f, nil
|
if ctx.Err() != nil {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.getFile(uri), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFile is the unlocked internal implementation of GetFile.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := tok.Pos(from.Start.Offset)
|
pos := tok.Pos(from.Start.Offset)
|
||||||
if !pos.IsValid() {
|
if !pos.IsValid() {
|
||||||
return fmt.Errorf("invalid position %v", from.Start.Offset)
|
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{}
|
var result interface{}
|
||||||
switch d.query.Emulate {
|
switch d.query.Emulate {
|
||||||
case "":
|
case "":
|
||||||
result, err = buildDefinition(view, ident)
|
result, err = buildDefinition(ctx, view, ident)
|
||||||
case emulateGuru:
|
case emulateGuru:
|
||||||
result, err = buildGuruDefinition(view, ident)
|
result, err = buildGuruDefinition(ctx, view, ident)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
func buildDefinition(ctx context.Context, view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
||||||
content, err := ident.Hover(nil)
|
content, err := ident.Hover(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -116,9 +116,9 @@ func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definitio
|
||||||
}, nil
|
}, 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)
|
loc := newLocation(view.FileSet(), ident.Declaration.Range)
|
||||||
pkg := ident.File.GetPackage()
|
pkg := ident.File.GetPackage(ctx)
|
||||||
// guru does not support ranges
|
// guru does not support ranges
|
||||||
loc.End = loc.Start
|
loc.End = loc.Start
|
||||||
// Behavior that attempts to match the expected output for guru. For an example
|
// 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?
|
return // handle error?
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
|
ctx := s.view.BackgroundContext()
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
reports, err := source.Diagnostics(ctx, s.view, sourceURI)
|
reports, err := source.Diagnostics(ctx, s.view, sourceURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return // handle error?
|
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 {
|
func (s *server) setContent(ctx context.Context, uri source.URI, content []byte) error {
|
||||||
v, err := s.view.SetContent(ctx, uri, content)
|
return s.view.SetContent(ctx, uri, content)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.viewMu.Lock()
|
|
||||||
s.view = v
|
|
||||||
s.viewMu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) []protocol.Diagnostic {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
var r source.Range
|
var r source.Range
|
||||||
if rng == nil {
|
if rng == nil {
|
||||||
r.Start = tok.Pos(0)
|
r.Start = tok.Pos(0)
|
||||||
|
@ -29,15 +29,15 @@ func formatRange(ctx context.Context, v source.View, uri protocol.DocumentURI, r
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if edits == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
content := f.GetContent()
|
content := f.GetContent(ctx)
|
||||||
// When a file ends with an empty line, the newline character is counted
|
// 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
|
// as part of the previous line. This causes the formatter to insert
|
||||||
// another unnecessary newline on each formatting. We handle this case by
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
r := source.Range{
|
r := source.Range{
|
||||||
Start: tok.Pos(0),
|
Start: tok.Pos(0),
|
||||||
End: tok.Pos(tok.Size()),
|
End: tok.Pos(tok.Size()),
|
||||||
|
@ -29,5 +29,5 @@ func organizeImports(ctx context.Context, v source.View, uri protocol.DocumentUR
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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), "")
|
got := strings.Join(diff.ApplyEdits(split, ops), "")
|
||||||
if gofmted != got {
|
if gofmted != got {
|
||||||
t.Errorf("format failed for %s: expected '%v', got '%v'", filename, 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 {
|
if err != nil {
|
||||||
return source.Range{}, err
|
return source.Range{}, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
return fromProtocolRange(tok, loc.Range), nil
|
return fromProtocolRange(tok, loc.Range), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ type server struct {
|
||||||
textDocumentSyncKind protocol.TextDocumentSyncKind
|
textDocumentSyncKind protocol.TextDocumentSyncKind
|
||||||
|
|
||||||
viewMu sync.Mutex
|
viewMu sync.Mutex
|
||||||
view source.View
|
view *cache.View
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
|
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")
|
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
content := file.GetContent()
|
content := file.GetContent(ctx)
|
||||||
for _, change := range params.ContentChanges {
|
for _, change := range params.ContentChanges {
|
||||||
start := bytesOffset(content, change.Range.Start)
|
start := bytesOffset(content, change.Range.Start)
|
||||||
if start == -1 {
|
if start == -1 {
|
||||||
|
@ -307,7 +307,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
items, prefix, err := source.Completion(ctx, f, pos)
|
items, prefix, err := source.Completion(ctx, f, pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -332,13 +332,13 @@ func (s *server) Hover(ctx context.Context, params *protocol.TextDocumentPositio
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
content, err := ident.Hover(nil)
|
content, err := ident.Hover(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,7 @@ func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumen
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
info, err := source.SignatureHelp(ctx, f, pos)
|
info, err := source.SignatureHelp(ctx, f, pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -379,7 +379,7 @@ func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -397,7 +397,7 @@ func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocume
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tok := f.GetToken()
|
tok := f.GetToken(ctx)
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
ident, err := source.Identifier(ctx, s.view, f, pos)
|
ident, err := source.Identifier(ctx, s.view, f, pos)
|
||||||
if err != nil {
|
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
|
// completion. For instance, some clients may tolerate imperfect matches as
|
||||||
// valid completion results, since users may make typos.
|
// valid completion results, since users may make typos.
|
||||||
func Completion(ctx context.Context, f File, pos token.Pos) (items []CompletionItem, prefix string, err error) {
|
func Completion(ctx context.Context, f File, pos token.Pos) (items []CompletionItem, prefix string, err error) {
|
||||||
file := f.GetAST()
|
file := f.GetAST(ctx)
|
||||||
pkg := f.GetPackage()
|
pkg := f.GetPackage(ctx)
|
||||||
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
|
||||||
if path == nil {
|
if path == nil {
|
||||||
return nil, "", fmt.Errorf("cannot find node enclosing position")
|
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
|
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 {
|
if q == nil {
|
||||||
fAST := i.File.GetAST()
|
fAST := i.File.GetAST(ctx)
|
||||||
pkg := i.File.GetPackage()
|
pkg := i.File.GetPackage(ctx)
|
||||||
q = qualifier(fAST, pkg.Types, pkg.TypesInfo)
|
q = qualifier(fAST, pkg.Types, pkg.TypesInfo)
|
||||||
}
|
}
|
||||||
return types.ObjectString(i.Declaration.Object, q), nil
|
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.
|
// identifier checks a single position for a potential identifier.
|
||||||
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
|
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
|
||||||
fAST := f.GetAST()
|
fAST := f.GetAST(ctx)
|
||||||
pkg := f.GetPackage()
|
pkg := f.GetPackage(ctx)
|
||||||
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
|
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
|
||||||
result := &IdentifierInfo{
|
result := &IdentifierInfo{
|
||||||
File: f,
|
File: f,
|
||||||
|
|
|
@ -58,7 +58,7 @@ func Diagnostics(ctx context.Context, v View, uri URI) (map[string][]Diagnostic,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pkg := f.GetPackage()
|
pkg := f.GetPackage(ctx)
|
||||||
// Prepare the reports we will send for this package.
|
// Prepare the reports we will send for this package.
|
||||||
reports := make(map[string][]Diagnostic)
|
reports := make(map[string][]Diagnostic)
|
||||||
for _, filename := range pkg.CompiledGoFiles {
|
for _, filename := range pkg.CompiledGoFiles {
|
||||||
|
@ -87,8 +87,8 @@ func Diagnostics(ctx context.Context, v View, uri URI) (map[string][]Diagnostic,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
diagTok := diagFile.GetToken()
|
diagTok := diagFile.GetToken(ctx)
|
||||||
end, err := identifierEnd(diagFile.GetContent(), pos.Line, pos.Column)
|
end, err := identifierEnd(diagFile.GetContent(ctx), pos.Line, pos.Column)
|
||||||
// Don't set a range if it's anything other than a type error.
|
// Don't set a range if it's anything other than a type error.
|
||||||
if err != nil || diag.Kind != packages.TypeError {
|
if err != nil || diag.Kind != packages.TypeError {
|
||||||
end = 0
|
end = 0
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
// Format formats a file with a given range.
|
// Format formats a file with a given range.
|
||||||
func Format(ctx context.Context, f File, rng Range) ([]TextEdit, error) {
|
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)
|
path, exact := astutil.PathEnclosingInterval(fAST, rng.Start, rng.End)
|
||||||
if !exact || len(path) == 0 {
|
if !exact || len(path) == 0 {
|
||||||
return nil, fmt.Errorf("no exact AST node matching the specified range")
|
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.
|
// 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
|
// This should be acceptable for all users, who likely be prompted to rebuild
|
||||||
// the LSP server on each Go release.
|
// the LSP server on each Go release.
|
||||||
fset := f.GetFileSet()
|
fset := f.GetFileSet(ctx)
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
if err := format.Node(buf, fset, node); err != nil {
|
if err := format.Node(buf, fset, node); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return computeTextEdits(f, buf.String()), nil
|
return computeTextEdits(ctx, f, buf.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imports formats a file using the goimports tool.
|
// Imports formats a file using the goimports tool.
|
||||||
func Imports(ctx context.Context, f File, rng Range) ([]TextEdit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return computeTextEdits(f, string(formatted)), nil
|
return computeTextEdits(ctx, f, string(formatted)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeTextEdits(file File, formatted string) (edits []TextEdit) {
|
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
|
||||||
u := strings.SplitAfter(string(file.GetContent()), "\n")
|
u := strings.SplitAfter(string(file.GetContent(ctx)), "\n")
|
||||||
tok := file.GetToken()
|
tok := file.GetToken(ctx)
|
||||||
f := strings.SplitAfter(formatted, "\n")
|
f := strings.SplitAfter(formatted, "\n")
|
||||||
for _, op := range diff.Operations(u, f) {
|
for _, op := range diff.Operations(u, f) {
|
||||||
start := lineStart(tok, op.I1+1)
|
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) {
|
func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) {
|
||||||
fAST := f.GetAST()
|
fAST := f.GetAST(ctx)
|
||||||
pkg := f.GetPackage()
|
pkg := f.GetPackage(ctx)
|
||||||
|
|
||||||
// Find a call expression surrounding the query position.
|
// Find a call expression surrounding the query position.
|
||||||
var callExpr *ast.CallExpr
|
var callExpr *ast.CallExpr
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
// package does not directly access the file system.
|
// package does not directly access the file system.
|
||||||
type View interface {
|
type View interface {
|
||||||
GetFile(ctx context.Context, uri URI) (File, error)
|
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
|
GetAnalysisCache() *AnalysisCache
|
||||||
FileSet() *token.FileSet
|
FileSet() *token.FileSet
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ type View interface {
|
||||||
// building blocks for most queries. Users of the source package can abstract
|
// building blocks for most queries. Users of the source package can abstract
|
||||||
// the loading of packages into their own caching systems.
|
// the loading of packages into their own caching systems.
|
||||||
type File interface {
|
type File interface {
|
||||||
GetAST() *ast.File
|
GetAST(ctx context.Context) *ast.File
|
||||||
GetFileSet() *token.FileSet
|
GetFileSet(ctx context.Context) *token.FileSet
|
||||||
GetPackage() *packages.Package
|
GetPackage(ctx context.Context) *packages.Package
|
||||||
GetToken() *token.File
|
GetToken(ctx context.Context) *token.File
|
||||||
GetContent() []byte
|
GetContent(ctx context.Context) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range represents a start and end position.
|
// Range represents a start and end position.
|
||||||
|
|
Loading…
Reference in New Issue