From 35ba81b9fb229f4181c1b2cbc257602955e295c3 Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Wed, 20 Nov 2019 16:38:43 -0500 Subject: [PATCH] internal/lsp: reorganize and refactor code This change cleans up internal/lsp/source/view.go to have a more logical ordering and deletes the view.CheckPackageHandle function. Now, the only way to get a CheckPackageHandle is through a snapshot (so all of the corresponding edits). Also, renamed fuzzy tests to fuzzymatch. Noticed this weird error when debugging - I had golang.org/x/tools/internal/lsp/fuzzy in my module cache and it conflicted with the test version. Change-Id: Ib87836796a8e76e6b6ed1306c2a93e9a5db91cce Reviewed-on: https://go-review.googlesource.com/c/tools/+/208099 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Heschi Kreinick --- internal/lsp/cache/gofile.go | 18 +- internal/lsp/code_action.go | 12 +- internal/lsp/completion.go | 3 +- internal/lsp/definition.go | 6 +- internal/lsp/diagnostics.go | 16 +- internal/lsp/folding_range.go | 3 +- internal/lsp/format.go | 3 +- internal/lsp/general.go | 4 +- internal/lsp/highlight.go | 7 +- internal/lsp/hover.go | 3 +- internal/lsp/implementation.go | 3 +- internal/lsp/lsp_test.go | 4 +- internal/lsp/references.go | 3 +- internal/lsp/rename.go | 6 +- internal/lsp/signature_help.go | 3 +- internal/lsp/source/completion.go | 16 +- internal/lsp/source/completion_format.go | 12 +- internal/lsp/source/completion_literal.go | 2 +- internal/lsp/source/completion_snippet.go | 2 +- internal/lsp/source/diagnostics.go | 30 +- internal/lsp/source/folding_range.go | 23 +- internal/lsp/source/format.go | 18 +- internal/lsp/source/highlight.go | 13 +- internal/lsp/source/identifier.go | 4 +- internal/lsp/source/signature_help.go | 10 +- internal/lsp/source/source_test.go | 32 +- internal/lsp/source/symbols.go | 10 +- internal/lsp/source/view.go | 384 +++++++++--------- internal/lsp/symbols.go | 3 +- .../fuzzy.go => fuzzymatch/fuzzymatch.go} | 0 internal/lsp/text_synchronization.go | 9 +- internal/lsp/watched_files.go | 6 +- internal/lsp/workspace.go | 2 +- 33 files changed, 332 insertions(+), 338 deletions(-) rename internal/lsp/testdata/{fuzzy/fuzzy.go => fuzzymatch/fuzzymatch.go} (100%) diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go index 497d1cbc62..a9d00fc864 100644 --- a/internal/lsp/cache/gofile.go +++ b/internal/lsp/cache/gofile.go @@ -13,21 +13,7 @@ import ( errors "golang.org/x/xerrors" ) -func (v *view) CheckPackageHandles(ctx context.Context, f source.File) (source.Snapshot, []source.CheckPackageHandle, error) { - // Get the snapshot that will be used for type-checking. - s := v.getSnapshot() - - cphs, err := s.CheckPackageHandles(ctx, f) - if err != nil { - return nil, nil, err - } - if len(cphs) == 0 { - return nil, nil, errors.Errorf("no CheckPackageHandles for %s", f.URI()) - } - return s, cphs, nil -} - -func (s *snapshot) CheckPackageHandles(ctx context.Context, f source.File) ([]source.CheckPackageHandle, error) { +func (s *snapshot) PackageHandles(ctx context.Context, f source.File) ([]source.CheckPackageHandle, error) { ctx = telemetry.File.With(ctx, f.URI()) fh := s.Handle(ctx, f) @@ -109,7 +95,7 @@ func (v *view) GetActiveReverseDeps(ctx context.Context, f source.File) (results if _, ok := seen[f.URI()]; ok { continue } - cphs, err := s.CheckPackageHandles(ctx, f) + cphs, err := s.PackageHandles(ctx, f) if err != nil { continue } diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index 6d182ac9f5..9a6bf99483 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -72,7 +72,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara }, }) case source.Go: - edits, editsPerFix, err := source.AllImportsFixes(ctx, view, f) + edits, editsPerFix, err := source.AllImportsFixes(ctx, snapshot, f) if err != nil { return nil, err } @@ -204,9 +204,9 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic return results } -func quickFixes(ctx context.Context, s source.Snapshot, f source.File, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) { +func quickFixes(ctx context.Context, snapshot source.Snapshot, f source.File, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) { var codeActions []protocol.CodeAction - cphs, err := s.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } @@ -217,7 +217,7 @@ func quickFixes(ctx context.Context, s source.Snapshot, f source.File, diagnosti return nil, err } for _, diag := range diagnostics { - srcErr, err := s.FindAnalysisError(ctx, cph.ID(), diag) + srcErr, err := snapshot.FindAnalysisError(ctx, cph.ID(), diag) if err != nil { continue } @@ -229,12 +229,12 @@ func quickFixes(ctx context.Context, s source.Snapshot, f source.File, diagnosti Edit: protocol.WorkspaceEdit{}, } for uri, edits := range fix.Edits { - f, err := s.View().GetFile(ctx, uri) + f, err := snapshot.View().GetFile(ctx, uri) if err != nil { log.Error(ctx, "no file", err, telemetry.URI.Of(uri)) continue } - fh := s.Handle(ctx, f) + fh := snapshot.Handle(ctx, f) action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, documentChanges(fh, edits)...) } codeActions = append(codeActions, action) diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 3bdc123084..9d1fb72915 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -22,13 +22,14 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara if err != nil { return nil, err } + snapshot := view.Snapshot() options := view.Options() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } options.Completion.FullDocumentation = options.HoverKind == source.FullDocumentation - candidates, surrounding, err := source.Completion(ctx, view, f, params.Position, options.Completion) + candidates, surrounding, err := source.Completion(ctx, snapshot, f, params.Position, options.Completion) if err != nil { log.Print(ctx, "no completions found", tag.Of("At", params.Position), tag.Of("Failure", err)) } diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go index 6e3cae2f0f..78aae36130 100644 --- a/internal/lsp/definition.go +++ b/internal/lsp/definition.go @@ -18,11 +18,12 @@ func (s *Server) definition(ctx context.Context, params *protocol.DefinitionPara if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, err } @@ -44,11 +45,12 @@ func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefini if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, err } diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 3519ad86b7..a77b490719 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -16,7 +16,7 @@ import ( "golang.org/x/tools/internal/telemetry/trace" ) -func (s *Server) diagnoseView(view source.View, cphs []source.CheckPackageHandle) { +func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, cphs []source.CheckPackageHandle) { for _, cph := range cphs { if len(cph.CompiledGoFiles()) == 0 { continue @@ -24,24 +24,24 @@ func (s *Server) diagnoseView(view source.View, cphs []source.CheckPackageHandle f := cph.CompiledGoFiles()[0] // Run diagnostics on the workspace package. - go func(view source.View, uri span.URI) { - s.diagnostics(view, uri) - }(view, f.File().Identity().URI) + go func(snapshot source.Snapshot, uri span.URI) { + s.diagnostics(snapshot, uri) + }(snapshot, f.File().Identity().URI) } } -func (s *Server) diagnostics(view source.View, uri span.URI) error { - ctx := view.BackgroundContext() +func (s *Server) diagnostics(snapshot source.Snapshot, uri span.URI) error { + ctx := snapshot.View().BackgroundContext() ctx, done := trace.StartSpan(ctx, "lsp:background-worker") defer done() ctx = telemetry.File.With(ctx, uri) - f, err := view.GetFile(ctx, uri) + f, err := snapshot.View().GetFile(ctx, uri) if err != nil { return err } - reports, warningMsg, err := source.Diagnostics(ctx, view, f, view.Options().DisabledAnalyses) + reports, warningMsg, err := source.Diagnostics(ctx, snapshot, f, snapshot.View().Options().DisabledAnalyses) if err != nil { return err } diff --git a/internal/lsp/folding_range.go b/internal/lsp/folding_range.go index 5d88e746cd..3800e62d92 100644 --- a/internal/lsp/folding_range.go +++ b/internal/lsp/folding_range.go @@ -14,11 +14,12 @@ func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRange if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ranges, err := source.FoldingRange(ctx, view, f, view.Options().LineFoldingOnly) + ranges, err := source.FoldingRange(ctx, snapshot, f, view.Options().LineFoldingOnly) if err != nil { return nil, err } diff --git a/internal/lsp/format.go b/internal/lsp/format.go index b16bfb6a99..67c668e43f 100644 --- a/internal/lsp/format.go +++ b/internal/lsp/format.go @@ -18,9 +18,10 @@ func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormat if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - return source.Format(ctx, view, f) + return source.Format(ctx, snapshot, f) } diff --git a/internal/lsp/general.go b/internal/lsp/general.go index a210aeee2c..8a2f1ffb2b 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -101,7 +101,7 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ }, }, Workspace: protocol.WorkspaceGn{ - protocol.WorkspaceFoldersGn{ + WorkspaceFolders: protocol.WorkspaceFoldersGn{ Supported: true, ChangeNotifications: "workspace/didChangeWorkspaceFolders", }, @@ -163,7 +163,7 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa viewErrors[uri] = err continue } - s.diagnoseView(view, workspacePackages) + s.diagnoseSnapshot(view.Snapshot(), workspacePackages) } if len(viewErrors) > 0 { errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(s.pendingFolders), len(s.session.Views())) diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go index 48f99923f3..52a246c25e 100644 --- a/internal/lsp/highlight.go +++ b/internal/lsp/highlight.go @@ -20,7 +20,12 @@ func (s *Server) documentHighlight(ctx context.Context, params *protocol.Documen if err != nil { return nil, err } - rngs, err := source.Highlight(ctx, view, uri, params.Position) + snapshot := view.Snapshot() + f, err := view.GetFile(ctx, uri) + if err != nil { + return nil, err + } + rngs, err := source.Highlight(ctx, snapshot, f, params.Position) if err != nil { log.Error(ctx, "no highlight", err, telemetry.URI.Of(uri)) } diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index 5c68aec296..11aad43187 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -21,11 +21,12 @@ func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*prot if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, nil } diff --git a/internal/lsp/implementation.go b/internal/lsp/implementation.go index 09f18e485e..9af0f7057a 100644 --- a/internal/lsp/implementation.go +++ b/internal/lsp/implementation.go @@ -18,11 +18,12 @@ func (s *Server) implementation(ctx context.Context, params *protocol.Implementa if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, err } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 3369103344..2895c9e41b 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -78,7 +78,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti if err != nil { t.Fatalf("no file for %s: %v", f, err) } - results, _, err := source.Diagnostics(r.ctx, v, f, nil) + results, _, err := source.Diagnostics(r.ctx, v.Snapshot(), f, nil) if err != nil { t.Fatal(err) } @@ -336,7 +336,7 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span) { if err != nil { t.Fatal(err) } - diagnostics, _, err := source.Diagnostics(r.ctx, view, f, nil) + diagnostics, _, err := source.Diagnostics(r.ctx, view.Snapshot(), f, nil) if err != nil { t.Fatal(err) } diff --git a/internal/lsp/references.go b/internal/lsp/references.go index a292035518..9d831b5c81 100644 --- a/internal/lsp/references.go +++ b/internal/lsp/references.go @@ -20,12 +20,13 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } // Find all references to the identifier at the position. - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, err } diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go index cc9f1a195e..1db1abdc99 100644 --- a/internal/lsp/rename.go +++ b/internal/lsp/rename.go @@ -18,11 +18,12 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, err } @@ -50,11 +51,12 @@ func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRena if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - ident, err := source.Identifier(ctx, view, f, params.Position) + ident, err := source.Identifier(ctx, snapshot, f, params.Position) if err != nil { return nil, nil // ignore errors } diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go index f65a7a3378..52f8a7a9ab 100644 --- a/internal/lsp/signature_help.go +++ b/internal/lsp/signature_help.go @@ -20,11 +20,12 @@ func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHe if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - info, err := source.SignatureHelp(ctx, view, f, params.Position) + info, err := source.SignatureHelp(ctx, snapshot, f, params.Position) if err != nil { log.Print(ctx, "no signature help", tag.Of("At", params.Position), tag.Of("Failure", err)) return nil, nil diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index de26587c67..537264a967 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -132,9 +132,6 @@ type completer struct { qf types.Qualifier opts CompletionOptions - // view is the View associated with this completion request. - view View - // ctx is the context associated with this completion request. ctx context.Context @@ -259,7 +256,7 @@ func (c *completer) setSurrounding(ident *ast.Ident) { cursor: c.pos, mappedRange: mappedRange{ // Overwrite the prefix only. - spanRange: span.NewRange(c.view.Session().Cache().FileSet(), ident.Pos(), c.pos), + spanRange: span.NewRange(c.snapshot.View().Session().Cache().FileSet(), ident.Pos(), c.pos), m: c.mapper, }, } @@ -279,7 +276,7 @@ func (c *completer) getSurrounding() *Selection { content: "", cursor: c.pos, mappedRange: mappedRange{ - spanRange: span.NewRange(c.view.Session().Cache().FileSet(), c.pos, c.pos), + spanRange: span.NewRange(c.snapshot.View().Session().Cache().FileSet(), c.pos, c.pos), m: c.mapper, }, } @@ -397,13 +394,13 @@ func (e ErrIsDefinition) Error() string { // The selection is computed based on the preceding identifier and can be used by // the client to score the quality of the completion. For instance, some clients // may tolerate imperfect matches as valid completion results, since users may make typos. -func Completion(ctx context.Context, view View, f File, pos protocol.Position, opts CompletionOptions) ([]CompletionItem, *Selection, error) { +func Completion(ctx context.Context, snapshot Snapshot, f File, pos protocol.Position, opts CompletionOptions) ([]CompletionItem, *Selection, error) { ctx, done := trace.StartSpan(ctx, "source.Completion") defer done() startTime := time.Now() - snapshot, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, nil, err } @@ -452,7 +449,6 @@ func Completion(ctx context.Context, view View, f File, pos protocol.Position, o pkg: pkg, snapshot: snapshot, qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()), - view: view, ctx: ctx, filename: f.URI().Filename(), file: file, @@ -586,7 +582,7 @@ func (c *completer) selector(sel *ast.SelectorExpr) error { // Try unimported packages. if id, ok := sel.X.(*ast.Ident); ok { - pkgExports, err := PackageExports(c.ctx, c.view, id.Name, c.filename) + pkgExports, err := PackageExports(c.ctx, c.snapshot.View(), id.Name, c.filename) if err != nil { return err } @@ -736,7 +732,7 @@ func (c *completer) lexical() error { if c.opts.Unimported { // Suggest packages that have not been imported yet. - pkgs, err := CandidateImports(c.ctx, c.view, c.filename) + pkgs, err := CandidateImports(c.ctx, c.snapshot.View(), c.filename) if err != nil { return err } diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 190ef86d78..67148c91f9 100644 --- a/internal/lsp/source/completion_format.go +++ b/internal/lsp/source/completion_format.go @@ -139,7 +139,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { if !c.opts.Documentation { return item, nil } - pos := c.view.Session().Cache().FileSet().Position(obj.Pos()) + pos := c.snapshot.View().Session().Cache().FileSet().Position(obj.Pos()) // We ignore errors here, because some types, like "unsafe" or "error", // may not have valid positions that we can use to get documentation. @@ -154,7 +154,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { if cand.imp != nil && cand.imp.pkg != nil { searchPkg = cand.imp.pkg } - file, _, pkg, err := c.view.FindPosInPackage(searchPkg, obj.Pos()) + file, _, pkg, err := c.snapshot.View().FindPosInPackage(searchPkg, obj.Pos()) if err != nil { log.Error(c.ctx, "error finding file in package", err, telemetry.URI.Of(uri), telemetry.Package.Of(searchPkg.ID())) return item, nil @@ -193,7 +193,7 @@ func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { return nil, errors.Errorf("no ParseGoHandle for %s", c.filename) } - return computeOneImportFixEdits(c.ctx, c.view, ph, &imports.ImportFix{ + return computeOneImportFixEdits(c.ctx, c.snapshot.View(), ph, &imports.ImportFix{ StmtInfo: imports.ImportInfo{ ImportPath: imp.importPath, Name: imp.name, @@ -215,7 +215,7 @@ func (c *completer) formatBuiltin(cand candidate) CompletionItem { item.Kind = protocol.ConstantCompletion case *types.Builtin: item.Kind = protocol.FunctionCompletion - builtin := c.view.BuiltinPackage().Lookup(obj.Name()) + builtin := c.snapshot.View().BuiltinPackage().Lookup(obj.Name()) if obj == nil { break } @@ -223,8 +223,8 @@ func (c *completer) formatBuiltin(cand candidate) CompletionItem { if !ok { break } - params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params) - results, writeResultParens := formatFieldList(c.ctx, c.view, decl.Type.Results) + params, _ := formatFieldList(c.ctx, c.snapshot.View(), decl.Type.Params) + results, writeResultParens := formatFieldList(c.ctx, c.snapshot.View(), decl.Type.Results) item.Label = obj.Name() item.Detail = "func" + formatFunction(params, results, writeResultParens) item.snippet = c.functionCallSnippet(obj.Name(), params) diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go index 7dd5097644..bdf1222b5a 100644 --- a/internal/lsp/source/completion_literal.go +++ b/internal/lsp/source/completion_literal.go @@ -104,7 +104,7 @@ func (c *completer) literal(literalType types.Type, imp *importInfo) { // If we are in a selector we must place the "&" before the selector. // For example, "foo.B<>" must complete to "&foo.Bar{}", not // "foo.&Bar{}". - edits, err := referenceEdit(c.view.Session().Cache().FileSet(), c.mapper, sel) + edits, err := referenceEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel) if err != nil { log.Error(c.ctx, "error making edit for literal pointer completion", err) return diff --git a/internal/lsp/source/completion_snippet.go b/internal/lsp/source/completion_snippet.go index 031bed106f..567825693f 100644 --- a/internal/lsp/source/completion_snippet.go +++ b/internal/lsp/source/completion_snippet.go @@ -41,7 +41,7 @@ func (c *completer) structFieldSnippet(label, detail string) *snippet.Builder { } }) - fset := c.view.Session().Cache().FileSet() + fset := c.snapshot.View().Session().Cache().FileSet() // If the cursor position is on a different line from the literal's opening brace, // we are in a multiline literal. diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go index d90951dc9c..b112c081a3 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -39,11 +39,11 @@ type RelatedInformation struct { Message string } -func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) { +func Diagnostics(ctx context.Context, snapshot Snapshot, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) { ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI())) defer done() - snapshot, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, "", err } @@ -56,7 +56,7 @@ func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[st // not correctly configured. Report errors, if possible. var warningMsg string if len(cph.MissingDependencies()) > 0 { - warningMsg, err = checkCommonErrors(ctx, view, f.URI()) + warningMsg, err = checkCommonErrors(ctx, snapshot.View(), f.URI()) if err != nil { log.Error(ctx, "error checking common errors", err, telemetry.File.Of(f.URI)) } @@ -70,7 +70,7 @@ func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[st // Prepare the reports we will send for the files in this package. reports := make(map[span.URI][]Diagnostic) for _, fh := range pkg.CompiledGoFiles() { - clearReports(view, reports, fh.File().Identity().URI) + clearReports(snapshot, reports, fh.File().Identity().URI) } // Prepare any additional reports for the errors in this package. @@ -78,27 +78,27 @@ func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[st if err.Kind != ListError { continue } - clearReports(view, reports, err.URI) + clearReports(snapshot, reports, err.URI) } // Run diagnostics for the package that this URI belongs to. - if !diagnostics(ctx, view, pkg, reports) { + if !diagnostics(ctx, pkg, reports) { // If we don't have any list, parse, or type errors, run analyses. if err := analyses(ctx, snapshot, cph, disabledAnalyses, reports); err != nil { log.Error(ctx, "failed to run analyses", err, telemetry.File.Of(f.URI())) } } // Updates to the diagnostics for this package may need to be propagated. - revDeps := view.GetActiveReverseDeps(ctx, f) + revDeps := snapshot.View().GetActiveReverseDeps(ctx, f) for _, cph := range revDeps { pkg, err := cph.Check(ctx) if err != nil { return nil, warningMsg, err } for _, fh := range pkg.CompiledGoFiles() { - clearReports(view, reports, fh.File().Identity().URI) + clearReports(snapshot, reports, fh.File().Identity().URI) } - diagnostics(ctx, view, pkg, reports) + diagnostics(ctx, pkg, reports) } return reports, warningMsg, nil } @@ -107,7 +107,7 @@ type diagnosticSet struct { listErrors, parseErrors, typeErrors []*Diagnostic } -func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.URI][]Diagnostic) bool { +func diagnostics(ctx context.Context, pkg Package, reports map[span.URI][]Diagnostic) bool { ctx, done := trace.StartSpan(ctx, "source.diagnostics", telemetry.Package.Of(pkg.ID())) _ = ctx // circumvent SA4006 defer done() @@ -181,7 +181,7 @@ func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, di if onlyDeletions(e.SuggestedFixes) { tags = append(tags, protocol.Unnecessary) } - addReport(snapshot.View(), reports, Diagnostic{ + addReport(snapshot, reports, Diagnostic{ URI: e.URI, Range: e.Range, Message: e.Message, @@ -195,15 +195,15 @@ func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, di return nil } -func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) { - if v.Ignore(uri) { +func clearReports(snapshot Snapshot, reports map[span.URI][]Diagnostic, uri span.URI) { + if snapshot.View().Ignore(uri) { return } reports[uri] = []Diagnostic{} } -func addReport(v View, reports map[span.URI][]Diagnostic, diagnostic Diagnostic) { - if v.Ignore(diagnostic.URI) { +func addReport(snapshot Snapshot, reports map[span.URI][]Diagnostic, diagnostic Diagnostic) { + if snapshot.View().Ignore(diagnostic.URI) { return } if _, ok := reports[diagnostic.URI]; ok { diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go index e944e9b6d1..2fcb310a42 100644 --- a/internal/lsp/source/folding_range.go +++ b/internal/lsp/source/folding_range.go @@ -16,19 +16,19 @@ type FoldingRangeInfo struct { } // FoldingRange gets all of the folding range for f. -func FoldingRange(ctx context.Context, view View, f File, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) { +func FoldingRange(ctx context.Context, snapshot Snapshot, f File, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) { // TODO(suzmue): consider limiting the number of folding ranges returned, and // implement a way to prioritize folding ranges in that case. - s := view.Snapshot() - fh := s.Handle(ctx, f) - ph := view.Session().Cache().ParseGoHandle(fh, ParseFull) + fh := snapshot.Handle(ctx, f) + ph := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseFull) file, m, _, err := ph.Parse(ctx) if err != nil { return nil, err } + fset := snapshot.View().Session().Cache().FileSet() // Get folding ranges for comments separately as they are not walked by ast.Inspect. - ranges = append(ranges, commentsFoldingRange(view, m, file)...) + ranges = append(ranges, commentsFoldingRange(fset, m, file)...) foldingFunc := foldingRange if lineFoldingOnly { @@ -36,7 +36,7 @@ func FoldingRange(ctx context.Context, view View, f File, lineFoldingOnly bool) } visit := func(n ast.Node) bool { - rng := foldingFunc(view, m, n) + rng := foldingFunc(fset, m, n) if rng != nil { ranges = append(ranges, rng) } @@ -55,7 +55,7 @@ func FoldingRange(ctx context.Context, view View, f File, lineFoldingOnly bool) } // foldingRange calculates the folding range for n. -func foldingRange(view View, m *protocol.ColumnMapper, n ast.Node) *FoldingRangeInfo { +func foldingRange(fset *token.FileSet, m *protocol.ColumnMapper, n ast.Node) *FoldingRangeInfo { var kind protocol.FoldingRangeKind var start, end token.Pos switch n := n.(type) { @@ -85,15 +85,14 @@ func foldingRange(view View, m *protocol.ColumnMapper, n ast.Node) *FoldingRange return &FoldingRangeInfo{ mappedRange: mappedRange{ m: m, - spanRange: span.NewRange(view.Session().Cache().FileSet(), start, end), + spanRange: span.NewRange(fset, start, end), }, Kind: kind, } } // lineFoldingRange calculates the line folding range for n. -func lineFoldingRange(view View, m *protocol.ColumnMapper, n ast.Node) *FoldingRangeInfo { - fset := view.Session().Cache().FileSet() +func lineFoldingRange(fset *token.FileSet, m *protocol.ColumnMapper, n ast.Node) *FoldingRangeInfo { // TODO(suzmue): include trailing empty lines before the closing // parenthesis/brace. @@ -183,7 +182,7 @@ func lineFoldingRange(view View, m *protocol.ColumnMapper, n ast.Node) *FoldingR // commentsFoldingRange returns the folding ranges for all comment blocks in file. // The folding range starts at the end of the first comment, and ends at the end of the // comment block and has kind protocol.Comment. -func commentsFoldingRange(view View, m *protocol.ColumnMapper, file *ast.File) (comments []*FoldingRangeInfo) { +func commentsFoldingRange(fset *token.FileSet, m *protocol.ColumnMapper, file *ast.File) (comments []*FoldingRangeInfo) { for _, commentGrp := range file.Comments { // Don't fold single comments. if len(commentGrp.List) <= 1 { @@ -193,7 +192,7 @@ func commentsFoldingRange(view View, m *protocol.ColumnMapper, file *ast.File) ( mappedRange: mappedRange{ m: m, // Fold from the end of the first line comment to the end of the comment block. - spanRange: span.NewRange(view.Session().Cache().FileSet(), commentGrp.List[0].End(), commentGrp.End()), + spanRange: span.NewRange(fset, commentGrp.List[0].End(), commentGrp.End()), }, Kind: protocol.Comment, }) diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index 20ce807d59..021ec718c2 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -23,11 +23,11 @@ import ( ) // Format formats a file with a given range. -func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error) { +func Format(ctx context.Context, snapshot Snapshot, f File) ([]protocol.TextEdit, error) { ctx, done := trace.StartSpan(ctx, "source.Format") defer done() - snapshot, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } @@ -61,10 +61,10 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error) if err != nil { return nil, err } - return computeTextEdits(ctx, view, ph.File(), m, string(formatted)) + return computeTextEdits(ctx, snapshot.View(), ph.File(), m, string(formatted)) } - fset := view.Session().Cache().FileSet() + fset := snapshot.View().Session().Cache().FileSet() buf := &bytes.Buffer{} // format.Node changes slightly from one release to another, so the version @@ -74,7 +74,7 @@ func Format(ctx context.Context, view View, f File) ([]protocol.TextEdit, error) if err := format.Node(buf, fset, file); err != nil { return nil, err } - return computeTextEdits(ctx, view, ph.File(), m, buf.String()) + return computeTextEdits(ctx, snapshot.View(), ph.File(), m, buf.String()) } func formatSource(ctx context.Context, s Snapshot, f File) ([]byte, error) { @@ -97,11 +97,11 @@ type ImportFix struct { // In addition to returning the result of applying all edits, // it returns a list of fixes that could be applied to the file, with the // corresponding TextEdits that would be needed to apply that fix. -func AllImportsFixes(ctx context.Context, view View, f File) (allFixEdits []protocol.TextEdit, editsPerFix []*ImportFix, err error) { +func AllImportsFixes(ctx context.Context, snapshot Snapshot, f File) (allFixEdits []protocol.TextEdit, editsPerFix []*ImportFix, err error) { ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes") defer done() - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, nil, err } @@ -135,8 +135,8 @@ func AllImportsFixes(ctx context.Context, view View, f File) (allFixEdits []prot TabIndent: true, TabWidth: 8, } - err = view.RunProcessEnvFunc(ctx, func(opts *imports.Options) error { - allFixEdits, editsPerFix, err = computeImportEdits(ctx, view, ph, opts) + err = snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error { + allFixEdits, editsPerFix, err = computeImportEdits(ctx, snapshot.View(), ph, opts) return err }, options) if err != nil { diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go index f08258a562..560956065c 100644 --- a/internal/lsp/source/highlight.go +++ b/internal/lsp/source/highlight.go @@ -10,20 +10,15 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) -func Highlight(ctx context.Context, view View, uri span.URI, pos protocol.Position) ([]protocol.Range, error) { +func Highlight(ctx context.Context, snapshot Snapshot, f File, pos protocol.Position) ([]protocol.Range, error) { ctx, done := trace.StartSpan(ctx, "source.Highlight") defer done() - f, err := view.GetFile(ctx, uri) - if err != nil { - return nil, err - } - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } @@ -79,13 +74,11 @@ func Highlight(ctx context.Context, view View, uri span.URI, pos protocol.Positi if n.Obj != id.Obj { return true } - nodeObj := pkg.GetTypesInfo().ObjectOf(n) if nodeObj != idObj { return false } - - if rng, err := nodeToProtocolRange(ctx, view, m, n); err == nil { + if rng, err := nodeToProtocolRange(ctx, snapshot.View(), m, n); err == nil { result = append(result, rng) } return true diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 48df4ba9b0..5e5f4c8428 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -46,8 +46,8 @@ type Declaration struct { // Identifier returns identifier information for a position // in a file, accounting for a potentially incomplete selector. -func Identifier(ctx context.Context, view View, f File, pos protocol.Position) (*IdentifierInfo, error) { - snapshot, cphs, err := view.CheckPackageHandles(ctx, f) +func Identifier(ctx context.Context, snapshot Snapshot, f File, pos protocol.Position) (*IdentifierInfo, error) { + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index c884edab9e..2b2582a6aa 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -27,11 +27,11 @@ type ParameterInformation struct { Label string } -func SignatureHelp(ctx context.Context, view View, f File, pos protocol.Position) (*SignatureInformation, error) { +func SignatureHelp(ctx context.Context, snapshot Snapshot, f File, pos protocol.Position) (*SignatureInformation, error) { ctx, done := trace.StartSpan(ctx, "source.SignatureHelp") defer done() - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } @@ -97,7 +97,7 @@ FindCall: // Handle builtin functions separately. if obj, ok := obj.(*types.Builtin); ok { - return builtinSignature(ctx, view, callExpr, obj.Name(), rng.Start) + return builtinSignature(ctx, snapshot.View(), callExpr, obj.Name(), rng.Start) } // Get the type information for the function being called. @@ -121,11 +121,11 @@ FindCall: comment *ast.CommentGroup ) if obj != nil { - node, err := objToNode(ctx, view, pkg, obj) + node, err := objToNode(ctx, snapshot.View(), pkg, obj) if err != nil { return nil, err } - rng, err := objToMappedRange(ctx, view, pkg, obj) + rng, err := objToMappedRange(ctx, snapshot.View(), pkg, obj) if err != nil { return nil, err } diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index cdda4cc499..fb825a7e44 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -71,7 +71,7 @@ func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnosti if err != nil { t.Fatal(err) } - results, _, err := source.Diagnostics(r.ctx, r.view, f, nil) + results, _, err := source.Diagnostics(r.ctx, r.view.Snapshot(), f, nil) if err != nil { t.Fatal(err) } @@ -239,7 +239,7 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options source.Comp if err != nil { t.Fatal(err) } - list, surrounding, err := source.Completion(r.ctx, r.view, f, protocol.Position{ + list, surrounding, err := source.Completion(r.ctx, r.view.Snapshot(), f, protocol.Position{ Line: float64(src.Start().Line() - 1), Character: float64(src.Start().Column() - 1), }, options) @@ -288,7 +288,7 @@ func (r *runner) FoldingRanges(t *testing.T, spn span.Span) { } // Test all folding ranges. - ranges, err := source.FoldingRange(r.ctx, r.view, f, false) + ranges, err := source.FoldingRange(r.ctx, r.view.Snapshot(), f, false) if err != nil { t.Error(err) return @@ -296,7 +296,7 @@ func (r *runner) FoldingRanges(t *testing.T, spn span.Span) { r.foldingRanges(t, "foldingRange", uri, string(data), ranges) // Test folding ranges with lineFoldingOnly - ranges, err = source.FoldingRange(r.ctx, r.view, f, true) + ranges, err = source.FoldingRange(r.ctx, r.view.Snapshot(), f, true) if err != nil { t.Error(err) return @@ -424,7 +424,7 @@ func (r *runner) Format(t *testing.T, spn span.Span) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - edits, err := source.Format(ctx, r.view, f) + edits, err := source.Format(ctx, r.view.Snapshot(), f) if err != nil { if gofmted != "" { t.Error(err) @@ -459,7 +459,7 @@ func (r *runner) Import(t *testing.T, spn span.Span) { t.Fatalf("failed for %v: %v", spn, err) } fh := r.view.Snapshot().Handle(r.ctx, f) - edits, _, err := source.AllImportsFixes(ctx, r.view, f) + edits, _, err := source.AllImportsFixes(ctx, r.view.Snapshot(), f) if err != nil { t.Error(err) } @@ -497,7 +497,7 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { if err != nil { t.Fatal(err) } - ident, err := source.Identifier(ctx, r.view, f, srcRng.Start) + ident, err := source.Identifier(ctx, r.view.Snapshot(), f, srcRng.Start) if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } @@ -559,7 +559,7 @@ func (r *runner) Implementation(t *testing.T, spn span.Span, m tests.Implementat if err != nil { t.Fatalf("failed for %v: %v", m.Src, err) } - ident, err := source.Identifier(ctx, r.view, f, loc.Range.Start) + ident, err := source.Identifier(ctx, r.view.Snapshot(), f, loc.Range.Start) if err != nil { t.Fatalf("failed for %v: %v", m.Src, err) } @@ -591,7 +591,11 @@ func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) { if err != nil { t.Fatal(err) } - highlights, err := source.Highlight(ctx, r.view, src.URI(), srcRng.Start) + f, err := r.view.GetFile(ctx, src.URI()) + if err != nil { + t.Fatalf("failed for %v: %v", src, err) + } + highlights, err := source.Highlight(ctx, r.view.Snapshot(), f, srcRng.Start) if err != nil { t.Errorf("highlight failed for %s: %v", src.URI(), err) } @@ -619,7 +623,7 @@ func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) { if err != nil { t.Fatal(err) } - ident, err := source.Identifier(ctx, r.view, f, srcRng.Start) + ident, err := source.Identifier(ctx, r.view.Snapshot(), f, srcRng.Start) if err != nil { t.Fatalf("failed for %v: %v", src, err) } @@ -666,7 +670,7 @@ func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { if err != nil { t.Fatal(err) } - ident, err := source.Identifier(r.ctx, r.view, f, srcRng.Start) + ident, err := source.Identifier(r.ctx, r.view.Snapshot(), f, srcRng.Start) if err != nil { t.Error(err) return @@ -755,7 +759,7 @@ func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.Prepare t.Fatal(err) } // Find the identifier at the position. - ident, err := source.Identifier(ctx, r.view, f, srcRng.Start) + ident, err := source.Identifier(ctx, r.view.Snapshot(), f, srcRng.Start) if err != nil { if want.Text != "" { // expected an ident. t.Errorf("prepare rename failed for %v: got error: %v", src, err) @@ -798,7 +802,7 @@ func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol. if err != nil { t.Fatalf("failed for %v: %v", uri, err) } - symbols, err := source.DocumentSymbols(ctx, r.view, f) + symbols, err := source.DocumentSymbols(ctx, r.view.Snapshot(), f) if err != nil { t.Errorf("symbols failed for %s: %v", uri, err) } @@ -864,7 +868,7 @@ func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *s if err != nil { t.Fatal(err) } - gotSignature, err := source.SignatureHelp(ctx, r.view, f, rng.Start) + gotSignature, err := source.SignatureHelp(ctx, r.view.Snapshot(), f, rng.Start) if err != nil { // Only fail if we got an error we did not expect. if expectedSignature != nil { diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go index 6bb5bf71a1..95f3df330e 100644 --- a/internal/lsp/source/symbols.go +++ b/internal/lsp/source/symbols.go @@ -14,11 +14,11 @@ import ( "golang.org/x/tools/internal/telemetry/trace" ) -func DocumentSymbols(ctx context.Context, view View, f File) ([]protocol.DocumentSymbol, error) { +func DocumentSymbols(ctx context.Context, snapshot Snapshot, f File) ([]protocol.DocumentSymbol, error) { ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols") defer done() - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := snapshot.PackageHandles(ctx, f) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func DocumentSymbols(ctx context.Context, view View, f File) ([]protocol.Documen switch decl := decl.(type) { case *ast.FuncDecl: if obj := info.ObjectOf(decl.Name); obj != nil { - if fs := funcSymbol(ctx, view, m, decl, obj, q); fs.Kind == protocol.Method { + if fs := funcSymbol(ctx, snapshot.View(), m, decl, obj, q); fs.Kind == protocol.Method { // Store methods separately, as we want them to appear as children // of the corresponding type (which we may not have seen yet). rtype := obj.Type().(*types.Signature).Recv().Type() @@ -63,14 +63,14 @@ func DocumentSymbols(ctx context.Context, view View, f File) ([]protocol.Documen switch spec := spec.(type) { case *ast.TypeSpec: if obj := info.ObjectOf(spec.Name); obj != nil { - ts := typeSymbol(ctx, view, m, info, spec, obj, q) + ts := typeSymbol(ctx, snapshot.View(), m, info, spec, obj, q) symbols = append(symbols, ts) symbolsToReceiver[obj.Type()] = len(symbols) - 1 } case *ast.ValueSpec: for _, name := range spec.Names { if obj := info.ObjectOf(name); obj != nil { - symbols = append(symbols, varSymbol(ctx, view, m, decl, name, obj, q)) + symbols = append(symbols, varSymbol(ctx, snapshot.View(), m, decl, name, obj, q)) } } } diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 0f64561315..b41b8cf730 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -18,98 +18,35 @@ import ( "golang.org/x/tools/internal/span" ) -// FileIdentity uniquely identifies a file at a version from a FileSystem. -type FileIdentity struct { - URI span.URI +// Snapshot represents the current state for the given view. +type Snapshot interface { + ID() uint64 - // Version is the version of the file, as specified by the client. - Version float64 + // Handle returns the FileHandle for the given file. + Handle(ctx context.Context, f File) FileHandle - // Identifier represents a unique identifier for the file. - // It could be a file's modification time or its SHA1 hash if it is not on disk. - Identifier string + // View returns the View associated with this snapshot. + View() View - // Kind is the file's kind. - Kind FileKind + // Analyze runs the analyses for the given package at this snapshot. + Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) ([]*Error, error) + + // FindAnalysisError returns the analysis error represented by the diagnostic. + // This is used to get the SuggestedFixes associated with that error. + FindAnalysisError(ctx context.Context, id string, diag protocol.Diagnostic) (*Error, error) + + // PackageHandles returns the PackageHandles for the packages + // that this file belongs to. + PackageHandles(ctx context.Context, f File) ([]CheckPackageHandle, error) + + // KnownImportPaths returns all the imported packages loaded in this snapshot, + // indexed by their import path. + KnownImportPaths() map[string]Package + + // KnownPackages returns all the packages loaded in this snapshot. + KnownPackages(ctx context.Context) []Package } -func (identity FileIdentity) String() string { - return fmt.Sprintf("%s%f%s%s", identity.URI, identity.Version, identity.Identifier, identity.Kind) -} - -// FileHandle represents a handle to a specific version of a single file from -// a specific file system. -type FileHandle interface { - // FileSystem returns the file system this handle was acquired from. - FileSystem() FileSystem - - // Identity returns the FileIdentity for the file. - Identity() FileIdentity - - // Read reads the contents of a file and returns it along with its hash value. - // If the file is not available, returns a nil slice and an error. - Read(ctx context.Context) ([]byte, string, error) -} - -// FileSystem is the interface to something that provides file contents. -type FileSystem interface { - // GetFile returns a handle for the specified file. - GetFile(uri span.URI, kind FileKind) FileHandle -} - -// File represents a source file of any type. -type File interface { - URI() span.URI - Kind() FileKind -} - -// FileKind describes the kind of the file in question. -// It can be one of Go, mod, or sum. -type FileKind int - -const ( - Go = FileKind(iota) - Mod - Sum - UnknownKind -) - -// ParseGoHandle represents a handle to the AST for a file. -type ParseGoHandle interface { - // File returns a file handle for which to get the AST. - File() FileHandle - - // Mode returns the parse mode of this handle. - Mode() ParseMode - - // Parse returns the parsed AST for the file. - // If the file is not available, returns nil and an error. - Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error, error) - - // Cached returns the AST for this handle, if it has already been stored. - Cached() (*ast.File, *protocol.ColumnMapper, error, error) -} - -// ParseMode controls the content of the AST produced when parsing a source file. -type ParseMode int - -const ( - // ParseHeader specifies that the main package declaration and imports are needed. - // This is the mode used when attempting to examine the package graph structure. - ParseHeader = ParseMode(iota) - - // ParseExported specifies that the public symbols are needed, but things like - // private symbols and function bodies are not. - // This mode is used for things where a package is being consumed only as a - // dependency. - ParseExported - - // ParseFull specifies the full AST is needed. - // This is used for files of direct interest where the entire contents must - // be considered. - ParseFull -) - // CheckPackageHandle represents a handle to a specific version of a package. // It is uniquely defined by the file handles that make up the package. type CheckPackageHandle interface { @@ -129,25 +66,69 @@ type CheckPackageHandle interface { MissingDependencies() []string } -// Cache abstracts the core logic of dealing with the environment from the -// higher level logic that processes the information to produce results. -// The cache provides access to files and their contents, so the source -// package does not directly access the file system. -// A single cache is intended to be process wide, and is the primary point of -// sharing between all consumers. -// A cache may have many active sessions at any given time. -type Cache interface { - // A FileSystem that reads file contents from external storage. - FileSystem +// View represents a single workspace. +// This is the level at which we maintain configuration like working directory +// and build tags. +type View interface { + // Session returns the session that created this view. + Session() Session - // NewSession creates a new Session manager and returns it. - NewSession(ctx context.Context) Session + // Name returns the name this view was constructed with. + Name() string - // FileSet returns the shared fileset used by all files in the system. - FileSet() *token.FileSet + // Folder returns the root folder for this view. + Folder() span.URI - // ParseGoHandle returns a ParseGoHandle for the given file handle. - ParseGoHandle(fh FileHandle, mode ParseMode) ParseGoHandle + // BuiltinPackage returns the type information for the special "builtin" package. + BuiltinPackage() BuiltinPackage + + // GetFile returns the file object for a given URI, initializing it + // if it is not already part of the view. + GetFile(ctx context.Context, uri span.URI) (File, error) + + // FindFile returns the file object for a given URI if it is + // already part of the view. + FindFile(ctx context.Context, uri span.URI) File + + // Called to set the effective contents of a file from this view. + SetContent(ctx context.Context, uri span.URI, version float64, content []byte) (wasFirstChange bool, err error) + + // BackgroundContext returns a context used for all background processing + // on behalf of this view. + BackgroundContext() context.Context + + // Shutdown closes this view, and detaches it from it's session. + Shutdown(ctx context.Context) + + // Ignore returns true if this file should be ignored by this view. + Ignore(span.URI) bool + + // Config returns the configuration for the view. + Config(ctx context.Context) *packages.Config + + // RunProcessEnvFunc runs fn with the process env for this view inserted into opts. + // Note: the process env contains cached module and filesystem state. + RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error, opts *imports.Options) error + + // Options returns a copy of the Options for this view. + Options() Options + + // SetOptions sets the options of this view to new values. + // Calling this may cause the view to be invalidated and a replacement view + // added to the session. If so the new view will be returned, otherwise the + // original one will be. + SetOptions(context.Context, Options) (View, error) + + // GetActiveReverseDeps returns the active files belonging to the reverse + // dependencies of this file's package. + GetActiveReverseDeps(ctx context.Context, f File) []CheckPackageHandle + + // FindFileInPackage returns the AST and type information for a file that may + // belong to or be part of a dependency of the given package. + FindPosInPackage(pkg Package, pos token.Pos) (*ast.File, *protocol.ColumnMapper, Package, error) + + // Snapshot returns the current snapshot for the view. + Snapshot() Snapshot } // Session represents a single connection from a client. @@ -214,104 +195,119 @@ const ( UnknownFileAction ) -// View represents a single workspace. -// This is the level at which we maintain configuration like working directory -// and build tags. -type View interface { - // Session returns the session that created this view. - Session() Session +// Cache abstracts the core logic of dealing with the environment from the +// higher level logic that processes the information to produce results. +// The cache provides access to files and their contents, so the source +// package does not directly access the file system. +// A single cache is intended to be process wide, and is the primary point of +// sharing between all consumers. +// A cache may have many active sessions at any given time. +type Cache interface { + // A FileSystem that reads file contents from external storage. + FileSystem - // Name returns the name this view was constructed with. - Name() string + // NewSession creates a new Session manager and returns it. + NewSession(ctx context.Context) Session - // Folder returns the root folder for this view. - Folder() span.URI + // FileSet returns the shared fileset used by all files in the system. + FileSet() *token.FileSet - // BuiltinPackage returns the type information for the special "builtin" package. - BuiltinPackage() BuiltinPackage - - // GetFile returns the file object for a given URI, initializing it - // if it is not already part of the view. - GetFile(ctx context.Context, uri span.URI) (File, error) - - // FindFile returns the file object for a given URI if it is - // already part of the view. - FindFile(ctx context.Context, uri span.URI) File - - // Called to set the effective contents of a file from this view. - SetContent(ctx context.Context, uri span.URI, version float64, content []byte) (wasFirstChange bool, err error) - - // BackgroundContext returns a context used for all background processing - // on behalf of this view. - BackgroundContext() context.Context - - // Shutdown closes this view, and detaches it from it's session. - Shutdown(ctx context.Context) - - // Ignore returns true if this file should be ignored by this view. - Ignore(span.URI) bool - - // Config returns the configuration for the view. - Config(ctx context.Context) *packages.Config - - // RunProcessEnvFunc runs fn with the process env for this view inserted into opts. - // Note: the process env contains cached module and filesystem state. - RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error, opts *imports.Options) error - - // Options returns a copy of the Options for this view. - Options() Options - - // SetOptions sets the options of this view to new values. - // Calling this may cause the view to be invalidated and a replacement view - // added to the session. If so the new view will be returned, otherwise the - // original one will be. - SetOptions(context.Context, Options) (View, error) - - // CheckPackageHandles returns the CheckPackageHandles for the packages - // that this file belongs to. - CheckPackageHandles(ctx context.Context, f File) (Snapshot, []CheckPackageHandle, error) - - // GetActiveReverseDeps returns the active files belonging to the reverse - // dependencies of this file's package. - GetActiveReverseDeps(ctx context.Context, f File) []CheckPackageHandle - - // FindFileInPackage returns the AST and type information for a file that may - // belong to or be part of a dependency of the given package. - FindPosInPackage(pkg Package, pos token.Pos) (*ast.File, *protocol.ColumnMapper, Package, error) - - // Snapshot returns the current snapshot for the view. - Snapshot() Snapshot + // ParseGoHandle returns a ParseGoHandle for the given file handle. + ParseGoHandle(fh FileHandle, mode ParseMode) ParseGoHandle } -// Snapshot represents the current state for the given view. -type Snapshot interface { - ID() uint64 - - // Handle returns the FileHandle for the given file. - Handle(ctx context.Context, f File) FileHandle - - // View returns the View associated with this snapshot. - View() View - - // Analyze runs the analyses for the given package at this snapshot. - Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) ([]*Error, error) - - // FindAnalysisError returns the analysis error represented by the diagnostic. - // This is used to get the SuggestedFixes associated with that error. - FindAnalysisError(ctx context.Context, id string, diag protocol.Diagnostic) (*Error, error) - - // CheckPackageHandles returns the CheckPackageHandles for the packages - // that this file belongs to. - CheckPackageHandles(ctx context.Context, f File) ([]CheckPackageHandle, error) - - // KnownImportPaths returns all the imported packages loaded in this snapshot, - // indexed by their import path. - KnownImportPaths() map[string]Package - - // KnownPackages returns all the packages loaded in this snapshot. - KnownPackages(ctx context.Context) []Package +// FileSystem is the interface to something that provides file contents. +type FileSystem interface { + // GetFile returns a handle for the specified file. + GetFile(uri span.URI, kind FileKind) FileHandle } +// ParseGoHandle represents a handle to the AST for a file. +type ParseGoHandle interface { + // File returns a file handle for which to get the AST. + File() FileHandle + + // Mode returns the parse mode of this handle. + Mode() ParseMode + + // Parse returns the parsed AST for the file. + // If the file is not available, returns nil and an error. + Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error, error) + + // Cached returns the AST for this handle, if it has already been stored. + Cached() (*ast.File, *protocol.ColumnMapper, error, error) +} + +// ParseMode controls the content of the AST produced when parsing a source file. +type ParseMode int + +const ( + // ParseHeader specifies that the main package declaration and imports are needed. + // This is the mode used when attempting to examine the package graph structure. + ParseHeader = ParseMode(iota) + + // ParseExported specifies that the public symbols are needed, but things like + // private symbols and function bodies are not. + // This mode is used for things where a package is being consumed only as a + // dependency. + ParseExported + + // ParseFull specifies the full AST is needed. + // This is used for files of direct interest where the entire contents must + // be considered. + ParseFull +) + +// FileHandle represents a handle to a specific version of a single file from +// a specific file system. +type FileHandle interface { + // FileSystem returns the file system this handle was acquired from. + FileSystem() FileSystem + + // Identity returns the FileIdentity for the file. + Identity() FileIdentity + + // Read reads the contents of a file and returns it along with its hash value. + // If the file is not available, returns a nil slice and an error. + Read(ctx context.Context) ([]byte, string, error) +} + +// FileIdentity uniquely identifies a file at a version from a FileSystem. +type FileIdentity struct { + URI span.URI + + // Version is the version of the file, as specified by the client. + Version float64 + + // Identifier represents a unique identifier for the file. + // It could be a file's modification time or its SHA1 hash if it is not on disk. + Identifier string + + // Kind is the file's kind. + Kind FileKind +} + +func (identity FileIdentity) String() string { + return fmt.Sprintf("%s%f%s%s", identity.URI, identity.Version, identity.Identifier, identity.Kind) +} + +// File represents a source file of any type. +type File interface { + URI() span.URI + Kind() FileKind +} + +// FileKind describes the kind of the file in question. +// It can be one of Go, mod, or sum. +type FileKind int + +const ( + Go = FileKind(iota) + Mod + Sum + UnknownKind +) + type FileURI span.URI func (f FileURI) URI() span.URI { diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go index b4d3a3f1f6..033c6ffbb2 100644 --- a/internal/lsp/symbols.go +++ b/internal/lsp/symbols.go @@ -22,9 +22,10 @@ func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSy if err != nil { return nil, err } + snapshot := view.Snapshot() f, err := view.GetFile(ctx, uri) if err != nil { return nil, err } - return source.DocumentSymbols(ctx, view, f) + return source.DocumentSymbols(ctx, snapshot, f) } diff --git a/internal/lsp/testdata/fuzzy/fuzzy.go b/internal/lsp/testdata/fuzzymatch/fuzzymatch.go similarity index 100% rename from internal/lsp/testdata/fuzzy/fuzzy.go rename to internal/lsp/testdata/fuzzymatch/fuzzymatch.go diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index 90c153b69e..510d96cff3 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -33,9 +33,10 @@ func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocume if err != nil { return err } + snapshot := view.Snapshot() // Run diagnostics on the newly-changed file. - go s.diagnostics(view, uri) + go s.diagnostics(snapshot, uri) return nil } @@ -71,6 +72,8 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo if err != nil { return err } + snapshot := view.Snapshot() + wasFirstChange, err := view.SetContent(ctx, uri, params.TextDocument.Version, []byte(text)) if err != nil { return err @@ -86,7 +89,7 @@ func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDo } // Run diagnostics on the newly-changed file. - go s.diagnostics(view, uri) + go s.diagnostics(snapshot, uri) return nil } @@ -169,7 +172,7 @@ func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocu log.Error(ctx, "no file", err, telemetry.URI) return nil } - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := view.Snapshot().PackageHandles(ctx, f) if err != nil { log.Error(ctx, "no CheckPackageHandles", err, telemetry.URI.Of(uri)) return nil diff --git a/internal/lsp/watched_files.go b/internal/lsp/watched_files.go index de75c5f020..a5efe0511a 100644 --- a/internal/lsp/watched_files.go +++ b/internal/lsp/watched_files.go @@ -34,7 +34,7 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did if s.session.DidChangeOutOfBand(ctx, uri, action) { // If we had been tracking the given file, // recompute diagnostics to reflect updated file contents. - go s.diagnostics(view, uri) + go s.diagnostics(view.Snapshot(), uri) } case source.Delete: f := view.FindFile(ctx, uri) @@ -42,7 +42,7 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did if f == nil { continue } - _, cphs, err := view.CheckPackageHandles(ctx, f) + cphs, err := view.Snapshot().PackageHandles(ctx, f) if err != nil { log.Error(ctx, "didChangeWatchedFiles: CheckPackageHandles", err, telemetry.File) continue @@ -77,7 +77,7 @@ func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.Did } // Refresh diagnostics for the package the file belonged to. - go s.diagnostics(view, otherFile.URI()) + go s.diagnostics(view.Snapshot(), otherFile.URI()) } } } diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go index d78f37cb78..10b082ffd9 100644 --- a/internal/lsp/workspace.go +++ b/internal/lsp/workspace.go @@ -28,7 +28,7 @@ func (s *Server) changeFolders(ctx context.Context, event protocol.WorkspaceFold if err != nil { return err } - go s.diagnoseView(view, cphs) + go s.diagnoseSnapshot(view.Snapshot(), cphs) } return nil }