1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:04:46 -07:00

internal/lsp: support running go mod tidy as a code action

This changes adds basic support for running `go mod tidy` as a code
action when a user opens a go.mod file. When we have a command
available like `go mod tidy -check`, we will be able to return edits as
part of the codeAction. For now, we execute the command directly.

This change also required a few modifications to our handling of file
kinds so that we could distinguish between a Go file and a go.mod file.

Change-Id: I343079b8886724b67f90a314e45639545a34f21e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/196322
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-09-19 01:21:54 -04:00
parent c426260dee
commit 1081e67f6b
21 changed files with 197 additions and 162 deletions

View File

@ -40,7 +40,7 @@ func (view *view) buildBuiltinPackage(ctx context.Context) error {
pkg := pkgs[0] pkg := pkgs[0]
files := make(map[string]*ast.File) files := make(map[string]*ast.File)
for _, filename := range pkg.GoFiles { 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) ph := view.session.cache.ParseGoHandle(fh, source.ParseFull)
view.builtin.files = append(view.builtin.files, ph) view.builtin.files = append(view.builtin.files, ph)
file, _, _, err := ph.Parse(ctx) file, _, _, err := ph.Parse(ctx)

View File

@ -54,8 +54,8 @@ type fileData struct {
err error err error
} }
func (c *cache) GetFile(uri span.URI) source.FileHandle { func (c *cache) GetFile(uri span.URI, kind source.FileKind) source.FileHandle {
underlying := c.fs.GetFile(uri) underlying := c.fs.GetFile(uri, kind)
key := fileKey{ key := fileKey{
identity: underlying.Identity(), identity: underlying.Identity(),
} }
@ -96,10 +96,6 @@ func (h *fileHandle) Identity() source.FileIdentity {
return h.underlying.Identity() return h.underlying.Identity()
} }
func (h *fileHandle) Kind() source.FileKind {
return h.underlying.Kind()
}
func (h *fileHandle) Read(ctx context.Context) ([]byte, string, error) { func (h *fileHandle) Read(ctx context.Context) ([]byte, string, error) {
v := h.handle.Get(ctx) v := h.handle.Get(ctx)
if v == nil { if v == nil {

View File

@ -27,7 +27,7 @@ type nativeFileHandle struct {
identity source.FileIdentity 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" version := "DOES NOT EXIST"
if fi, err := os.Stat(uri.Filename()); err == nil { if fi, err := os.Stat(uri.Filename()); err == nil {
version = fi.ModTime().String() version = fi.ModTime().String()
@ -37,6 +37,7 @@ func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle {
identity: source.FileIdentity{ identity: source.FileIdentity{
URI: uri, URI: uri,
Version: version, Version: version,
Kind: kind,
}, },
} }
} }
@ -49,11 +50,6 @@ func (h *nativeFileHandle) Identity() source.FileIdentity {
return h.identity 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) { 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())) ctx, done := trace.StartSpan(ctx, "cache.nativeFileHandle.Read", telemetry.File.Of(h.identity.URI.Filename()))
defer done() defer done()

View File

@ -59,7 +59,7 @@ func (f *fileBase) Handle(ctx context.Context) source.FileHandle {
defer f.handleMu.Unlock() defer f.handleMu.Unlock()
if f.handle == nil { 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 return f.handle
} }

View File

@ -101,7 +101,7 @@ func (v *view) reverseDeps(ctx context.Context, seen map[packageID]struct{}, res
} }
for _, uri := range m.files { for _, uri := range m.files {
// Call unlocked version of getFile since we hold the lock on the view. // 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{}{} results[f.(*goFile)] = struct{}{}
} }
} }

View File

@ -245,7 +245,7 @@ func (v *view) link(ctx context.Context, g *importGraph) error {
m.files = append(m.files, span.FileURI(filename)) m.files = append(m.files, span.FileURI(filename))
// Call the unlocked version of getFile since we are holding the view's mutex. // 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 { if err != nil {
log.Error(ctx, "no file", err, telemetry.File.Of(filename)) log.Error(ctx, "no file", err, telemetry.File.Of(filename))
continue continue

View File

@ -4,22 +4,11 @@
package cache package cache
import (
"context"
"go/token"
errors "golang.org/x/xerrors"
)
// modFile holds all of the information we know about a mod file. // modFile holds all of the information we know about a mod file.
type modFile struct { type modFile struct {
fileBase fileBase
} }
func (*modFile) GetToken(context.Context) (*token.File, error) {
return nil, errors.Errorf("GetToken: not implemented")
}
func (*modFile) setContent(content []byte) {} func (*modFile) setContent(content []byte) {}
func (*modFile) filename() string { return "" } func (*modFile) filename() string { return "" }
func (*modFile) isActive() bool { return false } func (*modFile) isActive() bool { return false }

View File

@ -193,7 +193,7 @@ func (s *session) removeView(ctx context.Context, view *view) error {
} }
// TODO: Propagate the language ID through to the view. // 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) ctx = telemetry.File.With(ctx, uri)
// Files with _ prefixes are ignored. // 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. // 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. // 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. // 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. // 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 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 { if overlay := s.readOverlay(uri); overlay != nil {
return overlay return overlay
} }
// Fall back to the cache-level file system. // 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() s.overlayMu.Lock()
defer func() { defer func() {
s.overlayMu.Unlock() s.overlayMu.Unlock()
@ -280,6 +280,7 @@ func (s *session) SetOverlay(uri span.URI, data []byte) bool {
s.overlays[uri] = &overlay{ s.overlays[uri] = &overlay{
session: s, session: s,
uri: uri, uri: uri,
kind: kind,
data: data, data: data,
hash: hashContents(data), hash: hashContents(data),
unchanged: o == nil, 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. // openOverlay adds the file content to the overlay.
// It also checks if the provided content is equivalent to the file's content on disk. // 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() s.overlayMu.Lock()
defer func() { defer func() {
s.overlayMu.Unlock() s.overlayMu.Unlock()
@ -298,11 +299,12 @@ func (s *session) openOverlay(ctx context.Context, uri span.URI, data []byte) {
s.overlays[uri] = &overlay{ s.overlays[uri] = &overlay{
session: s, session: s,
uri: uri, uri: uri,
kind: kind,
data: data, data: data,
hash: hashContents(data), hash: hashContents(data),
unchanged: true, unchanged: true,
} }
_, hash, err := s.cache.GetFile(uri).Read(ctx) _, hash, err := s.cache.GetFile(uri, kind).Read(ctx)
if err != nil { if err != nil {
log.Error(ctx, "failed to read", err, telemetry.File) log.Error(ctx, "failed to read", err, telemetry.File)
return return
@ -356,14 +358,9 @@ func (o *overlay) Identity() source.FileIdentity {
return source.FileIdentity{ return source.FileIdentity{
URI: o.uri, URI: o.uri,
Version: o.hash, 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) { func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
return o.data, o.hash, nil return o.data, o.hash, nil
} }

View File

@ -4,22 +4,11 @@
package cache package cache
import (
"context"
"go/token"
errors "golang.org/x/xerrors"
)
// sumFile holds all of the information we know about a sum file. // sumFile holds all of the information we know about a sum file.
type sumFile struct { type sumFile struct {
fileBase fileBase
} }
func (*sumFile) GetToken(context.Context) (*token.File, error) {
return nil, errors.Errorf("GetToken: not implemented")
}
func (*sumFile) setContent(content []byte) {} func (*sumFile) setContent(content []byte) {}
func (*sumFile) filename() string { return "" } func (*sumFile) filename() string { return "" }
func (*sumFile) isActive() bool { return false } func (*sumFile) isActive() bool { return false }

View File

@ -12,7 +12,6 @@ import (
"go/types" "go/types"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"sync" "sync"
@ -229,7 +228,7 @@ func (v *view) modFilesChanged() bool {
// and modules included by a replace directive. Return true if // and modules included by a replace directive. Return true if
// any of these file versions do not match. // any of these file versions do not match.
for filename, version := range v.modFileVersions { for filename, version := range v.modFileVersions {
if version != v.fileVersion(filename) { if version != v.fileVersion(filename, source.Mod) {
return true return true
} }
} }
@ -248,14 +247,14 @@ func (v *view) storeModFileVersions() {
// and modules included by a replace directive in the resolver. // and modules included by a replace directive in the resolver.
for _, mod := range r.ModsByModPath { for _, mod := range r.ModsByModPath {
if (mod.Main || mod.Replace != nil) && mod.GoMod != "" { 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) uri := span.FileURI(filename)
f := v.session.GetFile(uri) f := v.session.GetFile(uri, kind)
return f.Identity().Version 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) v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
if !v.Ignore(uri) { 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 return false, nil
} }
@ -425,32 +425,33 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) {
v.mu.Lock() v.mu.Lock()
defer v.mu.Unlock() 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. // 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 { if f, err := v.findFile(uri); err != nil {
return nil, err return nil, err
} else if f != nil { } else if f != nil {
return f, nil return f, nil
} }
filename := uri.Filename()
var f viewFile var f viewFile
switch ext := filepath.Ext(filename); ext { switch kind {
case ".mod": case source.Mod:
f = &modFile{ f = &modFile{
fileBase: fileBase{ fileBase: fileBase{
view: v, view: v,
fname: filename, fname: uri.Filename(),
kind: source.Mod, kind: source.Mod,
}, },
} }
case ".sum": case source.Sum:
f = &sumFile{ f = &sumFile{
fileBase: fileBase{ fileBase: fileBase{
view: v, view: v,
fname: filename, fname: uri.Filename(),
kind: source.Sum, kind: source.Sum,
}, },
} }
@ -459,7 +460,7 @@ func (v *view) getFile(ctx context.Context, uri span.URI) (viewFile, error) {
f = &goFile{ f = &goFile{
fileBase: fileBase{ fileBase: fileBase{
view: v, view: v,
fname: filename, fname: uri.Filename(),
kind: source.Go, kind: source.Go,
}, },
} }

View File

@ -19,24 +19,6 @@ import (
errors "golang.org/x/xerrors" 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) { func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
uri := span.NewURI(params.TextDocument.URI) uri := span.NewURI(params.TextDocument.URI)
view := s.session.ViewOf(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. // 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] supportedCodeActions, ok := view.Options().SupportedCodeActions[fileKind]
if !ok { if !ok {
return nil, fmt.Errorf("no supported code actions for %v file kind", fileKind) return nil, fmt.Errorf("no supported code actions for %v file kind", fileKind)
@ -68,19 +50,33 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
} }
var codeActions []protocol.CodeAction var codeActions []protocol.CodeAction
switch fileKind {
edits, editsPerFix, err := source.AllImportsFixes(ctx, view, f) case source.Mod:
if !wanted[protocol.SourceOrganizeImports] {
return nil, nil
}
codeActions = append(codeActions, protocol.CodeAction{
Title: "Tidy",
Kind: protocol.SourceOrganizeImports,
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 { if err != nil {
return nil, err return nil, err
} }
// If the user wants to see quickfixes.
if wanted[protocol.QuickFix] { if wanted[protocol.QuickFix] {
// First, add the quick fixes reported by go/analysis. // 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())
}
qf, err := quickFixes(ctx, view, gof) qf, err := quickFixes(ctx, view, gof)
if err != nil { if err != nil {
log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri)) log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri))
@ -108,8 +104,6 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
} }
} }
} }
// Add the results of import organization as source.OrganizeImports.
if wanted[protocol.SourceOrganizeImports] { if wanted[protocol.SourceOrganizeImports] {
codeActions = append(codeActions, protocol.CodeAction{ codeActions = append(codeActions, protocol.CodeAction{
Title: "Organize Imports", Title: "Organize Imports",
@ -121,10 +115,30 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
}, },
}) })
} }
default:
// Unsupported file kind for a code action.
return nil, nil
}
return codeActions, 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 { type protocolImportFix struct {
fix *imports.ImportFix fix *imports.ImportFix
edits []protocol.TextEdit edits []protocol.TextEdit
@ -189,9 +203,7 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic
results = append(results, diagnostic) results = append(results, diagnostic)
} }
} }
} }
return results return results
} }

34
internal/lsp/command.go Normal file
View File

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

View File

@ -82,6 +82,9 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitia) (
DefinitionProvider: true, DefinitionProvider: true,
DocumentFormattingProvider: true, DocumentFormattingProvider: true,
DocumentSymbolProvider: true, DocumentSymbolProvider: true,
ExecuteCommandProvider: &protocol.ExecuteCommandOptions{
Commands: options.SupportedCommands,
},
FoldingRangeProvider: true, FoldingRangeProvider: true,
HoverProvider: true, HoverProvider: true,
DocumentHighlightProvider: true, DocumentHighlightProvider: true,

View File

@ -67,7 +67,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
options.Env = data.Config.Env options.Env = data.Config.Env
session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options) session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
for filename, content := range data.Config.Overlay { for filename, content := range data.Config.Overlay {
session.SetOverlay(span.FileURI(filename), content) session.SetOverlay(span.FileURI(filename), source.DetectLanguage("", filename), content)
} }
r := &runner{ r := &runner{

View File

@ -124,8 +124,8 @@ func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]pro
return nil, notImplemented("Symbol") return nil, notImplemented("Symbol")
} }
func (s *Server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) { func (s *Server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
return nil, notImplemented("ExecuteCommand") return s.executeCommand(ctx, params)
} }
// Text Synchronization // Text Synchronization

View File

@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"context" "context"
"go/format" "go/format"
"log"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/imports" "golang.org/x/tools/internal/imports"
@ -150,15 +149,11 @@ type ImportFix struct {
// In addition to returning the result of applying all edits, // 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 // 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. // 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") ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes")
defer done() defer done()
gof, ok := f.(GoFile) cphs, err := f.CheckPackageHandles(ctx)
if !ok {
return nil, nil, errors.Errorf("no imports fixes for non-Go files: %v", err)
}
cphs, err := gof.CheckPackageHandles(ctx)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -279,7 +274,6 @@ func hasParseErrors(pkg Package, uri span.URI) bool {
func hasListErrors(errors []packages.Error) bool { func hasListErrors(errors []packages.Error) bool {
for _, err := range errors { for _, err := range errors {
if err.Kind == packages.ListError { if err.Kind == packages.ListError {
log.Printf("LIST ERROR: %v", err)
return true return true
} }
} }

View File

@ -26,9 +26,14 @@ var (
protocol.SourceOrganizeImports: true, protocol.SourceOrganizeImports: true,
protocol.QuickFix: true, protocol.QuickFix: true,
}, },
Mod: {}, Mod: {
protocol.SourceOrganizeImports: true,
},
Sum: {}, Sum: {},
}, },
SupportedCommands: []string{
"tidy", // for go.mod files
},
Completion: CompletionOptions{ Completion: CompletionOptions{
Documentation: true, Documentation: true,
Deep: true, Deep: true,
@ -39,7 +44,6 @@ var (
) )
type Options struct { type Options struct {
// Env is the current set of environment overrides on this view. // Env is the current set of environment overrides on this view.
Env []string Env []string
@ -59,6 +63,8 @@ type Options struct {
SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool
SupportedCommands []string
// TODO: Remove the option once we are certain there are no issues here. // TODO: Remove the option once we are certain there are no issues here.
TextDocumentSyncKind protocol.TextDocumentSyncKind TextDocumentSyncKind protocol.TextDocumentSyncKind

View File

@ -57,7 +57,7 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
ctx: ctx, ctx: ctx,
} }
for filename, content := range data.Config.Overlay { 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) tests.Run(t, r, data)
} }

View File

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

View File

@ -22,10 +22,11 @@ import (
type FileIdentity struct { type FileIdentity struct {
URI span.URI URI span.URI
Version string Version string
Kind FileKind
} }
func (identity FileIdentity) String() string { 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 // 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 returns the FileIdentity for the file.
Identity() FileIdentity 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. // 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. // If the file is not available, returns a nil slice and an error.
Read(ctx context.Context) ([]byte, string, 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. // FileSystem is the interface to something that provides file contents.
type FileSystem interface { type FileSystem interface {
// GetFile returns a handle for the specified file. // 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. // FileKind describes the kind of the file in question.
@ -59,6 +57,7 @@ const (
Go = FileKind(iota) Go = FileKind(iota)
Mod Mod
Sum Sum
UnknownKind
) )
// TokenHandle represents a handle to the *token.File for a file. // TokenHandle represents a handle to the *token.File for a file.
@ -189,7 +188,7 @@ type Session interface {
IsOpen(uri span.URI) bool IsOpen(uri span.URI) bool
// Called to set the effective contents of a file from this session. // 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 // DidChangeOutOfBand is called when a file under the root folder
// changes. The file is not necessarily open in the editor. // changes. The file is not necessarily open in the editor.

View File

@ -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) { 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 { if err != nil {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err) return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found (%v)", err)
} }