diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go index b26c0e5e..49188f4c 100644 --- a/internal/lsp/cache/check.go +++ b/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, }) diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go index a3f67013..13a55154 100644 --- a/internal/lsp/cache/file.go +++ b/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 +} diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 8c222b29..26742327 100644 --- a/internal/lsp/cache/view.go +++ b/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. diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go index 829a44e9..5a2bab6e 100644 --- a/internal/lsp/cmd/definition.go +++ b/internal/lsp/cmd/definition.go @@ -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 diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 85205895..9f07c894 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -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 { diff --git a/internal/lsp/format.go b/internal/lsp/format.go index be88dacf..58369fb3 100644 --- a/internal/lsp/format.go +++ b/internal/lsp/format.go @@ -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 diff --git a/internal/lsp/imports.go b/internal/lsp/imports.go index fad98c82..a914d5a3 100644 --- a/internal/lsp/imports.go +++ b/internal/lsp/imports.go @@ -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 } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 5d8efeed..9206e3f4 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -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) diff --git a/internal/lsp/position.go b/internal/lsp/position.go index fb0d9bdb..c154105c 100644 --- a/internal/lsp/position.go +++ b/internal/lsp/position.go @@ -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 } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 93b99729..37cf6fc1 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -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 { diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index a6799ad2..5e673290 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -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") diff --git a/internal/lsp/source/definition.go b/internal/lsp/source/definition.go index bc233c0c..cca6035f 100644 --- a/internal/lsp/source/definition.go +++ b/internal/lsp/source/definition.go @@ -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, diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go index adb1ded1..3c3218ea 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -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 diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index 1a7e8b08..d1a89dda 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -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) diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index 4478fed2..152727ea 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -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 diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 5a427170..f9088280 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -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.