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:
Rebecca Stambler 2019-03-05 17:30:44 -05:00
Родитель b40df0fb21
Коммит 00c44ba9c1
16 изменённых файлов: 256 добавлений и 155 удалений

99
internal/lsp/cache/check.go поставляемый
Просмотреть файл

@ -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,
})

51
internal/lsp/cache/file.go поставляемый
Просмотреть файл

@ -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
}

146
internal/lsp/cache/view.go поставляемый
Просмотреть файл

@ -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.