all: merge master into gopls-release-branch.0.4
Change-Id: Icf1ae4d1f86ca3b24481d4755d511e82c5f9e609
diff --git a/internal/jsonrpc2/handler.go b/internal/jsonrpc2/handler.go
index 8351579..1382b5e 100644
--- a/internal/jsonrpc2/handler.go
+++ b/internal/jsonrpc2/handler.go
@@ -6,31 +6,17 @@
import (
"context"
+ "fmt"
+ "sync"
+
+ "golang.org/x/tools/internal/telemetry/event"
)
-// Handler is the interface used to hook into the message handling of an rpc
-// connection.
-type Handler interface {
- // Deliver is invoked to handle incoming requests.
- // If the request returns false from IsNotify then the Handler must eventually
- // call Reply on the Conn with the supplied request.
- // Handlers are called synchronously, they should pass the work off to a go
- // routine if they are going to take a long time.
- // If Deliver returns true all subsequent handlers will be invoked with
- // delivered set to true, and should not attempt to deliver the message.
- Deliver(ctx context.Context, r *Request, delivered bool) bool
-
- // Cancel is invoked for cancelled outgoing requests.
- // It is okay to use the connection to send notifications, but the context will
- // be in the cancelled state, so you must do it with the background context
- // instead.
- // If Cancel returns true all subsequent handlers will be invoked with
- // cancelled set to true, and should not attempt to cancel the message.
- Cancel(ctx context.Context, conn *Conn, id ID, cancelled bool) bool
-
- // Request is called near the start of processing any request.
- Request(ctx context.Context, conn *Conn, direction Direction, r *WireRequest) context.Context
-}
+// Handler is invoked to handle incoming requests.
+// If the request returns false from IsNotify then the Handler must eventually
+// call Reply on the Conn with the supplied request.
+// The handler should return ErrNotHandled if it could not handle the request.
+type Handler func(context.Context, *Request) error
// Direction is used to indicate to a logger whether the logged message was being
// sent or received.
@@ -54,32 +40,77 @@
}
}
-type EmptyHandler struct{}
-
-func (EmptyHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
- return false
+// MethodNotFound is a Handler that replies to all call requests with the
+// standard method not found response.
+// This should normally be the final handler in a chain.
+func MethodNotFound(ctx context.Context, r *Request) error {
+ return r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
}
-func (EmptyHandler) Cancel(ctx context.Context, conn *Conn, id ID, cancelled bool) bool {
- return false
-}
-
-func (EmptyHandler) Request(ctx context.Context, conn *Conn, direction Direction, r *WireRequest) context.Context {
- return ctx
-}
-
-func (EmptyHandler) Response(ctx context.Context, conn *Conn, direction Direction, r *WireResponse) context.Context {
- return ctx
-}
-
-type defaultHandler struct{ EmptyHandler }
-
-func (defaultHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
- if delivered {
- return false
+// MustReply creates a Handler that panics if the wrapped handler does
+// not call Reply for every request that it is passed.
+func MustReply(handler Handler) Handler {
+ return func(ctx context.Context, req *Request) error {
+ err := handler(ctx, req)
+ if req.done != nil {
+ panic(fmt.Errorf("request %q was never replied to", req.Method))
+ }
+ return err
}
- if !r.IsNotify() {
- r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
+}
+
+// CancelHandler returns a handler that supports cancellation, and a canceller
+// that can be used to trigger canceling in progress requests.
+func CancelHandler(handler Handler) (Handler, Canceller) {
+ var mu sync.Mutex
+ handling := make(map[ID]context.CancelFunc)
+ wrapped := func(ctx context.Context, req *Request) error {
+ if req.ID != nil {
+ cancelCtx, cancel := context.WithCancel(ctx)
+ ctx = cancelCtx
+ mu.Lock()
+ handling[*req.ID] = cancel
+ mu.Unlock()
+ req.OnReply(func() {
+ mu.Lock()
+ delete(handling, *req.ID)
+ mu.Unlock()
+ })
+ }
+ return handler(ctx, req)
}
- return true
+ return wrapped, func(id ID) {
+ mu.Lock()
+ cancel, found := handling[id]
+ mu.Unlock()
+ if found {
+ cancel()
+ }
+ }
+}
+
+// AsyncHandler returns a handler that processes each request goes in its own
+// goroutine.
+// The handler returns immediately, without the request being processed.
+// Each request then waits for the previous request to finish before it starts.
+// This allows the stream to unblock at the cost of unbounded goroutines
+// all stalled on the previous one.
+func AsyncHandler(handler Handler) Handler {
+ nextRequest := make(chan struct{})
+ close(nextRequest)
+ return func(ctx context.Context, req *Request) error {
+ waitForPrevious := nextRequest
+ nextRequest = make(chan struct{})
+ unlockNext := nextRequest
+ req.OnReply(func() { close(unlockNext) })
+ _, queueDone := event.StartSpan(ctx, "queued")
+ go func() {
+ <-waitForPrevious
+ queueDone()
+ if err := handler(ctx, req); err != nil {
+ event.Error(ctx, "jsonrpc2 async message delivery failed", err)
+ }
+ }()
+ return nil
+ }
}
diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go
index c7724ad..1f2fd4c 100644
--- a/internal/jsonrpc2/jsonrpc2.go
+++ b/internal/jsonrpc2/jsonrpc2.go
@@ -18,39 +18,46 @@
"golang.org/x/tools/internal/telemetry/event"
)
+const (
+ // ErrIdleTimeout is returned when serving timed out waiting for new connections.
+ ErrIdleTimeout = constError("timed out waiting for new connections")
+
+ // ErrDisconnected signals that the stream or connection exited normally.
+ ErrDisconnected = constError("disconnected")
+)
+
// Conn is a JSON RPC 2 client server connection.
// Conn is bidirectional; it does not have a designated server or client end.
type Conn struct {
- seq int64 // must only be accessed using atomic operations
- handlers []Handler
- stream Stream
- pendingMu sync.Mutex // protects the pending map
- pending map[ID]chan *WireResponse
- handlingMu sync.Mutex // protects the handling map
- handling map[ID]*Request
+ seq int64 // must only be accessed using atomic operations
+ stream Stream
+ pendingMu sync.Mutex // protects the pending map
+ pending map[ID]chan *WireResponse
+ onCancelled CallCanceller
}
-type requestState int
-
-const (
- requestWaiting = requestState(iota)
- requestSerial
- requestParallel
- requestReplied
- requestDone
-)
-
// Request is sent to a server to represent a Call or Notify operaton.
type Request struct {
- conn *Conn
- cancel context.CancelFunc
- state requestState
- nextRequest chan struct{}
+ conn *Conn
+ // done holds set of callbacks added by OnReply, and is set back to nil if
+ // Reply has been called.
+ done []func()
// The Wire values of the request.
WireRequest
}
+// Canceller is the type for a function that can cancel an in progress request.
+type Canceller func(id ID)
+
+// CallCanceller is the type for a callback when an outgoing request is
+// has it's context cancelled.
+type CallCanceller func(context.Context, *Conn, ID)
+
+type constError string
+
+func (e constError) Error() string { return string(e) }
+
// NewErrorf builds a Error struct for the supplied message and code.
// If args is not empty, message and args will be passed to Sprintf.
func NewErrorf(code int64, format string, args ...interface{}) *Error {
@@ -64,35 +71,17 @@
// You must call Run for the connection to be active.
func NewConn(s Stream) *Conn {
conn := &Conn{
- handlers: []Handler{defaultHandler{}},
- stream: s,
- pending: make(map[ID]chan *WireResponse),
- handling: make(map[ID]*Request),
+ stream: s,
+ pending: make(map[ID]chan *WireResponse),
}
return conn
}
-// AddHandler adds a new handler to the set the connection will invoke.
-// Handlers are invoked in the reverse order of how they were added, this
-// allows the most recent addition to be the first one to attempt to handle a
-// message.
-func (c *Conn) AddHandler(handler Handler) {
- // prepend the new handlers so we use them first
- c.handlers = append([]Handler{handler}, c.handlers...)
-}
-
-// Cancel cancels a pending Call on the server side.
-// The call is identified by its id.
-// JSON RPC 2 does not specify a cancel message, so cancellation support is not
-// directly wired in. This method allows a higher level protocol to choose how
-// to propagate the cancel.
-func (c *Conn) Cancel(id ID) {
- c.handlingMu.Lock()
- handling, found := c.handling[id]
- c.handlingMu.Unlock()
- if found {
- handling.cancel()
- }
+// OnCancelled sets the callback used when an outgoing call request has
+// it's context cancelled when still in progress.
+// Only the last callback registered is used.
+func (c *Conn) OnCancelled(cancelled CallCanceller) {
+ c.onCancelled = cancelled
}
// Notify is called to send a notification request over the connection.
@@ -111,9 +100,6 @@
if err != nil {
return fmt.Errorf("marshalling notify request: %v", err)
}
- for _, h := range c.handlers {
- ctx = h.Request(ctx, c, Send, request)
- }
ctx, done := event.StartSpan(ctx, request.Method,
tag.Method.Of(request.Method),
tag.RPCDirection.Of(tag.Outbound),
@@ -126,7 +112,7 @@
event.Record(ctx, tag.Started.Of(1))
n, err := c.stream.Write(ctx, data)
- event.Record(ctx, tag.ReceivedBytes.Of(n))
+ event.Record(ctx, tag.SentBytes.Of(n))
return err
}
@@ -150,9 +136,6 @@
if err != nil {
return fmt.Errorf("marshalling call request: %v", err)
}
- for _, h := range c.handlers {
- ctx = h.Request(ctx, c, Send, request)
- }
ctx, done := event.StartSpan(ctx, request.Method,
tag.Method.Of(request.Method),
tag.RPCDirection.Of(tag.Outbound),
@@ -178,7 +161,7 @@
}()
// now we are ready to send
n, err := c.stream.Write(ctx, data)
- event.Record(ctx, tag.ReceivedBytes.Of(n))
+ event.Record(ctx, tag.SentBytes.Of(n))
if err != nil {
// sending failed, we will never get a response, so don't leave it pending
return err
@@ -199,11 +182,8 @@
return nil
case <-ctx.Done():
// Allow the handler to propagate the cancel.
- cancelled := false
- for _, h := range c.handlers {
- if h.Cancel(ctx, c, id, cancelled) {
- cancelled = true
- }
+ if c.onCancelled != nil {
+ c.onCancelled(ctx, c, id)
}
return ctx.Err()
}
@@ -217,39 +197,27 @@
return r.ID == nil
}
-// Parallel indicates that the system is now allowed to process other requests
-// in parallel with this one.
-// It is safe to call any number of times, but must only be called from the
-// request handling go routine.
-// It is implied by both reply and by the handler returning.
-func (r *Request) Parallel() {
- if r.state >= requestParallel {
- return
- }
- r.state = requestParallel
- close(r.nextRequest)
-}
-
// Reply sends a reply to the given request.
-// It is an error to call this if request was not a call.
// You must call this exactly once for any given request.
-// It should only be called from the handler go routine.
// If err is set then result will be ignored.
-// If the request has not yet dropped into parallel mode
-// it will be before this function returns.
+// This will mark the request as done, triggering any done
+// handlers
func (r *Request) Reply(ctx context.Context, result interface{}, err error) error {
- if r.state >= requestReplied {
+ if r.done == nil {
return fmt.Errorf("reply invoked more than once")
}
+
+ defer func() {
+ recordStatus(ctx, err)
+ for i := len(r.done); i > 0; i-- {
+ r.done[i-1]()
+ }
+ r.done = nil
+ }()
+
if r.IsNotify() {
- return fmt.Errorf("reply not invoked with a valid call: %v, %v", r.Method, r.Params)
+ return nil
}
- // reply ends the handling phase of a call, so if we are not yet
- // parallel we should be now. The go routine is allowed to continue
- // to do work after replying, which is why it is important to unlock
- // the rpc system at this point.
- r.Parallel()
- r.state = requestReplied
var raw *json.RawMessage
if err == nil {
@@ -271,7 +239,7 @@
return err
}
n, err := r.conn.stream.Write(ctx, data)
- event.Record(ctx, tag.ReceivedBytes.Of(n))
+ event.Record(ctx, tag.SentBytes.Of(n))
if err != nil {
// TODO(iancottrell): if a stream write fails, we really need to shut down
@@ -281,17 +249,15 @@
return nil
}
-func (c *Conn) setHandling(r *Request, active bool) {
- if r.ID == nil {
- return
- }
- r.conn.handlingMu.Lock()
- defer r.conn.handlingMu.Unlock()
- if active {
- r.conn.handling[*r.ID] = r
- } else {
- delete(r.conn.handling, *r.ID)
- }
+// OnReply adds a done callback to the request.
+// All added callbacks are invoked during the one required call to Reply, and
+// then dropped.
+// It is an error to call this after Reply.
+// This call is not safe for concurrent use, but should only be invoked by
+// handlers and in general only one handler should be working on a request
+// at any time.
+func (r *Request) OnReply(do func()) {
+ r.done = append(r.done, do)
}
// combined has all the fields of both Request and Response.
@@ -309,12 +275,7 @@
// caused the termination.
// It must be called exactly once for each Conn.
// It returns only when the reader is closed or there is an error in the stream.
-func (c *Conn) Run(runCtx context.Context) error {
- // we need to make the next request "lock" in an unlocked state to allow
- // the first incoming request to proceed. All later requests are unlocked
- // by the preceding request going to parallel mode.
- nextRequest := make(chan struct{})
- close(nextRequest)
+func (c *Conn) Run(runCtx context.Context, handler Handler) error {
for {
// get the data for a message
data, n, err := c.stream.Read(runCtx)
@@ -334,13 +295,17 @@
switch {
case msg.Method != "":
// If method is set it must be a request.
- reqCtx, cancelReq := context.WithCancel(runCtx)
- thisRequest := nextRequest
- nextRequest = make(chan struct{})
+ reqCtx, spanDone := event.StartSpan(runCtx, msg.Method,
+ tag.Method.Of(msg.Method),
+ tag.RPCDirection.Of(tag.Inbound),
+ tag.RPCID.Of(msg.ID.String()),
+ )
+ event.Record(reqCtx,
+ tag.Started.Of(1),
+ tag.ReceivedBytes.Of(n))
+
req := &Request{
- conn: c,
- cancel: cancelReq,
- nextRequest: nextRequest,
+ conn: c,
WireRequest: WireRequest{
VersionTag: msg.VersionTag,
Method: msg.Method,
@@ -348,40 +313,12 @@
ID: msg.ID,
},
}
- for _, h := range c.handlers {
- reqCtx = h.Request(reqCtx, c, Receive, &req.WireRequest)
+ req.OnReply(func() { spanDone() })
+
+ if err := handler(reqCtx, req); err != nil {
+ // delivery failed, not much we can do
+ event.Error(reqCtx, "jsonrpc2 message delivery failed", err)
}
- reqCtx, done := event.StartSpan(reqCtx, req.WireRequest.Method,
- tag.Method.Of(req.WireRequest.Method),
- tag.RPCDirection.Of(tag.Inbound),
- tag.RPCID.Of(req.WireRequest.ID.String()),
- )
- event.Record(reqCtx,
- tag.Started.Of(1),
- tag.SentBytes.Of(n))
- c.setHandling(req, true)
- _, queueDone := event.StartSpan(reqCtx, "queued")
- go func() {
- <-thisRequest
- queueDone()
- req.state = requestSerial
- defer func() {
- c.setHandling(req, false)
- if !req.IsNotify() && req.state < requestReplied {
- req.Reply(reqCtx, nil, NewErrorf(CodeInternalError, "method %q did not reply", req.Method))
- }
- req.Parallel()
- recordStatus(reqCtx, nil)
- done()
- cancelReq()
- }()
- delivered := false
- for _, h := range c.handlers {
- if h.Deliver(reqCtx, req, delivered) {
- delivered = true
- }
- }
- }()
case msg.ID != nil:
// If method is not set, this should be a response, in which case we must
// have an id to send the response back to the caller.
diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go
index 4fcca31..a458f44 100644
--- a/internal/jsonrpc2/jsonrpc2_test.go
+++ b/internal/jsonrpc2/jsonrpc2_test.go
@@ -10,11 +10,9 @@
"flag"
"fmt"
"io"
- "log"
"path"
"reflect"
"testing"
- "time"
"golang.org/x/tools/internal/jsonrpc2"
)
@@ -108,95 +106,46 @@
stream = jsonrpc2.NewStream(r, w)
}
conn := jsonrpc2.NewConn(stream)
- conn.AddHandler(&handle{log: *logRPC})
go func() {
defer func() {
r.Close()
w.Close()
}()
- if err := conn.Run(ctx); err != nil {
+ if err := conn.Run(ctx, testHandler(*logRPC)); err != nil {
t.Errorf("Stream failed: %v", err)
}
}()
return conn
}
-type handle struct {
- log bool
-}
-
-func (h *handle) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- switch r.Method {
- case "no_args":
- if r.Params != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
- return true
+func testHandler(log bool) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
+ switch r.Method {
+ case "no_args":
+ if r.Params != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
+ }
+ return r.Reply(ctx, true, nil)
+ case "one_string":
+ var v string
+ if err := json.Unmarshal(*r.Params, &v); err != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
+ }
+ return r.Reply(ctx, "got:"+v, nil)
+ case "one_number":
+ var v int
+ if err := json.Unmarshal(*r.Params, &v); err != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
+ }
+ return r.Reply(ctx, fmt.Sprintf("got:%d", v), nil)
+ case "join":
+ var v []string
+ if err := json.Unmarshal(*r.Params, &v); err != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
+ }
+ return r.Reply(ctx, path.Join(v...), nil)
+ default:
+ return jsonrpc2.MethodNotFound(ctx, r)
}
- r.Reply(ctx, true, nil)
- case "one_string":
- var v string
- if err := json.Unmarshal(*r.Params, &v); err != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
- return true
- }
- r.Reply(ctx, "got:"+v, nil)
- case "one_number":
- var v int
- if err := json.Unmarshal(*r.Params, &v); err != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
- return true
- }
- r.Reply(ctx, fmt.Sprintf("got:%d", v), nil)
- case "join":
- var v []string
- if err := json.Unmarshal(*r.Params, &v); err != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
- return true
- }
- r.Reply(ctx, path.Join(v...), nil)
- default:
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
}
- return true
-}
-
-func (h *handle) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
- return false
-}
-
-func (h *handle) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
- if h.log {
- if r.ID != nil {
- log.Printf("%v call [%v] %s %v", direction, r.ID, r.Method, r.Params)
- } else {
- log.Printf("%v notification %s %v", direction, r.Method, r.Params)
- }
- ctx = context.WithValue(ctx, "method", r.Method)
- ctx = context.WithValue(ctx, "start", time.Now())
- }
- return ctx
-}
-
-func (h *handle) Response(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context {
- if h.log {
- method := ctx.Value("method")
- elapsed := time.Since(ctx.Value("start").(time.Time))
- log.Printf("%v response in %v [%v] %s %v", direction, elapsed, r.ID, method, r.Result)
- }
- return ctx
-}
-
-func (h *handle) Done(ctx context.Context, err error) {
-}
-
-func (h *handle) Read(ctx context.Context, bytes int64) context.Context {
- return ctx
-}
-
-func (h *handle) Wrote(ctx context.Context, bytes int64) context.Context {
- return ctx
-}
-
-func (h *handle) Error(ctx context.Context, err error) {
- log.Printf("%v", err)
}
diff --git a/internal/jsonrpc2/serve.go b/internal/jsonrpc2/serve.go
index c2cfa60..fe1899e 100644
--- a/internal/jsonrpc2/serve.go
+++ b/internal/jsonrpc2/serve.go
@@ -6,7 +6,6 @@
import (
"context"
- "errors"
"fmt"
"log"
"net"
@@ -39,8 +38,7 @@
func HandlerServer(h Handler) StreamServer {
return ServerFunc(func(ctx context.Context, s Stream) error {
conn := NewConn(s)
- conn.AddHandler(h)
- return conn.Run(ctx)
+ return conn.Run(ctx, h)
})
}
@@ -59,9 +57,6 @@
return Serve(ctx, ln, server, idleTimeout)
}
-// ErrIdleTimeout is returned when serving timed out waiting for new connections.
-var ErrIdleTimeout = errors.New("timed out waiting for new connections")
-
// Serve accepts incoming connections from the network, and handles them using
// the provided server. If idleTimeout is non-zero, ListenAndServe exits after
// there are no clients for this duration, otherwise it exits only on error.
diff --git a/internal/jsonrpc2/serve_test.go b/internal/jsonrpc2/serve_test.go
index ec6e1e1..2cbfe8f 100644
--- a/internal/jsonrpc2/serve_test.go
+++ b/internal/jsonrpc2/serve_test.go
@@ -30,7 +30,7 @@
return conn
}
- server := HandlerServer(EmptyHandler{})
+ server := HandlerServer(MethodNotFound)
// connTimer := &fakeTimer{c: make(chan time.Time, 1)}
var (
runErr error
diff --git a/internal/jsonrpc2/servertest/servertest.go b/internal/jsonrpc2/servertest/servertest.go
index 1217591..13815e0 100644
--- a/internal/jsonrpc2/servertest/servertest.go
+++ b/internal/jsonrpc2/servertest/servertest.go
@@ -53,9 +53,7 @@
s.cls.add(func() {
netConn.Close()
})
- conn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn))
- go conn.Run(ctx)
- return conn
+ return jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn))
}
// Close closes all connected pipes.
@@ -92,9 +90,7 @@
go s.server.ServeStream(ctx, serverStream)
clientStream := jsonrpc2.NewStream(cReader, sWriter)
- clientConn := jsonrpc2.NewConn(clientStream)
- go clientConn.Run(ctx)
- return clientConn
+ return jsonrpc2.NewConn(clientStream)
}
// Close closes all connected pipes.
diff --git a/internal/jsonrpc2/servertest/servertest_test.go b/internal/jsonrpc2/servertest/servertest_test.go
index 21b540a..c0905d1 100644
--- a/internal/jsonrpc2/servertest/servertest_test.go
+++ b/internal/jsonrpc2/servertest/servertest_test.go
@@ -12,25 +12,18 @@
"golang.org/x/tools/internal/jsonrpc2"
)
-type fakeHandler struct {
- jsonrpc2.EmptyHandler
-}
-
type msg struct {
Msg string
}
-func (fakeHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- if err := r.Reply(ctx, &msg{"pong"}, nil); err != nil {
- panic(err)
- }
- return true
+func fakeHandler(ctx context.Context, r *jsonrpc2.Request) error {
+ return r.Reply(ctx, &msg{"pong"}, nil)
}
func TestTestServer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
- server := jsonrpc2.HandlerServer(fakeHandler{})
+ server := jsonrpc2.HandlerServer(fakeHandler)
tcpTS := NewTCPServer(ctx, server)
defer tcpTS.Close()
pipeTS := NewPipeServer(ctx, server)
@@ -47,6 +40,7 @@
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
conn := test.connector.Connect(ctx)
+ go conn.Run(ctx, jsonrpc2.MethodNotFound)
var got msg
if err := conn.Call(ctx, "ping", &msg{"ping"}, &got); err != nil {
t.Fatal(err)
diff --git a/internal/jsonrpc2/stream.go b/internal/jsonrpc2/stream.go
index 2d1e7c4..3276c1a 100644
--- a/internal/jsonrpc2/stream.go
+++ b/internal/jsonrpc2/stream.go
@@ -8,7 +8,6 @@
"bufio"
"context"
"encoding/json"
- "errors"
"fmt"
"io"
"strconv"
@@ -29,9 +28,6 @@
Write(context.Context, []byte) (int64, error)
}
-// ErrDisconnected signals that the stream or connection exited normally.
-var ErrDisconnected = errors.New("disconnected")
-
// NewStream returns a Stream built on top of an io.Reader and io.Writer
// The messages are sent with no wrapping, and rely on json decode consistency
// to determine message boundaries.
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index f667c46..e05d2b5 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -233,10 +233,11 @@
stream := jsonrpc2.NewHeaderStream(conn, conn)
cc := jsonrpc2.NewConn(stream)
connection.Server = protocol.ServerDispatcher(cc)
- cc.AddHandler(protocol.ClientHandler(connection.Client))
- cc.AddHandler(protocol.Canceller{})
ctx = protocol.WithClient(ctx, connection.Client)
- go cc.Run(ctx)
+ go cc.Run(ctx,
+ protocol.Handlers(
+ protocol.ClientHandler(connection.Client,
+ jsonrpc2.MethodNotFound)))
return connection, connection.initialize(ctx, app.options)
}
diff --git a/internal/lsp/debug/rpc.go b/internal/lsp/debug/rpc.go
index 823ee9d..e1c93e2 100644
--- a/internal/lsp/debug/rpc.go
+++ b/internal/lsp/debug/rpc.go
@@ -16,7 +16,6 @@
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/telemetry/export"
- "golang.org/x/tools/internal/telemetry/export/metric"
)
var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
@@ -32,10 +31,10 @@
<b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress)
<br>
<i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
- <i>By bucket</i> 0s {{range .Latency.Values}}<b>{{.Count}}</b> {{.Limit}} {{end}}
+ <i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}}
<br>
- <i>Received</i> {{with .Received}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
- <i>Sent</i> {{with .Sent}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
+ <i>Received</i> {{.Received}} (avg. {{.ReceivedMean}})
+ <i>Sent</i> {{.Sent}} (avg. {{.SentMean}})
<br>
<i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
</P>
@@ -45,25 +44,24 @@
type rpcs struct {
mu sync.Mutex
- Inbound []*rpcStats
- Outbound []*rpcStats
+ Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name
+ Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name
}
type rpcStats struct {
- Method string
- Started int64
- Completed int64
- InProgress int64
- Latency rpcTimeHistogram
- Received rpcBytesHistogram
- Sent rpcBytesHistogram
- Codes []*rpcCodeBucket
+ Method string
+ Started int64
+ Completed int64
+
+ Latency rpcTimeHistogram
+ Received byteUnits
+ Sent byteUnits
+ Codes []*rpcCodeBucket
}
type rpcTimeHistogram struct {
Sum timeUnits
Count int64
- Mean timeUnits
Min timeUnits
Max timeUnits
Values []rpcTimeBucket
@@ -74,140 +72,141 @@
Count int64
}
-type rpcBytesHistogram struct {
- Sum byteUnits
- Count int64
- Mean byteUnits
- Min byteUnits
- Max byteUnits
- Values []rpcBytesBucket
-}
-
-type rpcBytesBucket struct {
- Limit byteUnits
- Count int64
-}
-
type rpcCodeBucket struct {
Key string
Count int64
}
func (r *rpcs) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context {
- switch {
- case ev.IsEndSpan():
- // calculate latency if this was an rpc span
- span := export.GetSpan(ctx)
- if span == nil {
- return ctx
- }
- // is this a finished rpc span, if so it will have a status code record
- for _, ev := range span.Events() {
- code := tag.StatusCode.Get(ev.Map())
- if code != "" {
- elapsedTime := span.Finish().At.Sub(span.Start().At)
- latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
- statsCtx := event.Label1(ctx, tag.StatusCode.Of(code))
- event.Record1(statsCtx, tag.Latency.Of(latencyMillis))
- }
- }
- return ctx
- case ev.IsRecord():
- // fall through to the metrics handling logic
- default:
- // ignore all other event types
- return ctx
- }
r.mu.Lock()
defer r.mu.Unlock()
- //TODO(38168): we should just deal with the events here and not use metrics
- metrics := metric.Entries.Get(tagMap).([]metric.Data)
- for _, data := range metrics {
- for i, group := range data.Groups() {
- set := &r.Inbound
- groupTags := event.NewTagMap(group...)
- if tag.RPCDirection.Get(groupTags) == tag.Outbound {
- set = &r.Outbound
- }
- method := tag.Method.Get(groupTags)
- index := sort.Search(len(*set), func(i int) bool {
- return (*set)[i].Method >= method
- })
- if index >= len(*set) || (*set)[index].Method != method {
- old := *set
- *set = make([]*rpcStats, len(old)+1)
- copy(*set, old[:index])
- copy((*set)[index+1:], old[index:])
- (*set)[index] = &rpcStats{Method: method}
- }
- stats := (*set)[index]
- switch data.Handle() {
- case started.Name:
- stats.Started = data.(*metric.Int64Data).Rows[i]
- case completed.Name:
- status := tag.StatusCode.Get(groupTags)
- var b *rpcCodeBucket
- for c, entry := range stats.Codes {
- if entry.Key == status {
- b = stats.Codes[c]
- break
- }
- }
- if b == nil {
- b = &rpcCodeBucket{Key: status}
- stats.Codes = append(stats.Codes, b)
- sort.Slice(stats.Codes, func(i int, j int) bool {
- return stats.Codes[i].Key < stats.Codes[j].Key
- })
- }
- b.Count = data.(*metric.Int64Data).Rows[i]
- case latency.Name:
- data := data.(*metric.HistogramFloat64Data)
- row := data.Rows[i]
- stats.Latency.Count = row.Count
- stats.Latency.Sum = timeUnits(row.Sum)
- stats.Latency.Min = timeUnits(row.Min)
- stats.Latency.Max = timeUnits(row.Max)
- stats.Latency.Mean = timeUnits(row.Sum) / timeUnits(row.Count)
- stats.Latency.Values = make([]rpcTimeBucket, len(data.Info.Buckets))
- last := int64(0)
- for i, b := range data.Info.Buckets {
- stats.Latency.Values[i].Limit = timeUnits(b)
- stats.Latency.Values[i].Count = row.Values[i] - last
- last = row.Values[i]
- }
- case sentBytes.Name:
- data := data.(*metric.HistogramInt64Data)
- row := data.Rows[i]
- stats.Sent.Count = row.Count
- stats.Sent.Sum = byteUnits(row.Sum)
- stats.Sent.Min = byteUnits(row.Min)
- stats.Sent.Max = byteUnits(row.Max)
- stats.Sent.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
- case receivedBytes.Name:
- data := data.(*metric.HistogramInt64Data)
- row := data.Rows[i]
- stats.Received.Count = row.Count
- stats.Received.Sum = byteUnits(row.Sum)
- stats.Sent.Min = byteUnits(row.Min)
- stats.Sent.Max = byteUnits(row.Max)
- stats.Received.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
- }
+ switch {
+ case ev.IsStartSpan():
+ if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
+ stats.Started++
}
- }
-
- for _, set := range [][]*rpcStats{r.Inbound, r.Outbound} {
- for _, stats := range set {
- stats.Completed = 0
- for _, b := range stats.Codes {
- stats.Completed += b.Count
+ case ev.IsEndSpan():
+ span, stats := r.getRPCSpan(ctx, ev)
+ if stats != nil {
+ endRPC(ctx, ev, span, stats)
+ }
+ case ev.IsRecord():
+ sent := byteUnits(tag.SentBytes.Get(tagMap))
+ rec := byteUnits(tag.ReceivedBytes.Get(tagMap))
+ if sent != 0 || rec != 0 {
+ if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
+ stats.Sent += sent
+ stats.Received += rec
}
- stats.InProgress = stats.Started - stats.Completed
}
}
return ctx
}
+func endRPC(ctx context.Context, ev event.Event, span *export.Span, stats *rpcStats) {
+ // update the basic counts
+ stats.Completed++
+
+ // get and record the status code
+ if status := getStatusCode(span); status != "" {
+ var b *rpcCodeBucket
+ for c, entry := range stats.Codes {
+ if entry.Key == status {
+ b = stats.Codes[c]
+ break
+ }
+ }
+ if b == nil {
+ b = &rpcCodeBucket{Key: status}
+ stats.Codes = append(stats.Codes, b)
+ sort.Slice(stats.Codes, func(i int, j int) bool {
+ return stats.Codes[i].Key < stats.Codes[j].Key
+ })
+ }
+ b.Count++
+ }
+
+ // calculate latency if this was an rpc span
+ elapsedTime := span.Finish().At.Sub(span.Start().At)
+ latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond)
+ if stats.Latency.Count == 0 {
+ stats.Latency.Min = latencyMillis
+ stats.Latency.Max = latencyMillis
+ } else {
+ if stats.Latency.Min > latencyMillis {
+ stats.Latency.Min = latencyMillis
+ }
+ if stats.Latency.Max < latencyMillis {
+ stats.Latency.Max = latencyMillis
+ }
+ }
+ stats.Latency.Count++
+ stats.Latency.Sum += latencyMillis
+ for i := range stats.Latency.Values {
+ if stats.Latency.Values[i].Limit > latencyMillis {
+ stats.Latency.Values[i].Count++
+ break
+ }
+ }
+}
+
+func (r *rpcs) getRPCSpan(ctx context.Context, ev event.Event) (*export.Span, *rpcStats) {
+ // get the span
+ span := export.GetSpan(ctx)
+ if span == nil {
+ return nil, nil
+ }
+ // use the span start event look up the correct stats block
+ // we do this because it prevents us matching a sub span
+ return span, r.getRPCStats(span.Start())
+}
+
+func (r *rpcs) getRPCStats(tagMap event.TagMap) *rpcStats {
+ method := tag.Method.Get(tagMap)
+ if method == "" {
+ return nil
+ }
+ set := &r.Inbound
+ if tag.RPCDirection.Get(tagMap) != tag.Inbound {
+ set = &r.Outbound
+ }
+ // get the record for this method
+ index := sort.Search(len(*set), func(i int) bool {
+ return (*set)[i].Method >= method
+ })
+
+ if index < len(*set) && (*set)[index].Method == method {
+ return (*set)[index]
+ }
+
+ old := *set
+ *set = make([]*rpcStats, len(old)+1)
+ copy(*set, old[:index])
+ copy((*set)[index+1:], old[index:])
+ stats := &rpcStats{Method: method}
+ stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution))
+ for i, m := range millisecondsDistribution {
+ stats.Latency.Values[i].Limit = timeUnits(m)
+ }
+ (*set)[index] = stats
+ return stats
+}
+
+func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed }
+func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) }
+func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) }
+
+func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) }
+
+func getStatusCode(span *export.Span) string {
+ for _, ev := range span.Events() {
+ if status := tag.StatusCode.Get(ev); status != "" {
+ return status
+ }
+ }
+ return ""
+}
+
func (r *rpcs) getData(req *http.Request) interface{} {
return r
}
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index 11364b6..c5781e5 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -547,7 +547,7 @@
if ev.IsLog() {
// Don't log context cancellation errors.
- if err := event.Err.Get(ev.Map()); xerrors.Is(err, context.Canceled) {
+ if err := event.Err.Get(ev); xerrors.Is(err, context.Canceled) {
return ctx
}
// Make sure any log messages without an instance go to stderr.
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index 3b4e72f..1cd213a 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -57,7 +57,10 @@
e := NewEditor(ws)
e.server = protocol.ServerDispatcher(conn)
e.client = &Client{Editor: e}
- conn.AddHandler(protocol.ClientHandler(e.client))
+ go conn.Run(ctx,
+ protocol.Handlers(
+ protocol.ClientHandler(e.client,
+ jsonrpc2.MethodNotFound)))
if err := e.initialize(ctx); err != nil {
return nil, err
}
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index 37003f1..5c971f2 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -134,18 +134,17 @@
event.Error(ctx, "error shutting down", err)
}
}()
- conn.AddHandler(protocol.ServerHandler(server))
- conn.AddHandler(protocol.Canceller{})
executable, err := os.Executable()
if err != nil {
log.Printf("error getting gopls path: %v", err)
executable = ""
}
- conn.AddHandler(&handshaker{
- client: dc,
- goplsPath: executable,
- })
- return conn.Run(protocol.WithClient(ctx, client))
+ ctx = protocol.WithClient(ctx, client)
+ return conn.Run(ctx,
+ protocol.Handlers(
+ handshaker(dc, executable,
+ protocol.ServerHandler(server,
+ jsonrpc2.MethodNotFound))))
}
// A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
@@ -235,7 +234,7 @@
return nil, fmt.Errorf("dialing remote: %v", err)
}
serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn))
- go serverConn.Run(ctx)
+ go serverConn.Run(ctx, jsonrpc2.MethodNotFound)
var state ServerState
if err := serverConn.Call(ctx, sessionsMethod, nil, &state); err != nil {
return nil, fmt.Errorf("querying server state: %v", err)
@@ -257,14 +256,12 @@
server := protocol.ServerDispatcher(serverConn)
// Forward between connections.
- serverConn.AddHandler(protocol.ClientHandler(client))
- serverConn.AddHandler(protocol.Canceller{})
- clientConn.AddHandler(protocol.ServerHandler(server))
- clientConn.AddHandler(protocol.Canceller{})
- clientConn.AddHandler(forwarderHandler{})
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
- return serverConn.Run(ctx)
+ return serverConn.Run(ctx,
+ protocol.Handlers(
+ protocol.ClientHandler(client,
+ jsonrpc2.MethodNotFound)))
})
// Don't run the clientConn yet, so that we can complete the handshake before
// processing any client messages.
@@ -302,7 +299,11 @@
})
}
g.Go(func() error {
- return clientConn.Run(ctx)
+ return clientConn.Run(ctx,
+ protocol.Handlers(
+ forwarderHandler(
+ protocol.ServerHandler(server,
+ jsonrpc2.MethodNotFound))))
})
return g.Wait()
@@ -414,28 +415,19 @@
// forwarderHandler intercepts 'exit' messages to prevent the shared gopls
// instance from exiting. In the future it may also intercept 'shutdown' to
// provide more graceful shutdown of the client connection.
-type forwarderHandler struct {
- jsonrpc2.EmptyHandler
-}
-
-func (forwarderHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- // TODO(golang.org/issues/34111): we should more gracefully disconnect here,
- // once that process exists.
- if r.Method == "exit" {
- ForwarderExitFunc(0)
- // Still return true here to prevent the message from being delivered: in
- // tests, ForwarderExitFunc may be overridden to something that doesn't
- // exit the process.
- return true
+func forwarderHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
+ // TODO(golang.org/issues/34111): we should more gracefully disconnect here,
+ // once that process exists.
+ if r.Method == "exit" {
+ ForwarderExitFunc(0)
+ // reply nil here to consume the message: in
+ // tests, ForwarderExitFunc may be overridden to something that doesn't
+ // exit the process.
+ return r.Reply(ctx, nil, nil)
+ }
+ return handler(ctx, r)
}
- return false
-}
-
-type handshaker struct {
- jsonrpc2.EmptyHandler
- client *debugClient
- instance *debug.Instance
- goplsPath string
}
// A handshakeRequest identifies a client to the LSP server.
@@ -493,55 +485,51 @@
sessionsMethod = "gopls/sessions"
)
-func (h *handshaker) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- switch r.Method {
- case handshakeMethod:
- var req handshakeRequest
- if err := json.Unmarshal(*r.Params, &req); err != nil {
- sendError(ctx, r, err)
- return true
- }
- h.client.debugAddress = req.DebugAddr
- h.client.logfile = req.Logfile
- h.client.serverID = req.ServerID
- h.client.goplsPath = req.GoplsPath
- resp := handshakeResponse{
- ClientID: h.client.id,
- SessionID: cache.DebugSession{Session: h.client.session}.ID(),
- GoplsPath: h.goplsPath,
- }
- if di := debug.GetInstance(ctx); di != nil {
- resp.Logfile = di.Logfile
- resp.DebugAddr = di.ListenedDebugAddress
- }
-
- if err := r.Reply(ctx, resp, nil); err != nil {
- event.Error(ctx, "replying to handshake", err)
- }
- return true
- case sessionsMethod:
- resp := ServerState{
- GoplsPath: h.goplsPath,
- CurrentClientID: h.client.ID(),
- }
- if di := debug.GetInstance(ctx); di != nil {
- resp.Logfile = di.Logfile
- resp.DebugAddr = di.ListenedDebugAddress
- for _, c := range di.State.Clients() {
- resp.Clients = append(resp.Clients, ClientSession{
- ClientID: c.ID(),
- SessionID: c.Session().ID(),
- Logfile: c.Logfile(),
- DebugAddr: c.DebugAddress(),
- })
+func handshaker(client *debugClient, goplsPath string, handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
+ switch r.Method {
+ case handshakeMethod:
+ var req handshakeRequest
+ if err := json.Unmarshal(*r.Params, &req); err != nil {
+ sendError(ctx, r, err)
+ return nil
}
+ client.debugAddress = req.DebugAddr
+ client.logfile = req.Logfile
+ client.serverID = req.ServerID
+ client.goplsPath = req.GoplsPath
+ resp := handshakeResponse{
+ ClientID: client.id,
+ SessionID: cache.DebugSession{Session: client.session}.ID(),
+ GoplsPath: goplsPath,
+ }
+ if di := debug.GetInstance(ctx); di != nil {
+ resp.Logfile = di.Logfile
+ resp.DebugAddr = di.ListenedDebugAddress
+ }
+
+ return r.Reply(ctx, resp, nil)
+ case sessionsMethod:
+ resp := ServerState{
+ GoplsPath: goplsPath,
+ CurrentClientID: client.ID(),
+ }
+ if di := debug.GetInstance(ctx); di != nil {
+ resp.Logfile = di.Logfile
+ resp.DebugAddr = di.ListenedDebugAddress
+ for _, c := range di.State.Clients() {
+ resp.Clients = append(resp.Clients, ClientSession{
+ ClientID: c.ID(),
+ SessionID: c.Session().ID(),
+ Logfile: c.Logfile(),
+ DebugAddr: c.DebugAddress(),
+ })
+ }
+ }
+ return r.Reply(ctx, resp, nil)
}
- if err := r.Reply(ctx, resp, nil); err != nil {
- event.Error(ctx, "replying to sessions request", err)
- }
- return true
+ return handler(ctx, r)
}
- return false
}
func sendError(ctx context.Context, req *jsonrpc2.Request, err error) {
diff --git a/internal/lsp/lsprpc/lsprpc_test.go b/internal/lsp/lsprpc/lsprpc_test.go
index bb2c3b3..37a3e9a 100644
--- a/internal/lsp/lsprpc/lsprpc_test.go
+++ b/internal/lsp/lsprpc/lsprpc_test.go
@@ -11,6 +11,7 @@
"testing"
"time"
+ "golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/jsonrpc2/servertest"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/debug"
@@ -54,7 +55,7 @@
ts := servertest.NewPipeServer(ctx, ss)
defer ts.Close()
cc := ts.Connect(ctx)
- cc.AddHandler(protocol.ClientHandler(client))
+ go cc.Run(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound))
protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
@@ -127,7 +128,11 @@
for _, test := range tests {
t.Run(test.serverType, func(t *testing.T) {
cc := test.ts.Connect(baseCtx)
- cc.AddHandler(protocol.Canceller{})
+ sd := protocol.ServerDispatcher(cc)
+ go cc.Run(baseCtx,
+ protocol.Handlers(
+ jsonrpc2.MethodNotFound))
+
ctx := context.Background()
ctx1, cancel1 := context.WithCancel(ctx)
var (
@@ -137,11 +142,11 @@
wg.Add(2)
go func() {
defer wg.Done()
- _, err1 = protocol.ServerDispatcher(cc).Hover(ctx1, &protocol.HoverParams{})
+ _, err1 = sd.Hover(ctx1, &protocol.HoverParams{})
}()
go func() {
defer wg.Done()
- _, err2 = protocol.ServerDispatcher(cc).Resolve(ctx, &protocol.CompletionItem{})
+ _, err2 = sd.Resolve(ctx, &protocol.CompletionItem{})
}()
// Wait for the Hover request to start.
<-server.started
@@ -153,7 +158,7 @@
if err2 != nil {
t.Errorf("uncancelled Hover(): err: %v", err2)
}
- if _, err := protocol.ServerDispatcher(cc).Resolve(ctx, &protocol.CompletionItem{}); err != nil {
+ if _, err := sd.Resolve(ctx, &protocol.CompletionItem{}); err != nil {
t.Errorf("subsequent Hover(): %v", err)
}
})
diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go
index ebb0642..00610ad 100644
--- a/internal/lsp/protocol/protocol.go
+++ b/internal/lsp/protocol/protocol.go
@@ -19,86 +19,61 @@
RequestCancelledError = -32800
)
-type clientHandler struct {
- jsonrpc2.EmptyHandler
- client Client
-}
-
-// ClientHandler returns a jsonrpc2.Handler that handles the LSP client
-// protocol.
-func ClientHandler(client Client) jsonrpc2.Handler {
- return &clientHandler{client: client}
-}
-
-type serverHandler struct {
- jsonrpc2.EmptyHandler
- server Server
-}
-
-// ServerHandler returns a jsonrpc2.Handler that handles the LSP server
-// protocol.
-func ServerHandler(server Server) jsonrpc2.Handler {
- return &serverHandler{server: server}
-}
-
// ClientDispatcher returns a Client that dispatches LSP requests across the
// given jsonrpc2 connection.
func ClientDispatcher(conn *jsonrpc2.Conn) Client {
+ conn.OnCancelled(cancelCall)
return &clientDispatcher{Conn: conn}
}
// ServerDispatcher returns a Server that dispatches LSP requests across the
// given jsonrpc2 connection.
func ServerDispatcher(conn *jsonrpc2.Conn) Server {
+ conn.OnCancelled(cancelCall)
return &serverDispatcher{Conn: conn}
}
-// Canceller is a jsonrpc2.Handler that handles LSP request cancellation.
-type Canceller struct{ jsonrpc2.EmptyHandler }
-
-func (Canceller) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
- if direction == jsonrpc2.Receive && r.Method == "$/cancelRequest" {
- var params CancelParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- event.Error(ctx, "", err)
- } else {
- v := jsonrpc2.ID{}
- if n, ok := params.ID.(float64); ok {
- v.Number = int64(n)
- } else if s, ok := params.ID.(string); ok {
- v.Name = s
- } else {
- event.Error(ctx, fmt.Sprintf("Request ID %v malformed", params.ID), nil)
- return ctx
- }
- conn.Cancel(v)
- }
- }
- return ctx
+func Handlers(handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return CancelHandler(
+ CancelHandler(
+ jsonrpc2.AsyncHandler(
+ jsonrpc2.MustReply(handler))))
}
-func (Canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
- if cancelled {
- return false
+func CancelHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
+ handler, canceller := jsonrpc2.CancelHandler(handler)
+ return func(ctx context.Context, req *jsonrpc2.Request) error {
+ if req.Method != "$/cancelRequest" {
+ return handler(ctx, req)
+ }
+ var params CancelParams
+ if err := json.Unmarshal(*req.Params, ¶ms); err != nil {
+ return sendParseError(ctx, req, err)
+ }
+ v := jsonrpc2.ID{}
+ if n, ok := params.ID.(float64); ok {
+ v.Number = int64(n)
+ } else if s, ok := params.ID.(string); ok {
+ v.Name = s
+ } else {
+ return sendParseError(ctx, req, fmt.Errorf("Request ID %v malformed", params.ID))
+ }
+ canceller(v)
+ return req.Reply(ctx, nil, nil)
}
+}
+
+func cancelCall(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID) {
ctx = xcontext.Detach(ctx)
ctx, done := event.StartSpan(ctx, "protocol.canceller")
defer done()
// Note that only *jsonrpc2.ID implements json.Marshaler.
conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id})
- return true
}
-func (Canceller) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- // Hide cancellations from downstream handlers.
- return r.Method == "$/cancelRequest"
-}
-
-func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) {
+func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) error {
if _, ok := err.(*jsonrpc2.Error); !ok {
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
}
- if err := req.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
+ return req.Reply(ctx, nil, err)
}
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
index c873327..2cf5af0 100644
--- a/internal/lsp/protocol/tsclient.go
+++ b/internal/lsp/protocol/tsclient.go
@@ -3,7 +3,7 @@
// Package protocol contains data types and code for LSP jsonrpcs
// generated automatically from vscode-languageserver-node
// commit: 151b520c995ee3d76729b5c46258ab273d989726
-// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time)
+// last fetched Mon Mar 30 2020 21:01:17 GMT-0400 (Eastern Daylight Time)
// Code generated (see typescript/README.md) DO NOT EDIT.
@@ -12,7 +12,6 @@
"encoding/json"
"golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/xcontext"
)
@@ -31,145 +30,100 @@
ApplyEdit(context.Context, *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResponse, error)
}
-func (h clientHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- if delivered {
- return false
- }
- if ctx.Err() != nil {
- ctx := xcontext.Detach(ctx)
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
- return true
- }
- switch r.Method {
- case "window/showMessage": // notif
- var params ShowMessageParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
+func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
+ if ctx.Err() != nil {
+ ctx := xcontext.Detach(ctx)
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
}
- if err := h.client.ShowMessage(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "window/logMessage": // notif
- var params LogMessageParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.client.LogMessage(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "telemetry/event": // notif
- var params interface{}
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.client.Event(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/publishDiagnostics": // notif
- var params PublishDiagnosticsParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.client.PublishDiagnostics(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "$/progress": // notif
- var params ProgressParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.client.Progress(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/workspaceFolders": // req
- if r.Params != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
- return true
- }
- resp, err := h.client.WorkspaceFolders(ctx)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/configuration": // req
- var params ParamConfiguration
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.client.Configuration(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "window/workDoneProgress/create": // req
- var params WorkDoneProgressCreateParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- err := h.client.WorkDoneProgressCreate(ctx, ¶ms)
- if err := r.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "client/registerCapability": // req
- var params RegistrationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- err := h.client.RegisterCapability(ctx, ¶ms)
- if err := r.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "client/unregisterCapability": // req
- var params UnregistrationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- err := h.client.UnregisterCapability(ctx, ¶ms)
- if err := r.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "window/showMessageRequest": // req
- var params ShowMessageRequestParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.client.ShowMessageRequest(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/applyEdit": // req
- var params ApplyWorkspaceEditParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.client.ApplyEdit(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- default:
- return false
+ switch r.Method {
+ case "window/showMessage": // notif
+ var params ShowMessageParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.ShowMessage(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "window/logMessage": // notif
+ var params LogMessageParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.LogMessage(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "telemetry/event": // notif
+ var params interface{}
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.Event(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/publishDiagnostics": // notif
+ var params PublishDiagnosticsParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.PublishDiagnostics(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "$/progress": // notif
+ var params ProgressParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.Progress(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "workspace/workspaceFolders": // req
+ if r.Params != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
+ }
+ resp, err := client.WorkspaceFolders(ctx)
+ return r.Reply(ctx, resp, err)
+ case "workspace/configuration": // req
+ var params ParamConfiguration
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := client.Configuration(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "window/workDoneProgress/create": // req
+ var params WorkDoneProgressCreateParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.WorkDoneProgressCreate(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "client/registerCapability": // req
+ var params RegistrationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.RegisterCapability(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "client/unregisterCapability": // req
+ var params UnregistrationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := client.UnregisterCapability(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "window/showMessageRequest": // req
+ var params ShowMessageRequestParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := client.ShowMessageRequest(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "workspace/applyEdit": // req
+ var params ApplyWorkspaceEditParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := client.ApplyEdit(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ default:
+ return handler(ctx, r)
+ }
}
}
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
index 221c41a..488cb78 100644
--- a/internal/lsp/protocol/tsprotocol.go
+++ b/internal/lsp/protocol/tsprotocol.go
@@ -1,7 +1,7 @@
// Package protocol contains data types and code for LSP jsonrpcs
// generated automatically from vscode-languageserver-node
// commit: 151b520c995ee3d76729b5c46258ab273d989726
-// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time)
+// last fetched Mon Mar 30 2020 21:01:17 GMT-0400 (Eastern Daylight Time)
package protocol
// Code generated (see typescript/README.md) DO NOT EDIT.
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
index cefd5db..fc675d8 100644
--- a/internal/lsp/protocol/tsserver.go
+++ b/internal/lsp/protocol/tsserver.go
@@ -3,7 +3,7 @@
// Package protocol contains data types and code for LSP jsonrpcs
// generated automatically from vscode-languageserver-node
// commit: 151b520c995ee3d76729b5c46258ab273d989726
-// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time)
+// last fetched Mon Mar 30 2020 21:01:17 GMT-0400 (Eastern Daylight Time)
// Code generated (see typescript/README.md) DO NOT EDIT.
@@ -12,7 +12,6 @@
"encoding/json"
"golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/xcontext"
)
@@ -69,548 +68,359 @@
NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)
}
-func (h serverHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- if delivered {
- return false
- }
- if ctx.Err() != nil {
- ctx := xcontext.Detach(ctx)
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
- return true
- }
- switch r.Method {
- case "workspace/didChangeWorkspaceFolders": // notif
- var params DidChangeWorkspaceFoldersParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
+func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
+ if ctx.Err() != nil {
+ ctx := xcontext.Detach(ctx)
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
}
- if err := h.server.DidChangeWorkspaceFolders(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "window/workDoneProgress/cancel": // notif
- var params WorkDoneProgressCancelParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.WorkDoneProgressCancel(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "initialized": // notif
- var params InitializedParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.Initialized(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "exit": // notif
- if err := h.server.Exit(ctx); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/didChangeConfiguration": // notif
- var params DidChangeConfigurationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidChangeConfiguration(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/didOpen": // notif
- var params DidOpenTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidOpen(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/didChange": // notif
- var params DidChangeTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidChange(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/didClose": // notif
- var params DidCloseTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidClose(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/didSave": // notif
- var params DidSaveTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidSave(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/willSave": // notif
- var params WillSaveTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.WillSave(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/didChangeWatchedFiles": // notif
- var params DidChangeWatchedFilesParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.DidChangeWatchedFiles(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "$/setTraceNotification": // notif
- var params SetTraceParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.SetTraceNotification(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "$/logTraceNotification": // notif
- var params LogTraceParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- if err := h.server.LogTraceNotification(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/implementation": // req
- var params ImplementationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Implementation(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/typeDefinition": // req
- var params TypeDefinitionParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.TypeDefinition(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/documentColor": // req
- var params DocumentColorParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.DocumentColor(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/colorPresentation": // req
- var params ColorPresentationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.ColorPresentation(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/foldingRange": // req
- var params FoldingRangeParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.FoldingRange(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/declaration": // req
- var params DeclarationParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Declaration(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/selectionRange": // req
- var params SelectionRangeParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.SelectionRange(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "initialize": // req
- var params ParamInitialize
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Initialize(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "shutdown": // req
- if r.Params != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
- return true
- }
- err := h.server.Shutdown(ctx)
- if err := r.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/willSaveWaitUntil": // req
- var params WillSaveTextDocumentParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.WillSaveWaitUntil(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/completion": // req
- var params CompletionParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Completion(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "completionItem/resolve": // req
- var params CompletionItem
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Resolve(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/hover": // req
- var params HoverParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Hover(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/signatureHelp": // req
- var params SignatureHelpParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.SignatureHelp(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/definition": // req
- var params DefinitionParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Definition(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/references": // req
- var params ReferenceParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.References(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/documentHighlight": // req
- var params DocumentHighlightParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.DocumentHighlight(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/documentSymbol": // req
- var params DocumentSymbolParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.DocumentSymbol(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/codeAction": // req
- var params CodeActionParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.CodeAction(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/symbol": // req
- var params WorkspaceSymbolParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Symbol(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/codeLens": // req
- var params CodeLensParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.CodeLens(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "codeLens/resolve": // req
- var params CodeLens
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.ResolveCodeLens(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/documentLink": // req
- var params DocumentLinkParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.DocumentLink(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "documentLink/resolve": // req
- var params DocumentLink
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.ResolveDocumentLink(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/formatting": // req
- var params DocumentFormattingParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Formatting(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/rangeFormatting": // req
- var params DocumentRangeFormattingParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.RangeFormatting(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/onTypeFormatting": // req
- var params DocumentOnTypeFormattingParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.OnTypeFormatting(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/rename": // req
- var params RenameParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.Rename(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/prepareRename": // req
- var params PrepareRenameParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.PrepareRename(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "workspace/executeCommand": // req
- var params ExecuteCommandParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.ExecuteCommand(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/prepareCallHierarchy": // req
- var params CallHierarchyPrepareParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.PrepareCallHierarchy(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "callHierarchy/incomingCalls": // req
- var params CallHierarchyIncomingCallsParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.IncomingCalls(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "callHierarchy/outgoingCalls": // req
- var params CallHierarchyOutgoingCallsParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.OutgoingCalls(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/semanticTokens": // req
- var params SemanticTokensParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.SemanticTokens(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/semanticTokens/edits": // req
- var params SemanticTokensEditsParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.SemanticTokensEdits(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- case "textDocument/semanticTokens/range": // req
- var params SemanticTokensRangeParams
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.SemanticTokensRange(ctx, ¶ms)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
- default:
- var params interface{}
- if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
- }
- resp, err := h.server.NonstandardRequest(ctx, r.Method, params)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
+ switch r.Method {
+ case "workspace/didChangeWorkspaceFolders": // notif
+ var params DidChangeWorkspaceFoldersParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidChangeWorkspaceFolders(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "window/workDoneProgress/cancel": // notif
+ var params WorkDoneProgressCancelParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.WorkDoneProgressCancel(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "initialized": // notif
+ var params InitializedParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.Initialized(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "exit": // notif
+ return server.Exit(ctx)
+ case "workspace/didChangeConfiguration": // notif
+ var params DidChangeConfigurationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidChangeConfiguration(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/didOpen": // notif
+ var params DidOpenTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidOpen(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/didChange": // notif
+ var params DidChangeTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidChange(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/didClose": // notif
+ var params DidCloseTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidClose(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/didSave": // notif
+ var params DidSaveTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidSave(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/willSave": // notif
+ var params WillSaveTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.WillSave(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "workspace/didChangeWatchedFiles": // notif
+ var params DidChangeWatchedFilesParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.DidChangeWatchedFiles(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "$/setTraceNotification": // notif
+ var params SetTraceParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.SetTraceNotification(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "$/logTraceNotification": // notif
+ var params LogTraceParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ err := server.LogTraceNotification(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/implementation": // req
+ var params ImplementationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Implementation(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/typeDefinition": // req
+ var params TypeDefinitionParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.TypeDefinition(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/documentColor": // req
+ var params DocumentColorParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.DocumentColor(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/colorPresentation": // req
+ var params ColorPresentationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.ColorPresentation(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/foldingRange": // req
+ var params FoldingRangeParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.FoldingRange(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/declaration": // req
+ var params DeclarationParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Declaration(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/selectionRange": // req
+ var params SelectionRangeParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.SelectionRange(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "initialize": // req
+ var params ParamInitialize
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Initialize(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "shutdown": // req
+ if r.Params != nil {
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
+ }
+ err := server.Shutdown(ctx)
+ return r.Reply(ctx, nil, err)
+ case "textDocument/willSaveWaitUntil": // req
+ var params WillSaveTextDocumentParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.WillSaveWaitUntil(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/completion": // req
+ var params CompletionParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Completion(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "completionItem/resolve": // req
+ var params CompletionItem
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Resolve(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/hover": // req
+ var params HoverParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Hover(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/signatureHelp": // req
+ var params SignatureHelpParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.SignatureHelp(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/definition": // req
+ var params DefinitionParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Definition(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/references": // req
+ var params ReferenceParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.References(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/documentHighlight": // req
+ var params DocumentHighlightParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.DocumentHighlight(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/documentSymbol": // req
+ var params DocumentSymbolParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.DocumentSymbol(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/codeAction": // req
+ var params CodeActionParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.CodeAction(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "workspace/symbol": // req
+ var params WorkspaceSymbolParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Symbol(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/codeLens": // req
+ var params CodeLensParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.CodeLens(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "codeLens/resolve": // req
+ var params CodeLens
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.ResolveCodeLens(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/documentLink": // req
+ var params DocumentLinkParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.DocumentLink(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "documentLink/resolve": // req
+ var params DocumentLink
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.ResolveDocumentLink(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/formatting": // req
+ var params DocumentFormattingParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Formatting(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/rangeFormatting": // req
+ var params DocumentRangeFormattingParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.RangeFormatting(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/onTypeFormatting": // req
+ var params DocumentOnTypeFormattingParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.OnTypeFormatting(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/rename": // req
+ var params RenameParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.Rename(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/prepareRename": // req
+ var params PrepareRenameParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.PrepareRename(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "workspace/executeCommand": // req
+ var params ExecuteCommandParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.ExecuteCommand(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/prepareCallHierarchy": // req
+ var params CallHierarchyPrepareParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.PrepareCallHierarchy(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "callHierarchy/incomingCalls": // req
+ var params CallHierarchyIncomingCallsParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.IncomingCalls(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "callHierarchy/outgoingCalls": // req
+ var params CallHierarchyOutgoingCallsParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.OutgoingCalls(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/semanticTokens": // req
+ var params SemanticTokensParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.SemanticTokens(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/semanticTokens/edits": // req
+ var params SemanticTokensEditsParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.SemanticTokensEdits(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ case "textDocument/semanticTokens/range": // req
+ var params SemanticTokensRangeParams
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.SemanticTokensRange(ctx, ¶ms)
+ return r.Reply(ctx, resp, err)
+ default:
+ var params interface{}
+ if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
+ return sendParseError(ctx, r, err)
+ }
+ resp, err := server.NonstandardRequest(ctx, r.Method, params)
+ return r.Reply(ctx, resp, err)
+ }
}
}
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index 388f439..a006acb 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -908,8 +908,7 @@
// commonly used output
const notNil = `if r.Params != nil {
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
- return true
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
}`;
// Go code for notifications. Side is client or server, m is the request
@@ -925,18 +924,12 @@
if (a != '' && a != 'void') {
case1 = `var params ${a}
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
+ return sendParseError(ctx, r, err)
}
- if err := h.${side.name}.${nm}(ctx, ¶ms); err != nil {
- event.Error(ctx, "", err)
- }
- return true`;
+ err:= ${side.name}.${nm}(ctx, ¶ms)
+ return r.Reply(ctx, nil, err)`
} else {
- case1 = `if err := h.${side.name}.${nm}(ctx); err != nil {
- event.Error(ctx, "", err)
- }
- return true`;
+ case1 = `return ${side.name}.${nm}(ctx)`;
}
side.cases.push(`${caseHdr}\n${case1}`);
@@ -966,26 +959,19 @@
if (extraTypes.has('Param' + nm)) a = 'Param' + nm
case1 = `var params ${a}
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
+ return sendParseError(ctx, r, err)
}`;
}
const arg2 = a == '' ? '' : ', ¶ms';
- let case2 = `if err := h.${side.name}.${nm}(ctx${arg2}); err != nil {
+ let case2 = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
event.Error(ctx, "", err)
}`;
if (b != '' && b != 'void') {
- case2 = `resp, err := h.${side.name}.${nm}(ctx${arg2})
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true`;
+ case2 = `resp, err := ${side.name}.${nm}(ctx${arg2})
+ return r.Reply(ctx, resp, err)`;
} else { // response is nil
- case2 = `err := h.${side.name}.${nm}(ctx${arg2})
- if err := r.Reply(ctx, nil, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true`
+ case2 = `err := ${side.name}.${nm}(ctx${arg2})
+ return r.Reply(ctx, nil, err)`
}
side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
@@ -1087,7 +1073,6 @@
"encoding/json"
"golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/telemetry/event"
"golang.org/x/tools/internal/xcontext"
)
`);
@@ -1095,19 +1080,16 @@
f(`type ${a} interface {`);
side.methods.forEach((v) => {f(v)});
f('}\n');
- f(`func (h ${
- side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
- if delivered {
- return false
- }
+ f(`func ${a}Handler(${side.name} ${a}, handler jsonrpc2.Handler) jsonrpc2.Handler {
+ return func(ctx context.Context, r *jsonrpc2.Request) error {
if ctx.Err() != nil {
ctx := xcontext.Detach(ctx)
- r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
- return true
+ return r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, ""))
}
switch r.Method {`);
side.cases.forEach((v) => {f(v)});
f(`
+ }
}
}`);
f(`
@@ -1132,18 +1114,14 @@
}
`)
client.cases.push(`default:
- return false`)
+ return handler(ctx, r)`)
server.cases.push(`default:
var params interface{}
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
- sendParseError(ctx, r, err)
- return true
+ return sendParseError(ctx, r, err)
}
- resp, err := h.server.NonstandardRequest(ctx, r.Method, params)
- if err := r.Reply(ctx, resp, err); err != nil {
- event.Error(ctx, "", err)
- }
- return true
+ resp, err := server.NonstandardRequest(ctx, r.Method, params)
+ return r.Reply(ctx, resp, err)
`)
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index c43a394..e3defa2 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -136,9 +136,6 @@
qf types.Qualifier
opts *completionOptions
- // ctx is the context associated with this completion request.
- ctx context.Context
-
// filename is the name of the file associated with this completion request.
filename string
@@ -247,13 +244,6 @@
return p.content[p.cursor-p.spanRange.Start:]
}
-func (c *completer) deepCompletionContext() (context.Context, context.CancelFunc) {
- if c.opts.budget == 0 {
- return context.WithCancel(c.ctx)
- }
- return context.WithDeadline(c.ctx, c.startTime.Add(c.opts.budget))
-}
-
func (c *completer) setSurrounding(ident *ast.Ident) {
if c.surrounding != nil {
return
@@ -292,7 +282,7 @@
// found adds a candidate completion. We will also search through the object's
// members for more candidates.
-func (c *completer) found(cand candidate) {
+func (c *completer) found(ctx context.Context, cand candidate) {
obj := cand.obj
if obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() {
@@ -341,7 +331,7 @@
// We only care about named types (i.e. don't want builtin types).
if _, isNamed := obj.Type().(*types.Named); isNamed {
- c.literal(obj.Type(), cand.imp)
+ c.literal(ctx, obj.Type(), cand.imp)
}
}
@@ -370,13 +360,13 @@
// Avoid calling c.item() for deep candidates that wouldn't be in the top
// MaxDeepCompletions anyway.
if !c.inDeepCompletion() || c.deepState.isHighScore(cand.score) {
- if item, err := c.item(cand); err == nil {
+ if item, err := c.item(ctx, cand); err == nil {
c.items = append(c.items, item)
}
}
}
- c.deepSearch(cand)
+ c.deepSearch(ctx, cand)
}
// candidate represents a completion candidate.
@@ -482,7 +472,6 @@
pkg: pkg,
snapshot: snapshot,
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
- ctx: ctx,
filename: fh.Identity().URI.Filename(),
file: file,
path: path,
@@ -512,32 +501,40 @@
c.deepState.maxDepth = -1
}
+ var cancel context.CancelFunc
+ if c.opts.budget == 0 {
+ ctx, cancel = context.WithCancel(ctx)
+ } else {
+ ctx, cancel = context.WithDeadline(ctx, c.startTime.Add(c.opts.budget))
+ }
+ defer cancel()
+
if surrounding := c.containingIdent(src); surrounding != nil {
c.setSurrounding(surrounding)
}
- c.inference = expectedCandidate(c)
+ c.inference = expectedCandidate(ctx, c)
defer c.sortItems()
// If we're inside a comment return comment completions
for _, comment := range file.Comments {
if comment.Pos() < rng.Start && rng.Start <= comment.End() {
- c.populateCommentCompletions(comment)
+ c.populateCommentCompletions(ctx, comment)
return c.items, c.getSurrounding(), nil
}
}
// Struct literals are handled entirely separately.
if c.wantStructFieldCompletions() {
- if err := c.structLiteralFieldName(); err != nil {
+ if err := c.structLiteralFieldName(ctx); err != nil {
return nil, nil, err
}
return c.items, c.getSurrounding(), nil
}
if lt := c.wantLabelCompletion(); lt != labelNone {
- c.labels(lt)
+ c.labels(ctx, lt)
return c.items, c.getSurrounding(), nil
}
@@ -555,7 +552,7 @@
case *ast.Ident:
// Is this the Sel part of a selector?
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
- if err := c.selector(sel); err != nil {
+ if err := c.selector(ctx, sel); err != nil {
return nil, nil, err
}
return c.items, c.getSurrounding(), nil
@@ -573,19 +570,19 @@
return nil, nil, ErrIsDefinition{objStr: objStr}
}
}
- if err := c.lexical(); err != nil {
+ if err := c.lexical(ctx); err != nil {
return nil, nil, err
}
// The function name hasn't been typed yet, but the parens are there:
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
- if err := c.selector(&ast.SelectorExpr{X: n.X}); err != nil {
+ if err := c.selector(ctx, &ast.SelectorExpr{X: n.X}); err != nil {
return nil, nil, err
}
case *ast.SelectorExpr:
- if err := c.selector(n); err != nil {
+ if err := c.selector(ctx, n); err != nil {
return nil, nil, err
}
@@ -595,7 +592,7 @@
default:
// fallback to lexical completions
- if err := c.lexical(); err != nil {
+ if err := c.lexical(ctx); err != nil {
return nil, nil, err
}
}
@@ -687,7 +684,7 @@
// populateCommentCompletions yields completions for an exported
// variable immediately preceding comment.
-func (c *completer) populateCommentCompletions(comment *ast.CommentGroup) {
+func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast.CommentGroup) {
// Using the comment position find the line after
fset := c.snapshot.View().Session().Cache().FileSet()
@@ -720,7 +717,7 @@
}
exportedVar := c.pkg.GetTypesInfo().ObjectOf(name)
- c.found(candidate{obj: exportedVar, score: stdScore})
+ c.found(ctx, candidate{obj: exportedVar, score: stdScore})
}
}
}
@@ -748,11 +745,11 @@
)
// selector finds completions for the specified selector expression.
-func (c *completer) selector(sel *ast.SelectorExpr) error {
+func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
// Is sel a qualified identifier?
if id, ok := sel.X.(*ast.Ident); ok {
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
- c.packageMembers(pkgName.Imported(), stdScore, nil)
+ c.packageMembers(ctx, pkgName.Imported(), stdScore, nil)
return nil
}
}
@@ -760,21 +757,21 @@
// Invariant: sel is a true selector.
tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
if ok {
- return c.methodsAndFields(tv.Type, tv.Addressable(), nil)
+ return c.methodsAndFields(ctx, tv.Type, tv.Addressable(), nil)
}
// Try unimported packages.
if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported {
- if err := c.unimportedMembers(id); err != nil {
+ if err := c.unimportedMembers(ctx, id); err != nil {
return err
}
}
return nil
}
-func (c *completer) unimportedMembers(id *ast.Ident) error {
+func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error {
// Try loaded packages first. They're relevant, fast, and fully typed.
- known, err := c.snapshot.CachedImportPaths(c.ctx)
+ known, err := c.snapshot.CachedImportPaths(ctx)
if err != nil {
return err
}
@@ -789,8 +786,8 @@
var relevances map[string]int
if len(paths) != 0 {
- c.snapshot.View().RunProcessEnvFunc(c.ctx, func(opts *imports.Options) error {
- relevances = imports.ScoreImportPaths(c.ctx, opts.Env, paths)
+ c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
+ relevances = imports.ScoreImportPaths(ctx, opts.Env, paths)
return nil
})
}
@@ -807,14 +804,15 @@
if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
imp.name = pkg.GetTypes().Name()
}
- c.packageMembers(pkg.GetTypes(), stdScore+.01*float64(relevance), imp)
+ c.packageMembers(ctx, pkg.GetTypes(), stdScore+.01*float64(relevance), imp)
if len(c.items) >= unimportedMemberTarget {
return nil
}
}
- ctx, cancel := c.deepCompletionContext()
+ ctx, cancel := context.WithCancel(ctx)
defer cancel()
+
var mu sync.Mutex
add := func(pkgExport imports.PackageExport) {
mu.Lock()
@@ -827,7 +825,7 @@
pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
for _, export := range pkgExport.Exports {
score := stdScore + 0.01*float64(pkgExport.Fix.Relevance)
- c.found(candidate{
+ c.found(ctx, candidate{
obj: types.NewVar(0, pkg, export, nil),
score: score,
imp: &importInfo{
@@ -845,11 +843,11 @@
})
}
-func (c *completer) packageMembers(pkg *types.Package, score float64, imp *importInfo) {
+func (c *completer) packageMembers(ctx context.Context, pkg *types.Package, score float64, imp *importInfo) {
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
- c.found(candidate{
+ c.found(ctx, candidate{
obj: obj,
score: score,
imp: imp,
@@ -858,7 +856,7 @@
}
}
-func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *importInfo) error {
+func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addressable bool, imp *importInfo) error {
mset := c.methodSetCache[methodSetKey{typ, addressable}]
if mset == nil {
if addressable && !types.IsInterface(typ) && !isPointer(typ) {
@@ -872,7 +870,7 @@
}
for i := 0; i < mset.Len(); i++ {
- c.found(candidate{
+ c.found(ctx, candidate{
obj: mset.At(i).Obj(),
score: stdScore,
imp: imp,
@@ -882,7 +880,7 @@
// Add fields of T.
eachField(typ, func(v *types.Var) {
- c.found(candidate{
+ c.found(ctx, candidate{
obj: v,
score: stdScore - 0.01,
imp: imp,
@@ -894,7 +892,7 @@
}
// lexical finds completions in the lexical environment.
-func (c *completer) lexical() error {
+func (c *completer) lexical(ctx context.Context) error {
var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
for _, n := range c.path {
// Include *FuncType scope if pos is inside the function body.
@@ -978,7 +976,7 @@
// If we haven't already added a candidate for an object with this name.
if _, ok := seen[obj.Name()]; !ok {
seen[obj.Name()] = struct{}{}
- c.found(candidate{
+ c.found(ctx, candidate{
obj: obj,
score: score,
addressable: isVar(obj),
@@ -1007,7 +1005,7 @@
if imports.ImportPathToAssumedName(pkg.Path()) != pkg.Name() {
imp.name = pkg.Name()
}
- c.found(candidate{
+ c.found(ctx, candidate{
obj: obj,
score: stdScore,
imp: imp,
@@ -1018,7 +1016,7 @@
}
if c.opts.unimported {
- if err := c.unimportedPackages(seen); err != nil {
+ if err := c.unimportedPackages(ctx, seen); err != nil {
return err
}
}
@@ -1036,7 +1034,7 @@
// our expected type is "[]int", this will add a candidate of
// "[]int{}".
if _, named := t.(*types.Named); !named {
- c.literal(t, nil)
+ c.literal(ctx, t, nil)
}
}
@@ -1046,17 +1044,14 @@
return nil
}
-func (c *completer) unimportedPackages(seen map[string]struct{}) error {
- ctx, cancel := c.deepCompletionContext()
- defer cancel()
-
- prefix := ""
+func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error {
+ var prefix string
if c.surrounding != nil {
prefix = c.surrounding.Prefix()
}
initialItemCount := len(c.items)
- known, err := c.snapshot.CachedImportPaths(c.ctx)
+ known, err := c.snapshot.CachedImportPaths(ctx)
if err != nil {
return err
}
@@ -1071,7 +1066,7 @@
var relevances map[string]int
if len(paths) != 0 {
c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
- relevances = imports.ScoreImportPaths(c.ctx, opts.Env, paths)
+ relevances = imports.ScoreImportPaths(ctx, opts.Env, paths)
return nil
})
}
@@ -1086,7 +1081,7 @@
imp.name = pkg.GetTypes().Name()
}
score := 0.01 * float64(relevance)
- c.found(candidate{
+ c.found(ctx, candidate{
obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), pkg.GetTypes()),
score: score,
imp: imp,
@@ -1096,6 +1091,9 @@
}
}
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
var mu sync.Mutex
add := func(pkg imports.ImportFix) {
mu.Lock()
@@ -1115,7 +1113,7 @@
// multiple packages of the same name as completion suggestions, since
// only one will be chosen.
obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkg.StmtInfo.ImportPath, pkg.IdentName))
- c.found(candidate{
+ c.found(ctx, candidate{
obj: obj,
score: score,
imp: &importInfo{
@@ -1163,7 +1161,7 @@
}
// structLiteralFieldName finds completions for struct field names inside a struct literal.
-func (c *completer) structLiteralFieldName() error {
+func (c *completer) structLiteralFieldName(ctx context.Context) error {
clInfo := c.enclosingCompositeLiteral
// Mark fields of the composite literal that have already been set,
@@ -1190,7 +1188,7 @@
for i := 0; i < t.NumFields(); i++ {
field := t.Field(i)
if !addedFields[field] {
- c.found(candidate{
+ c.found(ctx, candidate{
obj: field,
score: highScore,
})
@@ -1200,10 +1198,10 @@
// Add lexical completions if we aren't certain we are in the key part of a
// key-value pair.
if clInfo.maybeInFieldName {
- return c.lexical()
+ return c.lexical(ctx)
}
default:
- return c.lexical()
+ return c.lexical(ctx)
}
return nil
@@ -1468,7 +1466,7 @@
// expectedCandidate returns information about the expected candidate
// for an expression at the query position.
-func expectedCandidate(c *completer) (inf candidateInference) {
+func expectedCandidate(ctx context.Context, c *completer) (inf candidateInference) {
inf.typeName = expectTypeName(c)
if c.enclosingCompositeLiteral != nil {
@@ -1592,7 +1590,7 @@
// inferred type from its parent node.
defer func() {
inf = c.builtinArgType(obj, node, inf)
- inf.objKind = c.builtinArgKind(obj, node)
+ inf.objKind = c.builtinArgKind(ctx, obj, node)
}()
// The expected type of builtin arguments like append() is
diff --git a/internal/lsp/source/completion_builtin.go b/internal/lsp/source/completion_builtin.go
index fb247ab..f314146 100644
--- a/internal/lsp/source/completion_builtin.go
+++ b/internal/lsp/source/completion_builtin.go
@@ -5,6 +5,7 @@
package source
import (
+ "context"
"go/ast"
"go/types"
)
@@ -12,8 +13,8 @@
// builtinArgKind determines the expected object kind for a builtin
// argument. It attempts to use the AST hints from builtin.go where
// possible.
-func (c *completer) builtinArgKind(obj types.Object, call *ast.CallExpr) objKind {
- astObj, err := c.snapshot.View().LookupBuiltin(c.ctx, obj.Name())
+func (c *completer) builtinArgKind(ctx context.Context, obj types.Object, call *ast.CallExpr) objKind {
+ astObj, err := c.snapshot.View().LookupBuiltin(ctx, obj.Name())
if err != nil {
return 0
}
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index e168534..b40b9f4 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -23,12 +23,12 @@
)
// formatCompletion creates a completion item for a given candidate.
-func (c *completer) item(cand candidate) (CompletionItem, error) {
+func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
obj := cand.obj
// Handle builtin types separately.
if obj.Parent() == types.Universe {
- return c.formatBuiltin(cand), nil
+ return c.formatBuiltin(ctx, cand), nil
}
var (
@@ -46,7 +46,7 @@
// expandFuncCall mutates the completion label, detail, and snippet
// to that of an invocation of sig.
expandFuncCall := func(sig *types.Signature) {
- params := formatParams(c.ctx, c.snapshot, c.pkg, sig, c.qf)
+ params := formatParams(ctx, c.snapshot, c.pkg, sig, c.qf)
snip = c.functionCallSnippet(label, params)
results, writeParens := formatResults(sig.Results(), c.qf)
detail = "func" + formatFunction(params, results, writeParens)
@@ -67,7 +67,7 @@
detail = "struct{...}" // for anonymous structs
} else if obj.IsField() {
var err error
- detail, err = formatFieldType(c.ctx, c.snapshot, c.pkg, obj)
+ detail, err = formatFieldType(ctx, c.snapshot, c.pkg, obj)
if err != nil {
detail = types.TypeString(obj.Type(), c.qf)
}
@@ -115,7 +115,7 @@
// If this candidate needs an additional import statement,
// add the additional text edits needed.
if cand.imp != nil {
- addlEdits, err := c.importEdits(cand.imp)
+ addlEdits, err := c.importEdits(ctx, cand.imp)
if err != nil {
return CompletionItem{}, err
}
@@ -144,10 +144,9 @@
if sel := enclosingSelector(c.path, c.pos); sel != nil {
edits, err := prependEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel, prefixOp)
if err != nil {
- event.Error(c.ctx, "error generating prefix edit", err)
- } else {
- protocolEdits = append(protocolEdits, edits...)
+ return CompletionItem{}, err
}
+ protocolEdits = append(protocolEdits, edits...)
} else {
// If there is no selector, just stick the prefix at the start.
insert = prefixOp + insert
@@ -190,13 +189,13 @@
if err != nil {
return item, nil
}
- ident, err := findIdentifier(c.ctx, c.snapshot, pkg, file, obj.Pos())
+ ident, err := findIdentifier(ctx, c.snapshot, pkg, file, obj.Pos())
if err != nil {
return item, nil
}
- hover, err := ident.Hover(c.ctx)
+ hover, err := ident.Hover(ctx)
if err != nil {
- event.Error(c.ctx, "failed to find Hover", err, tag.URI.Of(uri))
+ event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
return item, nil
}
item.Documentation = hover.Synopsis
@@ -207,7 +206,7 @@
}
// importEdits produces the text edits necessary to add the given import to the current file.
-func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) {
+func (c *completer) importEdits(ctx context.Context, imp *importInfo) ([]protocol.TextEdit, error) {
if imp == nil {
return nil, nil
}
@@ -223,7 +222,7 @@
return nil, errors.Errorf("building import completion for %v: no ParseGoHandle for %s", imp.importPath, c.filename)
}
- return computeOneImportFixEdits(c.ctx, c.snapshot.View(), ph, &imports.ImportFix{
+ return computeOneImportFixEdits(ctx, c.snapshot.View(), ph, &imports.ImportFix{
StmtInfo: imports.ImportInfo{
ImportPath: imp.importPath,
Name: imp.name,
@@ -233,7 +232,7 @@
})
}
-func (c *completer) formatBuiltin(cand candidate) CompletionItem {
+func (c *completer) formatBuiltin(ctx context.Context, cand candidate) CompletionItem {
obj := cand.obj
item := CompletionItem{
Label: obj.Name(),
@@ -245,17 +244,17 @@
item.Kind = protocol.ConstantCompletion
case *types.Builtin:
item.Kind = protocol.FunctionCompletion
- astObj, err := c.snapshot.View().LookupBuiltin(c.ctx, obj.Name())
+ astObj, err := c.snapshot.View().LookupBuiltin(ctx, obj.Name())
if err != nil {
- event.Error(c.ctx, "no builtin package", err)
+ event.Error(ctx, "no builtin package", err)
break
}
decl, ok := astObj.Decl.(*ast.FuncDecl)
if !ok {
break
}
- params, _ := formatFieldList(c.ctx, c.snapshot.View(), decl.Type.Params)
- results, writeResultParens := formatFieldList(c.ctx, c.snapshot.View(), decl.Type.Results)
+ params, _ := formatFieldList(ctx, c.snapshot.View(), decl.Type.Params)
+ results, writeResultParens := formatFieldList(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_labels.go b/internal/lsp/source/completion_labels.go
index 0a23aa8..abb2041 100644
--- a/internal/lsp/source/completion_labels.go
+++ b/internal/lsp/source/completion_labels.go
@@ -5,6 +5,7 @@
package source
import (
+ "context"
"go/ast"
"go/token"
"math"
@@ -49,7 +50,7 @@
// labels adds completion items for labels defined in the enclosing
// function.
-func (c *completer) labels(lt labelType) {
+func (c *completer) labels(ctx context.Context, lt labelType) {
if c.enclosingFunc == nil {
return
}
@@ -57,7 +58,7 @@
addLabel := func(score float64, l *ast.LabeledStmt) {
labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
if labelObj != nil {
- c.found(candidate{obj: labelObj, score: score})
+ c.found(ctx, candidate{obj: labelObj, score: score})
}
}
diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go
index 8abb899..fa6c71a 100644
--- a/internal/lsp/source/completion_literal.go
+++ b/internal/lsp/source/completion_literal.go
@@ -5,6 +5,7 @@
package source
import (
+ "context"
"go/ast"
"go/token"
"go/types"
@@ -19,7 +20,7 @@
// literal generates composite literal, function literal, and make()
// completion items.
-func (c *completer) literal(literalType types.Type, imp *importInfo) {
+func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) {
if !c.opts.literal {
return
}
@@ -101,9 +102,9 @@
matchName = types.TypeString(t.Elem(), qf)
}
- addlEdits, err := c.importEdits(imp)
+ addlEdits, err := c.importEdits(ctx, imp)
if err != nil {
- event.Error(c.ctx, "error adding import for literal candidate", err)
+ event.Error(ctx, "error adding import for literal candidate", err)
return
}
@@ -116,7 +117,7 @@
// "foo.&Bar{}".
edits, err := prependEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel, "&")
if err != nil {
- event.Error(c.ctx, "error making edit for literal pointer completion", err)
+ event.Error(ctx, "error making edit for literal pointer completion", err)
return
}
addlEdits = append(addlEdits, edits...)
diff --git a/internal/lsp/source/deep_completion.go b/internal/lsp/source/deep_completion.go
index 2ad1256..2b57782 100644
--- a/internal/lsp/source/deep_completion.go
+++ b/internal/lsp/source/deep_completion.go
@@ -5,6 +5,7 @@
package source
import (
+ "context"
"go/types"
"strings"
"time"
@@ -162,7 +163,7 @@
// deepSearch searches through cand's subordinate objects for more
// completion items.
-func (c *completer) deepSearch(cand candidate) {
+func (c *completer) deepSearch(ctx context.Context, cand candidate) {
if c.deepState.maxDepth == 0 {
return
}
@@ -198,7 +199,7 @@
// the deep chain.
c.deepState.push(obj, true)
// The result of a function call is not addressable.
- c.methodsAndFields(sig.Results().At(0).Type(), false, cand.imp)
+ c.methodsAndFields(ctx, sig.Results().At(0).Type(), false, cand.imp)
c.deepState.pop()
}
}
@@ -208,9 +209,9 @@
switch obj := obj.(type) {
case *types.PkgName:
- c.packageMembers(obj.Imported(), stdScore, cand.imp)
+ c.packageMembers(ctx, obj.Imported(), stdScore, cand.imp)
default:
- c.methodsAndFields(obj.Type(), cand.addressable, cand.imp)
+ c.methodsAndFields(ctx, obj.Type(), cand.addressable, cand.imp)
}
// Pop the object off our search stack.
diff --git a/internal/telemetry/event/event.go b/internal/telemetry/event/event.go
index c28e9d9..0ef81a8 100644
--- a/internal/telemetry/event/event.go
+++ b/internal/telemetry/event/event.go
@@ -54,7 +54,7 @@
func (ev Event) IsRecord() bool { return ev.typ == RecordType }
func (ev Event) Format(f fmt.State, r rune) {
- tagMap := ev.Map()
+ tagMap := TagMap(ev)
if !ev.At.IsZero() {
fmt.Fprint(f, ev.At.Format("2006/01/02 15:04:05 "))
}
@@ -70,6 +70,11 @@
}
for it := ev.Tags(); it.Valid(); it.Advance() {
tag := it.Tag()
+ // msg and err were both already printed above, so we skip them to avoid
+ // double printing
+ if tag.Key == Msg || tag.Key == Err {
+ continue
+ }
fmt.Fprintf(f, "\n\t%v", tag)
}
}
@@ -80,17 +85,13 @@
NewTagIterator(ev.dynamic...))
}
-func (ev Event) Map() TagMap {
- return &eventTagMap{event: ev}
-}
-
-func (m *eventTagMap) Find(key interface{}) Tag {
- for _, tag := range m.event.static {
+func (ev Event) Find(key interface{}) Tag {
+ for _, tag := range ev.static {
if tag.Key == key {
return tag
}
}
- for _, tag := range m.event.dynamic {
+ for _, tag := range ev.dynamic {
if tag.Key == key {
return tag
}
diff --git a/internal/telemetry/event/export.go b/internal/telemetry/event/export.go
index 5b41e99..a2633b6 100644
--- a/internal/telemetry/event/export.go
+++ b/internal/telemetry/event/export.go
@@ -40,7 +40,7 @@
// add the current time to the event
ev.At = time.Now()
// hand the event off to the current exporter
- return exporter(ctx, ev, ev.Map())
+ return exporter(ctx, ev, ev)
}
// dispatch is called to deliver an event to the global exporter if set.
diff --git a/internal/telemetry/event/tag.go b/internal/telemetry/event/tag.go
index 7d7a34c..e3c81eb 100644
--- a/internal/telemetry/event/tag.go
+++ b/internal/telemetry/event/tag.go
@@ -106,9 +106,9 @@
case *StringKey:
fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t))
case *ErrorKey:
- fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t))
+ fmt.Fprintf(f, "%s=%v", key.Name(), key.From(t))
case *ValueKey:
- fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t))
+ fmt.Fprintf(f, "%s=%v", key.Name(), key.From(t))
default:
fmt.Fprintf(f, `%s="invalid type %T"`, key.Name(), key)
}
diff --git a/internal/telemetry/export/log_test.go b/internal/telemetry/export/log_test.go
new file mode 100644
index 0000000..77fa02c
--- /dev/null
+++ b/internal/telemetry/export/log_test.go
@@ -0,0 +1,37 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package export_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "time"
+
+ "golang.org/x/tools/internal/telemetry/event"
+ "golang.org/x/tools/internal/telemetry/export"
+)
+
+func ExampleLog() {
+ ctx := context.Background()
+ event.SetExporter(timeFixer(export.LogWriter(os.Stdout, false)))
+ anInt := event.NewIntKey("myInt", "an integer")
+ aString := event.NewStringKey("myString", "a string")
+ event.Print(ctx, "my event", anInt.Of(6))
+ event.Error(ctx, "error event", errors.New("an error"), aString.Of("some string value"))
+ // Output:
+ // 2020/03/05 14:27:48 my event
+ // myInt=6
+ // 2020/03/05 14:27:48 error event: an error
+ // myString="some string value"
+}
+
+func timeFixer(output event.Exporter) event.Exporter {
+ at, _ := time.Parse(time.RFC3339Nano, "2020-03-05T14:27:48Z")
+ return func(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context {
+ ev.At = at
+ return output(ctx, ev, tagMap)
+ }
+}
diff --git a/internal/telemetry/export/ocagent/ocagent.go b/internal/telemetry/export/ocagent/ocagent.go
index 5af7861..6eec1b9 100644
--- a/internal/telemetry/export/ocagent/ocagent.go
+++ b/internal/telemetry/export/ocagent/ocagent.go
@@ -299,7 +299,7 @@
if !tags.Valid() {
return nil
}
- tagMap := ev.Map()
+ tagMap := event.TagMap(ev)
description := event.Msg.Get(tagMap)
tags = event.Filter(tags, event.Msg)
if description == "" {
diff --git a/internal/telemetry/export/tag.go b/internal/telemetry/export/tag.go
index 6acadf0..7d29529 100644
--- a/internal/telemetry/export/tag.go
+++ b/internal/telemetry/export/tag.go
@@ -20,7 +20,7 @@
stored, _ := ctx.Value(labelContextKey).(event.TagMap)
if ev.IsLabel() || ev.IsStartSpan() {
// update the tag source stored in the context
- fromEvent := ev.Map()
+ fromEvent := event.TagMap(ev)
if stored == nil {
stored = fromEvent
} else {