diff --git a/internal/lsp/cache/builtin.go b/internal/lsp/cache/builtin.go index a313290858..990acef051 100644 --- a/internal/lsp/cache/builtin.go +++ b/internal/lsp/cache/builtin.go @@ -40,7 +40,7 @@ func (view *view) buildBuiltinPackage(ctx context.Context) error { pkg := pkgs[0] files := make(map[string]*ast.File) for _, filename := range pkg.GoFiles { - fh := view.session.GetFile(span.FileURI(filename)) + fh := view.session.GetFile(span.FileURI(filename), source.Go) ph := view.session.cache.ParseGoHandle(fh, source.ParseFull) view.builtin.files = append(view.builtin.files, ph) file, _, _, err := ph.Parse(ctx) diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go index b20712edab..764d077add 100644 --- a/internal/lsp/cache/cache.go +++ b/internal/lsp/cache/cache.go @@ -54,8 +54,8 @@ type fileData struct { err error } -func (c *cache) GetFile(uri span.URI) source.FileHandle { - underlying := c.fs.GetFile(uri) +func (c *cache) GetFile(uri span.URI, kind source.FileKind) source.FileHandle { + underlying := c.fs.GetFile(uri, kind) key := fileKey{ identity: underlying.Identity(), } @@ -96,10 +96,6 @@ func (h *fileHandle) Identity() source.FileIdentity { return h.underlying.Identity() } -func (h *fileHandle) Kind() source.FileKind { - return h.underlying.Kind() -} - func (h *fileHandle) Read(ctx context.Context) ([]byte, string, error) { v := h.handle.Get(ctx) if v == nil { diff --git a/internal/lsp/cache/external.go b/internal/lsp/cache/external.go index 0b84b24890..315a13176b 100644 --- a/internal/lsp/cache/external.go +++ b/internal/lsp/cache/external.go @@ -27,7 +27,7 @@ type nativeFileHandle struct { identity source.FileIdentity } -func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle { +func (fs *nativeFileSystem) GetFile(uri span.URI, kind source.FileKind) source.FileHandle { version := "DOES NOT EXIST" if fi, err := os.Stat(uri.Filename()); err == nil { version = fi.ModTime().String() @@ -37,6 +37,7 @@ func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle { identity: source.FileIdentity{ URI: uri, Version: version, + Kind: kind, }, } } @@ -49,17 +50,12 @@ func (h *nativeFileHandle) Identity() source.FileIdentity { return h.identity } -func (h *nativeFileHandle) Kind() source.FileKind { - // TODO: How should we determine the file kind? - return source.Go -} - func (h *nativeFileHandle) Read(ctx context.Context) ([]byte, string, error) { ctx, done := trace.StartSpan(ctx, "cache.nativeFileHandle.Read", telemetry.File.Of(h.identity.URI.Filename())) defer done() ioLimit <- struct{}{} defer func() { <-ioLimit }() - //TODO: this should fail if the version is not the same as the handle + // TODO: this should fail if the version is not the same as the handle data, err := ioutil.ReadFile(h.identity.URI.Filename()) if err != nil { return nil, "", err diff --git a/internal/lsp/cache/file.go b/internal/lsp/cache/file.go index 19bf46b315..a9748d5336 100644 --- a/internal/lsp/cache/file.go +++ b/internal/lsp/cache/file.go @@ -59,7 +59,7 @@ func (f *fileBase) Handle(ctx context.Context) source.FileHandle { defer f.handleMu.Unlock() if f.handle == nil { - f.handle = f.view.Session().GetFile(f.URI()) + f.handle = f.view.session.GetFile(f.URI(), f.kind) } return f.handle } diff --git a/internal/lsp/cache/gofile.go b/internal/lsp/cache/gofile.go index 7bf6fd5d6d..6c55ef7151 100644 --- a/internal/lsp/cache/gofile.go +++ b/internal/lsp/cache/gofile.go @@ -101,7 +101,7 @@ func (v *view) reverseDeps(ctx context.Context, seen map[packageID]struct{}, res } for _, uri := range m.files { // Call unlocked version of getFile since we hold the lock on the view. - if f, err := v.getFile(ctx, uri); err == nil && v.session.IsOpen(uri) { + if f, err := v.getFile(ctx, uri, source.Go); err == nil && v.session.IsOpen(uri) { results[f.(*goFile)] = struct{}{} } } diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go index 3480366078..bb2477f746 100644 --- a/internal/lsp/cache/load.go +++ b/internal/lsp/cache/load.go @@ -245,7 +245,7 @@ func (v *view) link(ctx context.Context, g *importGraph) error { m.files = append(m.files, span.FileURI(filename)) // Call the unlocked version of getFile since we are holding the view's mutex. - f, err := v.getFile(ctx, span.FileURI(filename)) + f, err := v.getFile(ctx, span.FileURI(filename), source.Go) if err != nil { log.Error(ctx, "no file", err, telemetry.File.Of(filename)) continue diff --git a/internal/lsp/cache/modfile.go b/internal/lsp/cache/modfile.go index 883dba1882..4863e4368b 100644 --- a/internal/lsp/cache/modfile.go +++ b/internal/lsp/cache/modfile.go @@ -4,22 +4,11 @@ package cache -import ( - "context" - "go/token" - - errors "golang.org/x/xerrors" -) - // modFile holds all of the information we know about a mod file. type modFile struct { fileBase } -func (*modFile) GetToken(context.Context) (*token.File, error) { - return nil, errors.Errorf("GetToken: not implemented") -} - func (*modFile) setContent(content []byte) {} func (*modFile) filename() string { return "" } func (*modFile) isActive() bool { return false } diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index 10c25af2c1..e35adddb95 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -193,7 +193,7 @@ func (s *session) removeView(ctx context.Context, view *view) error { } // TODO: Propagate the language ID through to the view. -func (s *session) DidOpen(ctx context.Context, uri span.URI, _ source.FileKind, text []byte) { +func (s *session) DidOpen(ctx context.Context, uri span.URI, kind source.FileKind, text []byte) { ctx = telemetry.File.With(ctx, uri) // Files with _ prefixes are ignored. @@ -211,7 +211,7 @@ func (s *session) DidOpen(ctx context.Context, uri span.URI, _ source.FileKind, // Read the file on disk and compare it to the text provided. // If it is the same as on disk, we can avoid sending it as an overlay to go/packages. - s.openOverlay(ctx, uri, text) + s.openOverlay(ctx, uri, kind, text) // Mark the file as just opened so that we know to re-run packages.Load on it. // We do this because we may not be aware of all of the packages the file belongs to. @@ -254,15 +254,15 @@ func (s *session) IsOpen(uri span.URI) bool { return open } -func (s *session) GetFile(uri span.URI) source.FileHandle { +func (s *session) GetFile(uri span.URI, kind source.FileKind) source.FileHandle { if overlay := s.readOverlay(uri); overlay != nil { return overlay } // Fall back to the cache-level file system. - return s.Cache().GetFile(uri) + return s.cache.GetFile(uri, kind) } -func (s *session) SetOverlay(uri span.URI, data []byte) bool { +func (s *session) SetOverlay(uri span.URI, kind source.FileKind, data []byte) bool { s.overlayMu.Lock() defer func() { s.overlayMu.Unlock() @@ -280,6 +280,7 @@ func (s *session) SetOverlay(uri span.URI, data []byte) bool { s.overlays[uri] = &overlay{ session: s, uri: uri, + kind: kind, data: data, hash: hashContents(data), unchanged: o == nil, @@ -289,7 +290,7 @@ func (s *session) SetOverlay(uri span.URI, data []byte) bool { // openOverlay adds the file content to the overlay. // It also checks if the provided content is equivalent to the file's content on disk. -func (s *session) openOverlay(ctx context.Context, uri span.URI, data []byte) { +func (s *session) openOverlay(ctx context.Context, uri span.URI, kind source.FileKind, data []byte) { s.overlayMu.Lock() defer func() { s.overlayMu.Unlock() @@ -298,11 +299,12 @@ func (s *session) openOverlay(ctx context.Context, uri span.URI, data []byte) { s.overlays[uri] = &overlay{ session: s, uri: uri, + kind: kind, data: data, hash: hashContents(data), unchanged: true, } - _, hash, err := s.cache.GetFile(uri).Read(ctx) + _, hash, err := s.cache.GetFile(uri, kind).Read(ctx) if err != nil { log.Error(ctx, "failed to read", err, telemetry.File) return @@ -356,14 +358,9 @@ func (o *overlay) Identity() source.FileIdentity { return source.FileIdentity{ URI: o.uri, Version: o.hash, + Kind: o.kind, } } - -func (o *overlay) Kind() source.FileKind { - // TODO: Determine the file kind using textDocument.languageId. - return source.Go -} - func (o *overlay) Read(ctx context.Context) ([]byte, string, error) { return o.data, o.hash, nil } diff --git a/internal/lsp/cache/sumfile.go b/internal/lsp/cache/sumfile.go index 21d313cb50..f73171dfbf 100644 --- a/internal/lsp/cache/sumfile.go +++ b/internal/lsp/cache/sumfile.go @@ -4,22 +4,11 @@ package cache -import ( - "context" - "go/token" - - errors "golang.org/x/xerrors" -) - // sumFile holds all of the information we know about a sum file. type sumFile struct { fileBase } -func (*sumFile) GetToken(context.Context) (*token.File, error) { - return nil, errors.Errorf("GetToken: not implemented") -} - func (*sumFile) setContent(content []byte) {} func (*sumFile) filename() string { return "" } func (*sumFile) isActive() bool { return false } diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index b16e34edad..e80b6af874 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -12,7 +12,6 @@ import ( "go/types" "os" "os/exec" - "path/filepath" "strings" "sync" @@ -229,7 +228,7 @@ func (v *view) modFilesChanged() bool { // and modules included by a replace directive. Return true if // any of these file versions do not match. for filename, version := range v.modFileVersions { - if version != v.fileVersion(filename) { + if version != v.fileVersion(filename, source.Mod) { return true } } @@ -248,14 +247,14 @@ func (v *view) storeModFileVersions() { // and modules included by a replace directive in the resolver. for _, mod := range r.ModsByModPath { if (mod.Main || mod.Replace != nil) && mod.GoMod != "" { - v.modFileVersions[mod.GoMod] = v.fileVersion(mod.GoMod) + v.modFileVersions[mod.GoMod] = v.fileVersion(mod.GoMod, source.Mod) } } } -func (v *view) fileVersion(filename string) string { +func (v *view) fileVersion(filename string, kind source.FileKind) string { uri := span.FileURI(filename) - f := v.session.GetFile(uri) + f := v.session.GetFile(uri, kind) return f.Identity().Version } @@ -315,7 +314,8 @@ func (v *view) SetContent(ctx context.Context, uri span.URI, content []byte) (bo v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx) if !v.Ignore(uri) { - return v.session.SetOverlay(uri, content), nil + kind := source.DetectLanguage("", uri.Filename()) + return v.session.SetOverlay(uri, kind, content), nil } return false, nil } @@ -425,32 +425,33 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) { v.mu.Lock() defer v.mu.Unlock() - return v.getFile(ctx, uri) + // TODO(rstambler): Should there be a version that provides a kind explicitly? + kind := source.DetectLanguage("", uri.Filename()) + return v.getFile(ctx, uri, kind) } // getFile is the unlocked internal implementation of GetFile. -func (v *view) getFile(ctx context.Context, uri span.URI) (viewFile, error) { +func (v *view) getFile(ctx context.Context, uri span.URI, kind source.FileKind) (viewFile, error) { if f, err := v.findFile(uri); err != nil { return nil, err } else if f != nil { return f, nil } - filename := uri.Filename() var f viewFile - switch ext := filepath.Ext(filename); ext { - case ".mod": + switch kind { + case source.Mod: f = &modFile{ fileBase: fileBase{ view: v, - fname: filename, + fname: uri.Filename(), kind: source.Mod, }, } - case ".sum": + case source.Sum: f = &sumFile{ fileBase: fileBase{ view: v, - fname: filename, + fname: uri.Filename(), kind: source.Sum, }, } @@ -459,7 +460,7 @@ func (v *view) getFile(ctx context.Context, uri span.URI) (viewFile, error) { f = &goFile{ fileBase: fileBase{ view: v, - fname: filename, + fname: uri.Filename(), kind: source.Go, }, } diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index a8c7e8c963..c1889a5077 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -19,24 +19,6 @@ import ( errors "golang.org/x/xerrors" ) -func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind { - allCodeActionKinds := make(map[protocol.CodeActionKind]struct{}) - for _, kinds := range s.session.Options().SupportedCodeActions { - for kind := range kinds { - allCodeActionKinds[kind] = struct{}{} - } - } - - var result []protocol.CodeActionKind - for kind := range allCodeActionKinds { - result = append(result, kind) - } - sort.Slice(result, func(i, j int) bool { - return result[i] < result[j] - }) - return result -} - func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { uri := span.NewURI(params.TextDocument.URI) view := s.session.ViewOf(uri) @@ -46,7 +28,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara } // Determine the supported actions for this file kind. - fileKind := f.Handle(ctx).Kind() + fileKind := f.Handle(ctx).Identity().Kind supportedCodeActions, ok := view.Options().SupportedCodeActions[fileKind] if !ok { return nil, fmt.Errorf("no supported code actions for %v file kind", fileKind) @@ -68,63 +50,95 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara } var codeActions []protocol.CodeAction - - edits, editsPerFix, err := source.AllImportsFixes(ctx, view, f) - if err != nil { - return nil, err - } - - // If the user wants to see quickfixes. - if wanted[protocol.QuickFix] { - // First, add the quick fixes reported by go/analysis. - gof, ok := f.(source.GoFile) - if !ok { - return nil, fmt.Errorf("%s is not a Go file", f.URI()) + switch fileKind { + case source.Mod: + if !wanted[protocol.SourceOrganizeImports] { + return nil, nil } - qf, err := quickFixes(ctx, view, gof) - if err != nil { - log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri)) - } - codeActions = append(codeActions, qf...) - - // If we also have diagnostics for missing imports, we can associate them with quick fixes. - if findImportErrors(params.Context.Diagnostics) { - // Separate this into a set of codeActions per diagnostic, where - // each action is the addition, removal, or renaming of one import. - for _, importFix := range editsPerFix { - // Get the diagnostics this fix would affect. - if fixDiagnostics := importDiagnostics(importFix.Fix, params.Context.Diagnostics); len(fixDiagnostics) > 0 { - codeActions = append(codeActions, protocol.CodeAction{ - Title: importFixTitle(importFix.Fix), - Kind: protocol.QuickFix, - Edit: &protocol.WorkspaceEdit{ - Changes: &map[string][]protocol.TextEdit{ - string(uri): importFix.Edits, - }, - }, - Diagnostics: fixDiagnostics, - }) - } - } - } - } - - // Add the results of import organization as source.OrganizeImports. - if wanted[protocol.SourceOrganizeImports] { codeActions = append(codeActions, protocol.CodeAction{ - Title: "Organize Imports", + Title: "Tidy", Kind: protocol.SourceOrganizeImports, - Edit: &protocol.WorkspaceEdit{ - Changes: &map[string][]protocol.TextEdit{ - string(uri): edits, + Command: &protocol.Command{ + Title: "Tidy", + Command: "tidy", + Arguments: []interface{}{ + f.URI(), }, }, }) - } + case source.Go: + gof, ok := f.(source.GoFile) + if !ok { + return nil, errors.Errorf("%s is not a Go file", f.URI()) + } + edits, editsPerFix, err := source.AllImportsFixes(ctx, view, gof) + if err != nil { + return nil, err + } + if wanted[protocol.QuickFix] { + // First, add the quick fixes reported by go/analysis. + qf, err := quickFixes(ctx, view, gof) + if err != nil { + log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri)) + } + codeActions = append(codeActions, qf...) + // If we also have diagnostics for missing imports, we can associate them with quick fixes. + if findImportErrors(params.Context.Diagnostics) { + // Separate this into a set of codeActions per diagnostic, where + // each action is the addition, removal, or renaming of one import. + for _, importFix := range editsPerFix { + // Get the diagnostics this fix would affect. + if fixDiagnostics := importDiagnostics(importFix.Fix, params.Context.Diagnostics); len(fixDiagnostics) > 0 { + codeActions = append(codeActions, protocol.CodeAction{ + Title: importFixTitle(importFix.Fix), + Kind: protocol.QuickFix, + Edit: &protocol.WorkspaceEdit{ + Changes: &map[string][]protocol.TextEdit{ + string(uri): importFix.Edits, + }, + }, + Diagnostics: fixDiagnostics, + }) + } + } + } + } + if wanted[protocol.SourceOrganizeImports] { + codeActions = append(codeActions, protocol.CodeAction{ + Title: "Organize Imports", + Kind: protocol.SourceOrganizeImports, + Edit: &protocol.WorkspaceEdit{ + Changes: &map[string][]protocol.TextEdit{ + string(uri): edits, + }, + }, + }) + } + default: + // Unsupported file kind for a code action. + return nil, nil + } return codeActions, nil } +func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind { + allCodeActionKinds := make(map[protocol.CodeActionKind]struct{}) + for _, kinds := range s.session.Options().SupportedCodeActions { + for kind := range kinds { + allCodeActionKinds[kind] = struct{}{} + } + } + var result []protocol.CodeActionKind + for kind := range allCodeActionKinds { + result = append(result, kind) + } + sort.Slice(result, func(i, j int) bool { + return result[i] < result[j] + }) + return result +} + type protocolImportFix struct { fix *imports.ImportFix edits []protocol.TextEdit @@ -189,9 +203,7 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic results = append(results, diagnostic) } } - } - return results } diff --git a/internal/lsp/command.go b/internal/lsp/command.go new file mode 100644 index 0000000000..d6c4a03f4d --- /dev/null +++ b/internal/lsp/command.go @@ -0,0 +1,34 @@ +package lsp + +import ( + "context" + + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" + "golang.org/x/tools/internal/span" + errors "golang.org/x/xerrors" +) + +func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { + switch params.Command { + case "tidy": + if len(params.Arguments) == 0 || len(params.Arguments) > 1 { + return nil, errors.Errorf("expected one file URI for call to `go mod tidy`, got %v", params.Arguments) + } + // Confirm that this action is being taken on a go.mod file. + uri := span.NewURI(params.Arguments[0].(string)) + view := s.session.ViewOf(uri) + f, err := view.GetFile(ctx, uri) + if err != nil { + return nil, err + } + if _, ok := f.(source.ModFile); !ok { + return nil, errors.Errorf("%s is not a mod file", uri) + } + // Run go.mod tidy on the view. + if err := source.ModTidy(ctx, view); err != nil { + return nil, err + } + } + return nil, nil +} diff --git a/internal/lsp/general.go b/internal/lsp/general.go index a531814145..b10e6031de 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -82,12 +82,15 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitia) ( DefinitionProvider: true, DocumentFormattingProvider: true, DocumentSymbolProvider: true, - FoldingRangeProvider: true, - HoverProvider: true, - DocumentHighlightProvider: true, - DocumentLinkProvider: &protocol.DocumentLinkOptions{}, - ReferencesProvider: true, - RenameProvider: renameOpts, + ExecuteCommandProvider: &protocol.ExecuteCommandOptions{ + Commands: options.SupportedCommands, + }, + FoldingRangeProvider: true, + HoverProvider: true, + DocumentHighlightProvider: true, + DocumentLinkProvider: &protocol.DocumentLinkOptions{}, + ReferencesProvider: true, + RenameProvider: renameOpts, SignatureHelpProvider: &protocol.SignatureHelpOptions{ TriggerCharacters: []string{"(", ","}, }, diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index a6a1b604af..5033004be2 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -67,7 +67,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { options.Env = data.Config.Env session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options) for filename, content := range data.Config.Overlay { - session.SetOverlay(span.FileURI(filename), content) + session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content) } r := &runner{ diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 423f734c22..7b514fc547 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -124,8 +124,8 @@ func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]pro return nil, notImplemented("Symbol") } -func (s *Server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) { - return nil, notImplemented("ExecuteCommand") +func (s *Server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { + return s.executeCommand(ctx, params) } // Text Synchronization diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index ad91f40592..746de0ef5f 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -9,7 +9,6 @@ import ( "bytes" "context" "go/format" - "log" "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/imports" @@ -150,15 +149,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) (edits []protocol.TextEdit, editsPerFix []*ImportFix, err error) { +func AllImportsFixes(ctx context.Context, view View, f GoFile) (edits []protocol.TextEdit, editsPerFix []*ImportFix, err error) { ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes") defer done() - gof, ok := f.(GoFile) - if !ok { - return nil, nil, errors.Errorf("no imports fixes for non-Go files: %v", err) - } - cphs, err := gof.CheckPackageHandles(ctx) + cphs, err := f.CheckPackageHandles(ctx) if err != nil { return nil, nil, err } @@ -279,7 +274,6 @@ func hasParseErrors(pkg Package, uri span.URI) bool { func hasListErrors(errors []packages.Error) bool { for _, err := range errors { if err.Kind == packages.ListError { - log.Printf("LIST ERROR: %v", err) return true } } diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index 205ed9ac07..a5819a5f77 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -26,9 +26,14 @@ var ( protocol.SourceOrganizeImports: true, protocol.QuickFix: true, }, - Mod: {}, + Mod: { + protocol.SourceOrganizeImports: true, + }, Sum: {}, }, + SupportedCommands: []string{ + "tidy", // for go.mod files + }, Completion: CompletionOptions{ Documentation: true, Deep: true, @@ -39,7 +44,6 @@ var ( ) type Options struct { - // Env is the current set of environment overrides on this view. Env []string @@ -59,6 +63,8 @@ type Options struct { SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool + SupportedCommands []string + // TODO: Remove the option once we are certain there are no issues here. TextDocumentSyncKind protocol.TextDocumentSyncKind diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index bdc7722670..7e9af00daf 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -57,7 +57,7 @@ func testSource(t *testing.T, exporter packagestest.Exporter) { ctx: ctx, } for filename, content := range data.Config.Overlay { - session.SetOverlay(span.FileURI(filename), content) + session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content) } tests.Run(t, r, data) } diff --git a/internal/lsp/source/tidy.go b/internal/lsp/source/tidy.go new file mode 100644 index 0000000000..476a32a424 --- /dev/null +++ b/internal/lsp/source/tidy.go @@ -0,0 +1,19 @@ +package source + +import ( + "context" +) + +func ModTidy(ctx context.Context, view View) error { + cfg := view.Config(ctx) + + // Running `go mod tidy` modifies the file on disk directly. + // Ideally, we should return modules that could possibly be removed + // and apply each action as an edit. + // + // TODO(rstambler): This will be possible when golang/go#27005 is resolved. + if _, err := invokeGo(ctx, view.Folder().Filename(), cfg.Env, "mod", "tidy"); err != nil { + return err + } + return nil +} diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 2e89c1f32a..425e7a58e1 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -22,10 +22,11 @@ import ( type FileIdentity struct { URI span.URI Version string + Kind FileKind } func (identity FileIdentity) String() string { - return fmt.Sprintf("%s%s", identity.URI, identity.Version) + return fmt.Sprintf("%s%s%s", identity.URI, identity.Version, identity.Kind) } // FileHandle represents a handle to a specific version of a single file from @@ -37,9 +38,6 @@ type FileHandle interface { // Identity returns the FileIdentity for the file. Identity() FileIdentity - // Kind returns the FileKind for the file. - Kind() FileKind - // 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) @@ -48,7 +46,7 @@ type FileHandle interface { // 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) FileHandle + GetFile(uri span.URI, kind FileKind) FileHandle } // FileKind describes the kind of the file in question. @@ -59,6 +57,7 @@ const ( Go = FileKind(iota) Mod Sum + UnknownKind ) // TokenHandle represents a handle to the *token.File for a file. @@ -189,7 +188,7 @@ type Session interface { IsOpen(uri span.URI) bool // Called to set the effective contents of a file from this session. - SetOverlay(uri span.URI, data []byte) (wasFirstChange bool) + SetOverlay(uri span.URI, kind FileKind, data []byte) (wasFirstChange bool) // DidChangeOutOfBand is called when a file under the root folder // changes. The file is not necessarily open in the editor. diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go index 9e9072a62a..201b1423b3 100644 --- a/internal/lsp/text_synchronization.go +++ b/internal/lsp/text_synchronization.go @@ -96,7 +96,7 @@ func fullChange(changes []protocol.TextDocumentContentChangeEvent) (string, bool } func (s *Server) applyChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) (string, error) { - content, _, err := s.session.GetFile(uri).Read(ctx) + content, _, err := s.session.GetFile(uri, source.UnknownKind).Read(ctx) if err != nil { return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err) }