Skip to content

Commit

Permalink
deps: upgrade x/tools and gopls to 77f530d86f9a (#913)
Browse files Browse the repository at this point in the history
* internal/lsp: handle deletion of file content 77f530d8
* internal/lsp: move fillstruct suggested fixes out of analysis 4025ed84
* internal/lsp: support extract function 6d307edf
* internal/lsp: split only on the first = for Go environment variables 9cbb971a
* internal/lsp: support go1.12 cf97b7f4
* internal/lsp: don't re-run `go env` for every call to WriteEnv e6549b8d

The update also updates the fillstruct logic according to recent changes
in gopls where it now return Commands instead of direct edits.

As gopls went from edits to commands, it will not be possible to
fill more than one struct per fillstruct call.

This also adds support for incoming LSP ApplyEdit calls, to be able
to use LSP ExecuteCommand.
  • Loading branch information
leitzler authored Jul 29, 2020
1 parent 8e423df commit c6e168a
Show file tree
Hide file tree
Showing 23 changed files with 1,198 additions and 220 deletions.
124 changes: 103 additions & 21 deletions cmd/govim/fillstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/govim/govim"
"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/protocol"
"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/span"
"github.com/govim/govim/cmd/govim/internal/types"
)

func (v *vimstate) fillStruct(flags govim.CommandFlags, args ...string) error {
Expand All @@ -34,33 +35,114 @@ func (v *vimstate) fillStruct(flags govim.CommandFlags, args ...string) error {
return nil
}

buri := b.URI()
var edits []protocol.TextEdit
for _, ca := range codeActions {
// there should be just a single file
dcs := ca.Edit.DocumentChanges
switch len(dcs) {
case 1:
dc := dcs[0]
// verify that the URI and version of the edits match the buffer
euri := span.URI(dc.TextDocument.TextDocumentIdentifier.URI)
if euri != buri {
return fmt.Errorf("got edits for file %v, but buffer is %v", euri, buri)
// TODO: revisit this logic when gopls enables us to distingush two different
// code action responses.
//
// The response from gopls contain Commands, but gopls currently responds
// with all code actions at the current line (instead of the exact range
// passed as parameter).
//
// If there are more than one command returned, we can't apply them all since
// each one of them contains unspecified parameters that are bound to current
// version of the document.

// The gopls ExecuteCommand is blocking, and gopls will call back to govim
// using ApplyEdit that must be handled before the blocking is released.
// Since fillstruct is ordered by the user (and the single threaded nature
// of vim), we are effectively blocking ApplyEdit from modifying buffers.
//
// To prevent a deadlock, we create a channel that ApplyEdit can use pass
// edits to this thread (if needed). And then call ExecuteCommand in a
// separate goroutine so that this thread can go on updating buffers
// until the ExecuteCommand is released. When it is, we implicitly know
// that ApplyEdit has been processed.
editsCh := make(chan applyEditCall)
v.govimplugin.applyEditsLock.Lock()
v.govimplugin.applyEditsCh = editsCh
v.govimplugin.applyEditsLock.Unlock()
done := make(chan struct{})

var ecErr error
v.tomb.Go(func() error {
// We can only apply one command at the moment since they all target the same document
// version. Let's go for the first one and let the user call fillstruct again if they
// want to fill several structs on the same line.
ca := codeActions[0]
_, ecErr = v.server.ExecuteCommand(context.Background(), &protocol.ExecuteCommandParams{
Command: ca.Command.Command,
Arguments: ca.Command.Arguments,
WorkDoneProgressParams: protocol.WorkDoneProgressParams{},
})

v.govimplugin.applyEditsLock.Lock()
v.govimplugin.applyEditsCh = nil
v.govimplugin.applyEditsLock.Unlock()
close(done)
return nil
})

for {
select {
case <-done:
if ecErr != nil {
return fmt.Errorf("executeCommand failed: %v", ecErr)
}
return nil
case c := <-editsCh:
res, err := v.applyWorkspaceEdit(c.params)
c.responseCh <- applyEditResponse{res, err}
}
}
}

// applyEditCall represents a single LSP ApplyEdit call including a channel used
// to pass a response back.
type applyEditCall struct {
params *protocol.ApplyWorkspaceEditParams
responseCh chan applyEditResponse
}

// applyEditResponse represents a LSP ApplyEdit response
type applyEditResponse struct {
res *protocol.ApplyWorkspaceEditResponse
err error
}

func (v *vimstate) applyWorkspaceEdit(params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
res := &protocol.ApplyWorkspaceEditResponse{Applied: true}

edits := make(map[*types.Buffer][]protocol.TextEdit)
for _, dc := range params.Edit.DocumentChanges {
// verify that the version of the edits matches a buffer
var buf *types.Buffer
for _, b := range v.buffers {
if b.URI() != span.URI(dc.TextDocument.URI) {
continue
}

if ev := int(math.Round(dc.TextDocument.Version)); ev > 0 && ev != b.Version {
return fmt.Errorf("got edits for version %v, but current buffer version is %v", ev, b.Version)
return nil, fmt.Errorf("got edits for buffer version %v, found matching buffer with version %v", ev, b.Version)
}
edits = append(edits, dc.Edits...)
default:
return fmt.Errorf("expected single file, saw: %v", len(dcs))

buf = b
}
}

if len(edits) != 0 {
if err := v.applyProtocolTextEdits(b, edits); err != nil {
return err
if buf == nil {
// TODO: we might get edits for files that we don't have open so we need to support that
// as well. For fillstruct this isn't an issue since the user calls it within an open file.
res.FailureReason = fmt.Sprintf("got edits for buffer %v, but didn't find it", dc.TextDocument.URI)
res.Applied = false
return res, nil
}
edits[buf] = append(edits[buf], dc.Edits...)
}

return nil
for b, e := range edits {
if err := v.applyProtocolTextEdits(b, e); err != nil {
res.FailureReason = err.Error()
res.Applied = false
return res, nil
}
}
return res, nil
}
32 changes: 30 additions & 2 deletions cmd/govim/gopls_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,37 @@ func (g *govimplugin) Configuration(ctxt context.Context, params *protocol.Param
return res, nil
}

func (g *govimplugin) ApplyEdit(context.Context, *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
func (g *govimplugin) ApplyEdit(ctxt context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
defer absorbShutdownErr()
panic("ApplyEdit not implemented yet")
g.logGoplsClientf("ApplyEdit: %v", pretty.Sprint(params))

var err error
var res *protocol.ApplyWorkspaceEditResponse
g.applyEditsLock.Lock()
if g.applyEditsCh == nil {
// ApplyEdit wasn't send by another blocking call so it's fine to just schedule the edits here
g.applyEditsLock.Unlock()
done := make(chan struct{})
g.Schedule(func(govim.Govim) error {
v := g.vimstate
res, err = v.applyWorkspaceEdit(params)
close(done)
return nil
})
<-done
} else {
// There is an ongoing call that can apply edits on the vim thread. A Schedule here would deadlock so
// pass the edits to the vim thread instead.
e := applyEditCall{params: params, responseCh: make(chan applyEditResponse)}
g.applyEditsCh <- e
aer := <-e.responseCh
g.applyEditsLock.Unlock()
res = aer.res
err = aer.err
}

g.logGoplsClientf("ApplyEdit response: %v", pretty.Sprint(res))
return res, err
}

func (g *govimplugin) Event(context.Context, *interface{}) error {
Expand Down
93 changes: 87 additions & 6 deletions cmd/govim/internal/golang_org_x_tools/analysisinternal/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,95 @@ func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
default:
return ast.NewIdent(t.Name())
}
case *types.Pointer:
x := TypeExpr(fset, f, pkg, t.Elem())
if x == nil {
return nil
}
return &ast.UnaryExpr{
Op: token.MUL,
X: x,
}
case *types.Array:
elt := TypeExpr(fset, f, pkg, t.Elem())
if elt == nil {
return nil
}
return &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: fmt.Sprintf("%d", t.Len()),
},
Elt: elt,
}
case *types.Slice:
elt := TypeExpr(fset, f, pkg, t.Elem())
if elt == nil {
return nil
}
return &ast.ArrayType{
Elt: elt,
}
case *types.Map:
key := TypeExpr(fset, f, pkg, t.Key())
value := TypeExpr(fset, f, pkg, t.Elem())
if key == nil || value == nil {
return nil
}
return &ast.MapType{
Key: key,
Value: value,
}
case *types.Chan:
dir := ast.ChanDir(t.Dir())
if t.Dir() == types.SendRecv {
dir = ast.SEND | ast.RECV
}
value := TypeExpr(fset, f, pkg, t.Elem())
if value == nil {
return nil
}
return &ast.ChanType{
Dir: dir,
Value: value,
}
case *types.Signature:
var params []*ast.Field
for i := 0; i < t.Params().Len(); i++ {
p := TypeExpr(fset, f, pkg, t.Params().At(i).Type())
if p == nil {
return nil
}
params = append(params, &ast.Field{
Type: p,
Names: []*ast.Ident{
{
Name: t.Params().At(i).Name(),
},
},
})
}
var returns []*ast.Field
for i := 0; i < t.Results().Len(); i++ {
r := TypeExpr(fset, f, pkg, t.Results().At(i).Type())
if r == nil {
return nil
}
returns = append(returns, &ast.Field{
Type: r,
})
}
return &ast.FuncType{
Params: &ast.FieldList{
List: params,
},
Results: &ast.FieldList{
List: returns,
},
}
case *types.Named:
if t.Obj().Pkg() == nil {
return nil
return ast.NewIdent(t.Obj().Name())
}
if t.Obj().Pkg() == pkg {
return ast.NewIdent(t.Obj().Name())
Expand All @@ -109,11 +195,6 @@ func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
X: ast.NewIdent(pkgName),
Sel: ast.NewIdent(t.Obj().Name()),
}
case *types.Pointer:
return &ast.UnaryExpr{
Op: token.MUL,
X: TypeExpr(fset, f, pkg, t.Elem()),
}
default:
return nil // TODO: anonymous structs, but who does that
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/govim/internal/golang_org_x_tools/jsonrpc2/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ package jsonrpc2

import (
"encoding/json"
"errors"
"fmt"

errors "golang.org/x/xerrors"
)

// Message is the interface to all jsonrpc2 message types.
Expand Down
2 changes: 1 addition & 1 deletion cmd/govim/internal/golang_org_x_tools/jsonrpc2/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ package jsonrpc2

import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"time"

"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/event"
errors "golang.org/x/xerrors"
)

// NOTE: This file provides an experimental API for serving multiple remote
Expand Down
Loading

0 comments on commit c6e168a

Please sign in to comment.