utils/vscode: Add go language server packages.

The utils/vscode/src/lsp directory contains code forked from https://github.com/golang/tools/tree/master/internal/lsp.

This code has been modified to remove additional, unneeded features and dependencies.

Submitted on behalf of a third-party: The Go Authors
This commit is contained in:
Ben Clayton 2019-11-11 12:03:39 +00:00 committed by David Neto
parent ab3cdcaef5
commit b96c9a057e
22 changed files with 8457 additions and 0 deletions

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,5 @@
This directory contains code forked from https://github.com/golang/tools/tree/master/internal/lsp.
This code has been modified to remove unneeded features and dependencies.
Submitted on behalf of a third-party: The Go Authors

View File

@ -0,0 +1,134 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jsonrpc2
import (
"context"
)
// 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
// Log is invoked for all messages flowing through a Conn.
// direction indicates if the message being received or sent
// id is the message id, if not set it was a notification
// elapsed is the time between a call being seen and the response, and is
// negative for anything that is not a response.
// method is the method name specified in the message
// payload is the parameters for a call or notification, and the result for a
// response
// Request is called near the start of processing any request.
Request(ctx context.Context, conn *Conn, direction Direction, r *WireRequest) context.Context
// Response is called near the start of processing any response.
Response(ctx context.Context, conn *Conn, direction Direction, r *WireResponse) context.Context
// Done is called when any request is fully processed.
// For calls, this means the response has also been processed, for notifies
// this is as soon as the message has been written to the stream.
// If err is set, it implies the request failed.
Done(ctx context.Context, err error)
// Read is called with a count each time some data is read from the stream.
// The read calls are delayed until after the data has been interpreted so
// that it can be attributed to a request/response.
Read(ctx context.Context, bytes int64) context.Context
// Wrote is called each time some data is written to the stream.
Wrote(ctx context.Context, bytes int64) context.Context
// Error is called with errors that cannot be delivered through the normal
// mechanisms, for instance a failure to process a notify cannot be delivered
// back to the other party.
Error(ctx context.Context, err error)
}
// Direction is used to indicate to a logger whether the logged message was being
// sent or received.
type Direction bool
const (
// Send indicates the message is outgoing.
Send = Direction(true)
// Receive indicates the message is incoming.
Receive = Direction(false)
)
func (d Direction) String() string {
switch d {
case Send:
return "send"
case Receive:
return "receive"
default:
panic("unreachable")
}
}
type EmptyHandler struct{}
func (EmptyHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
return false
}
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
}
func (EmptyHandler) Done(ctx context.Context, err error) {
}
func (EmptyHandler) Read(ctx context.Context, bytes int64) context.Context {
return ctx
}
func (EmptyHandler) Wrote(ctx context.Context, bytes int64) context.Context {
return ctx
}
func (EmptyHandler) Error(ctx context.Context, err error) {}
type defaultHandler struct{ EmptyHandler }
func (defaultHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
if delivered {
return false
}
if !r.IsNotify() {
r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
}
return true
}

View File

@ -0,0 +1,416 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
// https://www.jsonrpc.org/specification
// It is intended to be compatible with other implementations at the wire level.
package jsonrpc2
import (
"context"
"encoding/json"
"fmt"
"sync"
"sync/atomic"
)
// 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
err error
pendingMu sync.Mutex // protects the pending map
pending map[ID]chan *WireResponse
handlingMu sync.Mutex // protects the handling map
handling map[ID]*Request
}
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{}
// The Wire values of the request.
WireRequest
}
// 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 {
return &Error{
Code: code,
Message: fmt.Sprintf(format, args...),
}
}
// NewConn creates a new connection object around the supplied stream.
// 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),
}
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()
}
}
// Notify is called to send a notification request over the connection.
// It will return as soon as the notification has been sent, as no response is
// possible.
func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (err error) {
jsonParams, err := marshalToRaw(params)
if err != nil {
return fmt.Errorf("marshalling notify parameters: %v", err)
}
request := &WireRequest{
Method: method,
Params: jsonParams,
}
data, err := json.Marshal(request)
if err != nil {
return fmt.Errorf("marshalling notify request: %v", err)
}
for _, h := range c.handlers {
ctx = h.Request(ctx, c, Send, request)
}
defer func() {
for _, h := range c.handlers {
h.Done(ctx, err)
}
}()
n, err := c.stream.Write(ctx, data)
for _, h := range c.handlers {
ctx = h.Wrote(ctx, n)
}
return err
}
// Call sends a request over the connection and then waits for a response.
// If the response is not an error, it will be decoded into result.
// result must be of a type you an pass to json.Unmarshal.
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) (err error) {
// generate a new request identifier
id := ID{Number: atomic.AddInt64(&c.seq, 1)}
jsonParams, err := marshalToRaw(params)
if err != nil {
return fmt.Errorf("marshalling call parameters: %v", err)
}
request := &WireRequest{
ID: &id,
Method: method,
Params: jsonParams,
}
// marshal the request now it is complete
data, err := json.Marshal(request)
if err != nil {
return fmt.Errorf("marshalling call request: %v", err)
}
for _, h := range c.handlers {
ctx = h.Request(ctx, c, Send, request)
}
// we have to add ourselves to the pending map before we send, otherwise we
// are racing the response
rchan := make(chan *WireResponse)
c.pendingMu.Lock()
c.pending[id] = rchan
c.pendingMu.Unlock()
defer func() {
// clean up the pending response handler on the way out
c.pendingMu.Lock()
delete(c.pending, id)
c.pendingMu.Unlock()
for _, h := range c.handlers {
h.Done(ctx, err)
}
}()
// now we are ready to send
n, err := c.stream.Write(ctx, data)
for _, h := range c.handlers {
ctx = h.Wrote(ctx, n)
}
if err != nil {
// sending failed, we will never get a response, so don't leave it pending
return err
}
// now wait for the response
select {
case response := <-rchan:
for _, h := range c.handlers {
ctx = h.Response(ctx, c, Receive, response)
}
// is it an error response?
if response.Error != nil {
return response.Error
}
if result == nil || response.Result == nil {
return nil
}
if err := json.Unmarshal(*response.Result, result); err != nil {
return fmt.Errorf("unmarshalling result: %v", err)
}
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
}
}
return ctx.Err()
}
}
// Conn returns the connection that created this request.
func (r *Request) Conn() *Conn { return r.conn }
// IsNotify returns true if this request is a notification.
func (r *Request) IsNotify() bool {
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.
func (r *Request) Reply(ctx context.Context, result interface{}, err error) error {
if r.state >= requestReplied {
return fmt.Errorf("reply invoked more than once")
}
if r.IsNotify() {
return fmt.Errorf("reply not invoked with a valid call")
}
// 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 {
raw, err = marshalToRaw(result)
}
response := &WireResponse{
Result: raw,
ID: r.ID,
}
if err != nil {
if callErr, ok := err.(*Error); ok {
response.Error = callErr
} else {
response.Error = NewErrorf(0, "%s", err)
}
}
data, err := json.Marshal(response)
if err != nil {
return err
}
for _, h := range r.conn.handlers {
ctx = h.Response(ctx, r.conn, Send, response)
}
n, err := r.conn.stream.Write(ctx, data)
for _, h := range r.conn.handlers {
ctx = h.Wrote(ctx, n)
}
if err != nil {
// TODO(iancottrell): if a stream write fails, we really need to shut down
// the whole stream
return err
}
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)
}
}
// combined has all the fields of both Request and Response.
// We can decode this and then work out which it is.
type combined struct {
VersionTag VersionTag `json:"jsonrpc"`
ID *ID `json:"id,omitempty"`
Method string `json:"method"`
Params *json.RawMessage `json:"params,omitempty"`
Result *json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
// Run blocks until the connection is terminated, and returns any error that
// 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)
for {
// get the data for a message
data, n, err := c.stream.Read(runCtx)
if err != nil {
// the stream failed, we cannot continue
return err
}
// read a combined message
msg := &combined{}
if err := json.Unmarshal(data, msg); err != nil {
// a badly formed message arrived, log it and continue
// we trust the stream to have isolated the error to just this message
for _, h := range c.handlers {
h.Error(runCtx, fmt.Errorf("unmarshal failed: %v", err))
}
continue
}
// work out which kind of message we have
switch {
case msg.Method != "":
// if method is set it must be a request
reqCtx, cancelReq := context.WithCancel(runCtx)
thisRequest := nextRequest
nextRequest = make(chan struct{})
req := &Request{
conn: c,
cancel: cancelReq,
nextRequest: nextRequest,
WireRequest: WireRequest{
VersionTag: msg.VersionTag,
Method: msg.Method,
Params: msg.Params,
ID: msg.ID,
},
}
for _, h := range c.handlers {
reqCtx = h.Request(reqCtx, c, Receive, &req.WireRequest)
reqCtx = h.Read(reqCtx, n)
}
c.setHandling(req, true)
go func() {
<-thisRequest
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()
for _, h := range c.handlers {
h.Done(reqCtx, err)
}
cancelReq()
}()
delivered := false
for _, h := range c.handlers {
if h.Deliver(reqCtx, req, delivered) {
delivered = true
}
}
}()
case msg.ID != nil:
// we have a response, get the pending entry from the map
c.pendingMu.Lock()
rchan := c.pending[*msg.ID]
if rchan != nil {
delete(c.pending, *msg.ID)
}
c.pendingMu.Unlock()
// and send the reply to the channel
response := &WireResponse{
Result: msg.Result,
Error: msg.Error,
ID: msg.ID,
}
rchan <- response
close(rchan)
default:
for _, h := range c.handlers {
h.Error(runCtx, fmt.Errorf("message not a call, notify or response, ignoring"))
}
}
}
}
func marshalToRaw(obj interface{}) (*json.RawMessage, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
raw := json.RawMessage(data)
return &raw, nil
}

View File

@ -0,0 +1,160 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jsonrpc2
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"sync"
)
// Stream abstracts the transport mechanics from the JSON RPC protocol.
// A Conn reads and writes messages using the stream it was provided on
// construction, and assumes that each call to Read or Write fully transfers
// a single message, or returns an error.
type Stream interface {
// Read gets the next message from the stream.
// It is never called concurrently.
Read(context.Context) ([]byte, int64, error)
// Write sends a message to the stream.
// It must be safe for concurrent use.
Write(context.Context, []byte) (int64, error)
}
// 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.
func NewStream(in io.Reader, out io.Writer) Stream {
return &plainStream{
in: json.NewDecoder(in),
out: out,
}
}
type plainStream struct {
in *json.Decoder
outMu sync.Mutex
out io.Writer
}
func (s *plainStream) Read(ctx context.Context) ([]byte, int64, error) {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
default:
}
var raw json.RawMessage
if err := s.in.Decode(&raw); err != nil {
return nil, 0, err
}
return raw, int64(len(raw)), nil
}
func (s *plainStream) Write(ctx context.Context, data []byte) (int64, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
s.outMu.Lock()
n, err := s.out.Write(data)
s.outMu.Unlock()
return int64(n), err
}
// NewHeaderStream returns a Stream built on top of an io.Reader and io.Writer
// The messages are sent with HTTP content length and MIME type headers.
// This is the format used by LSP and others.
func NewHeaderStream(in io.Reader, out io.Writer) Stream {
return &headerStream{
in: bufio.NewReader(in),
out: out,
}
}
type headerStream struct {
in *bufio.Reader
outMu sync.Mutex
out io.Writer
}
func (s *headerStream) Read(ctx context.Context) ([]byte, int64, error) {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
default:
}
var total, length int64
// read the header, stop on the first empty line
for {
line, err := s.in.ReadString('\n')
total += int64(len(line))
if err != nil {
return nil, total, fmt.Errorf("failed reading header line %q", err)
}
line = strings.TrimSpace(line)
// check we have a header line
if line == "" {
break
}
colon := strings.IndexRune(line, ':')
if colon < 0 {
return nil, total, fmt.Errorf("invalid header line %q", line)
}
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
switch name {
case "Content-Length":
if length, err = strconv.ParseInt(value, 10, 32); err != nil {
return nil, total, fmt.Errorf("failed parsing Content-Length: %v", value)
}
if length <= 0 {
return nil, total, fmt.Errorf("invalid Content-Length: %v", length)
}
default:
// ignoring unknown headers
}
}
if length == 0 {
return nil, total, fmt.Errorf("missing Content-Length header")
}
data := make([]byte, length)
if _, err := io.ReadFull(s.in, data); err != nil {
return nil, total, err
}
total += length
return data, total, nil
}
func (s *headerStream) Write(ctx context.Context, data []byte) (int64, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
s.outMu.Lock()
defer s.outMu.Unlock()
n, err := fmt.Fprintf(s.out, "Content-Length: %v\r\n\r\n", len(data))
total := int64(n)
if err == nil {
n, err = s.out.Write(data)
total += int64(n)
}
return total, err
}

View File

@ -0,0 +1,148 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jsonrpc2
import (
"encoding/json"
"fmt"
"strconv"
)
// this file contains the go forms of the wire specification
// see http://www.jsonrpc.org/specification for details
const (
// CodeUnknownError should be used for all non coded errors.
CodeUnknownError = -32001
// CodeParseError is used when invalid JSON was received by the server.
CodeParseError = -32700
//CodeInvalidRequest is used when the JSON sent is not a valid Request object.
CodeInvalidRequest = -32600
// CodeMethodNotFound should be returned by the handler when the method does
// not exist / is not available.
CodeMethodNotFound = -32601
// CodeInvalidParams should be returned by the handler when method
// parameter(s) were invalid.
CodeInvalidParams = -32602
// CodeInternalError is not currently returned but defined for completeness.
CodeInternalError = -32603
//CodeServerOverloaded is returned when a message was refused due to a
//server being temporarily unable to accept any new messages.
CodeServerOverloaded = -32000
)
// WireRequest is sent to a server to represent a Call or Notify operaton.
type WireRequest struct {
// VersionTag is always encoded as the string "2.0"
VersionTag VersionTag `json:"jsonrpc"`
// Method is a string containing the method name to invoke.
Method string `json:"method"`
// Params is either a struct or an array with the parameters of the method.
Params *json.RawMessage `json:"params,omitempty"`
// The id of this request, used to tie the Response back to the request.
// Will be either a string or a number. If not set, the Request is a notify,
// and no response is possible.
ID *ID `json:"id,omitempty"`
}
// WireResponse is a reply to a Request.
// It will always have the ID field set to tie it back to a request, and will
// have either the Result or Error fields set depending on whether it is a
// success or failure response.
type WireResponse struct {
// VersionTag is always encoded as the string "2.0"
VersionTag VersionTag `json:"jsonrpc"`
// Result is the response value, and is required on success.
Result *json.RawMessage `json:"result,omitempty"`
// Error is a structured error response if the call fails.
Error *Error `json:"error,omitempty"`
// ID must be set and is the identifier of the Request this is a response to.
ID *ID `json:"id,omitempty"`
}
// Error represents a structured error in a Response.
type Error struct {
// Code is an error code indicating the type of failure.
Code int64 `json:"code"`
// Message is a short description of the error.
Message string `json:"message"`
// Data is optional structured data containing additional information about the error.
Data *json.RawMessage `json:"data"`
}
// VersionTag is a special 0 sized struct that encodes as the jsonrpc version
// tag.
// It will fail during decode if it is not the correct version tag in the
// stream.
type VersionTag struct{}
// ID is a Request identifier.
// Only one of either the Name or Number members will be set, using the
// number form if the Name is the empty string.
type ID struct {
Name string
Number int64
}
func (err *Error) Error() string {
if err == nil {
return ""
}
return err.Message
}
func (VersionTag) MarshalJSON() ([]byte, error) {
return json.Marshal("2.0")
}
func (VersionTag) UnmarshalJSON(data []byte) error {
version := ""
if err := json.Unmarshal(data, &version); err != nil {
return err
}
if version != "2.0" {
return fmt.Errorf("Invalid RPC version %v", version)
}
return nil
}
// String returns a string representation of the ID.
// The representation is non ambiguous, string forms are quoted, number forms
// are preceded by a #
func (id *ID) String() string {
if id == nil {
return ""
}
if id.Name != "" {
return strconv.Quote(id.Name)
}
return "#" + strconv.FormatInt(id.Number, 10)
}
func (id *ID) MarshalJSON() ([]byte, error) {
if id.Name != "" {
return json.Marshal(id.Name)
}
return json.Marshal(id.Number)
}
func (id *ID) UnmarshalJSON(data []byte) error {
*id = ID{}
if err := json.Unmarshal(data, &id.Number); err == nil {
return nil
}
return json.Unmarshal(data, &id.Name)
}

View File

@ -0,0 +1,29 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"context"
)
type contextKey int
const (
clientKey = contextKey(iota)
)
func WithClient(ctx context.Context, client Client) context.Context {
return context.WithValue(ctx, clientKey, client)
}

View File

@ -0,0 +1,26 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package protocol contains the structs that map directly to the wire format
// of the "Language Server Protocol".
//
// It is a literal transcription, with unmodified comments, and only the changes
// required to make it go code.
// Names are uppercased to export them.
// All fields have JSON tags added to correct the names.
// Fields marked with a ? are also marked as "omitempty"
// Fields that are "|| null" are made pointers
// Fields that are string or number are left as string
// Fields that are type "number" are made float64
package protocol

View File

@ -0,0 +1,256 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"fmt"
)
var (
namesTextDocumentSyncKind [int(Incremental) + 1]string
namesInitializeError [int(UnknownProtocolVersion) + 1]string
namesMessageType [int(Log) + 1]string
namesFileChangeType [int(Deleted) + 1]string
namesWatchKind [int(WatchDelete) + 1]string
namesCompletionTriggerKind [int(TriggerForIncompleteCompletions) + 1]string
namesDiagnosticSeverity [int(SeverityHint) + 1]string
namesDiagnosticTag [int(Unnecessary) + 1]string
namesCompletionItemKind [int(TypeParameterCompletion) + 1]string
namesInsertTextFormat [int(SnippetTextFormat) + 1]string
namesDocumentHighlightKind [int(Write) + 1]string
namesSymbolKind [int(TypeParameter) + 1]string
namesTextDocumentSaveReason [int(FocusOut) + 1]string
)
func init() {
namesTextDocumentSyncKind[int(None)] = "None"
namesTextDocumentSyncKind[int(Full)] = "Full"
namesTextDocumentSyncKind[int(Incremental)] = "Incremental"
namesInitializeError[int(UnknownProtocolVersion)] = "UnknownProtocolVersion"
namesMessageType[int(Error)] = "Error"
namesMessageType[int(Warning)] = "Warning"
namesMessageType[int(Info)] = "Info"
namesMessageType[int(Log)] = "Log"
namesFileChangeType[int(Created)] = "Created"
namesFileChangeType[int(Changed)] = "Changed"
namesFileChangeType[int(Deleted)] = "Deleted"
namesWatchKind[int(WatchCreate)] = "WatchCreate"
namesWatchKind[int(WatchChange)] = "WatchChange"
namesWatchKind[int(WatchDelete)] = "WatchDelete"
namesCompletionTriggerKind[int(Invoked)] = "Invoked"
namesCompletionTriggerKind[int(TriggerCharacter)] = "TriggerCharacter"
namesCompletionTriggerKind[int(TriggerForIncompleteCompletions)] = "TriggerForIncompleteCompletions"
namesDiagnosticSeverity[int(SeverityError)] = "Error"
namesDiagnosticSeverity[int(SeverityWarning)] = "Warning"
namesDiagnosticSeverity[int(SeverityInformation)] = "Information"
namesDiagnosticSeverity[int(SeverityHint)] = "Hint"
namesDiagnosticTag[int(Unnecessary)] = "Unnecessary"
namesCompletionItemKind[int(TextCompletion)] = "text"
namesCompletionItemKind[int(MethodCompletion)] = "method"
namesCompletionItemKind[int(FunctionCompletion)] = "func"
namesCompletionItemKind[int(ConstructorCompletion)] = "constructor"
namesCompletionItemKind[int(FieldCompletion)] = "field"
namesCompletionItemKind[int(VariableCompletion)] = "var"
namesCompletionItemKind[int(ClassCompletion)] = "type"
namesCompletionItemKind[int(InterfaceCompletion)] = "interface"
namesCompletionItemKind[int(ModuleCompletion)] = "package"
namesCompletionItemKind[int(PropertyCompletion)] = "property"
namesCompletionItemKind[int(UnitCompletion)] = "unit"
namesCompletionItemKind[int(ValueCompletion)] = "value"
namesCompletionItemKind[int(EnumCompletion)] = "enum"
namesCompletionItemKind[int(KeywordCompletion)] = "keyword"
namesCompletionItemKind[int(SnippetCompletion)] = "snippet"
namesCompletionItemKind[int(ColorCompletion)] = "color"
namesCompletionItemKind[int(FileCompletion)] = "file"
namesCompletionItemKind[int(ReferenceCompletion)] = "reference"
namesCompletionItemKind[int(FolderCompletion)] = "folder"
namesCompletionItemKind[int(EnumMemberCompletion)] = "enumMember"
namesCompletionItemKind[int(ConstantCompletion)] = "const"
namesCompletionItemKind[int(StructCompletion)] = "struct"
namesCompletionItemKind[int(EventCompletion)] = "event"
namesCompletionItemKind[int(OperatorCompletion)] = "operator"
namesCompletionItemKind[int(TypeParameterCompletion)] = "typeParam"
namesInsertTextFormat[int(PlainTextTextFormat)] = "PlainText"
namesInsertTextFormat[int(SnippetTextFormat)] = "Snippet"
namesDocumentHighlightKind[int(Text)] = "Text"
namesDocumentHighlightKind[int(Read)] = "Read"
namesDocumentHighlightKind[int(Write)] = "Write"
namesSymbolKind[int(File)] = "File"
namesSymbolKind[int(Module)] = "Module"
namesSymbolKind[int(Namespace)] = "Namespace"
namesSymbolKind[int(Package)] = "Package"
namesSymbolKind[int(Class)] = "Class"
namesSymbolKind[int(Method)] = "Method"
namesSymbolKind[int(Property)] = "Property"
namesSymbolKind[int(Field)] = "Field"
namesSymbolKind[int(Constructor)] = "Constructor"
namesSymbolKind[int(Enum)] = "Enum"
namesSymbolKind[int(Interface)] = "Interface"
namesSymbolKind[int(Function)] = "Function"
namesSymbolKind[int(Variable)] = "Variable"
namesSymbolKind[int(Constant)] = "Constant"
namesSymbolKind[int(String)] = "String"
namesSymbolKind[int(Number)] = "Number"
namesSymbolKind[int(Boolean)] = "Boolean"
namesSymbolKind[int(Array)] = "Array"
namesSymbolKind[int(Object)] = "Object"
namesSymbolKind[int(Key)] = "Key"
namesSymbolKind[int(Null)] = "Null"
namesSymbolKind[int(EnumMember)] = "EnumMember"
namesSymbolKind[int(Struct)] = "Struct"
namesSymbolKind[int(Event)] = "Event"
namesSymbolKind[int(Operator)] = "Operator"
namesSymbolKind[int(TypeParameter)] = "TypeParameter"
namesTextDocumentSaveReason[int(Manual)] = "Manual"
namesTextDocumentSaveReason[int(AfterDelay)] = "AfterDelay"
namesTextDocumentSaveReason[int(FocusOut)] = "FocusOut"
}
func formatEnum(f fmt.State, c rune, i int, names []string, unknown string) {
s := ""
if i >= 0 && i < len(names) {
s = names[i]
}
if s != "" {
fmt.Fprint(f, s)
} else {
fmt.Fprintf(f, "%s(%d)", unknown, i)
}
}
func parseEnum(s string, names []string) int {
for i, name := range names {
if s == name {
return i
}
}
return 0
}
func (e TextDocumentSyncKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesTextDocumentSyncKind[:], "TextDocumentSyncKind")
}
func ParseTextDocumentSyncKind(s string) TextDocumentSyncKind {
return TextDocumentSyncKind(parseEnum(s, namesTextDocumentSyncKind[:]))
}
func (e InitializeError) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesInitializeError[:], "InitializeError")
}
func ParseInitializeError(s string) InitializeError {
return InitializeError(parseEnum(s, namesInitializeError[:]))
}
func (e MessageType) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesMessageType[:], "MessageType")
}
func ParseMessageType(s string) MessageType {
return MessageType(parseEnum(s, namesMessageType[:]))
}
func (e FileChangeType) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesFileChangeType[:], "FileChangeType")
}
func ParseFileChangeType(s string) FileChangeType {
return FileChangeType(parseEnum(s, namesFileChangeType[:]))
}
func (e WatchKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesWatchKind[:], "WatchKind")
}
func ParseWatchKind(s string) WatchKind {
return WatchKind(parseEnum(s, namesWatchKind[:]))
}
func (e CompletionTriggerKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesCompletionTriggerKind[:], "CompletionTriggerKind")
}
func ParseCompletionTriggerKind(s string) CompletionTriggerKind {
return CompletionTriggerKind(parseEnum(s, namesCompletionTriggerKind[:]))
}
func (e DiagnosticSeverity) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesDiagnosticSeverity[:], "DiagnosticSeverity")
}
func ParseDiagnosticSeverity(s string) DiagnosticSeverity {
return DiagnosticSeverity(parseEnum(s, namesDiagnosticSeverity[:]))
}
func (e DiagnosticTag) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesDiagnosticTag[:], "DiagnosticTag")
}
func ParseDiagnosticTag(s string) DiagnosticTag {
return DiagnosticTag(parseEnum(s, namesDiagnosticTag[:]))
}
func (e CompletionItemKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesCompletionItemKind[:], "CompletionItemKind")
}
func ParseCompletionItemKind(s string) CompletionItemKind {
return CompletionItemKind(parseEnum(s, namesCompletionItemKind[:]))
}
func (e InsertTextFormat) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesInsertTextFormat[:], "InsertTextFormat")
}
func ParseInsertTextFormat(s string) InsertTextFormat {
return InsertTextFormat(parseEnum(s, namesInsertTextFormat[:]))
}
func (e DocumentHighlightKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesDocumentHighlightKind[:], "DocumentHighlightKind")
}
func ParseDocumentHighlightKind(s string) DocumentHighlightKind {
return DocumentHighlightKind(parseEnum(s, namesDocumentHighlightKind[:]))
}
func (e SymbolKind) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesSymbolKind[:], "SymbolKind")
}
func ParseSymbolKind(s string) SymbolKind {
return SymbolKind(parseEnum(s, namesSymbolKind[:]))
}
func (e TextDocumentSaveReason) Format(f fmt.State, c rune) {
formatEnum(f, c, int(e), namesTextDocumentSaveReason[:], "TextDocumentSaveReason")
}
func ParseTextDocumentSaveReason(s string) TextDocumentSaveReason {
return TextDocumentSaveReason(parseEnum(s, namesTextDocumentSaveReason[:]))
}

View File

@ -0,0 +1,258 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync"
"time"
"../jsonrpc2"
)
type loggingStream struct {
stream jsonrpc2.Stream
log io.Writer
}
// LoggingStream returns a stream that does LSP protocol logging too
func LoggingStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream {
return &loggingStream{str, w}
}
func (s *loggingStream) Read(ctx context.Context) ([]byte, int64, error) {
data, count, err := s.stream.Read(ctx)
if err == nil {
logIn(s.log, data)
}
return data, count, err
}
func (s *loggingStream) Write(ctx context.Context, data []byte) (int64, error) {
logOut(s.log, data)
count, err := s.stream.Write(ctx, data)
return count, err
}
// Combined has all the fields of both Request and Response.
// We can decode this and then work out which it is.
type Combined struct {
VersionTag jsonrpc2.VersionTag `json:"jsonrpc"`
ID *jsonrpc2.ID `json:"id,omitempty"`
Method string `json:"method"`
Params *json.RawMessage `json:"params,omitempty"`
Result *json.RawMessage `json:"result,omitempty"`
Error *jsonrpc2.Error `json:"error,omitempty"`
}
type req struct {
method string
start time.Time
}
type mapped struct {
mu sync.Mutex
clientCalls map[string]req
serverCalls map[string]req
}
var maps = &mapped{
sync.Mutex{},
make(map[string]req),
make(map[string]req),
}
// these 4 methods are each used exactly once, but it seemed
// better to have the encapsulation rather than ad hoc mutex
// code in 4 places
func (m *mapped) client(id string, del bool) req {
m.mu.Lock()
defer m.mu.Unlock()
v := m.clientCalls[id]
if del {
delete(m.clientCalls, id)
}
return v
}
func (m *mapped) server(id string, del bool) req {
m.mu.Lock()
defer m.mu.Unlock()
v := m.serverCalls[id]
if del {
delete(m.serverCalls, id)
}
return v
}
func (m *mapped) setClient(id string, r req) {
m.mu.Lock()
defer m.mu.Unlock()
m.clientCalls[id] = r
}
func (m *mapped) setServer(id string, r req) {
m.mu.Lock()
defer m.mu.Unlock()
m.serverCalls[id] = r
}
const eor = "\r\n\r\n\r\n"
func strID(x *jsonrpc2.ID) string {
if x == nil {
// should never happen, but we need a number
return "999999999"
}
if x.Name != "" {
return x.Name
}
return fmt.Sprintf("%d", x.Number)
}
func logCommon(outfd io.Writer, data []byte) (*Combined, time.Time, string) {
if outfd == nil {
return nil, time.Time{}, ""
}
var v Combined
err := json.Unmarshal(data, &v)
if err != nil {
fmt.Fprintf(outfd, "Unmarshal %v\n", err)
panic(err) // do better
}
tm := time.Now()
tmfmt := tm.Format("15:04:05.000 PM")
return &v, tm, tmfmt
}
// logOut and logIn could be combined. "received"<->"Sending", serverCalls<->clientCalls
// but it wouldn't be a lot shorter or clearer and "shutdown" is a special case
// Writing a message to the client, log it
func logOut(outfd io.Writer, data []byte) {
v, tm, tmfmt := logCommon(outfd, data)
if v == nil {
return
}
if v.Error != nil {
id := strID(v.ID)
fmt.Fprintf(outfd, "[Error - %s] Received #%s %s%s", tmfmt, id, v.Error, eor)
return
}
buf := strings.Builder{}
id := strID(v.ID)
fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning
if v.ID != nil && v.Method != "" && v.Params != nil {
fmt.Fprintf(&buf, "Received request '%s - (%s)'.\n", v.Method, id)
fmt.Fprintf(&buf, "Params: %s%s", *v.Params, eor)
maps.setServer(id, req{method: v.Method, start: tm})
} else if v.ID != nil && v.Method == "" && v.Params == nil {
cc := maps.client(id, true)
elapsed := tm.Sub(cc.start)
fmt.Fprintf(&buf, "Received response '%s - (%s)' in %dms.\n",
cc.method, id, elapsed/time.Millisecond)
if v.Result == nil {
fmt.Fprintf(&buf, "Result: {}%s", eor)
} else {
fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor)
}
} else if v.ID == nil && v.Method != "" && v.Params != nil {
p := "null"
if v.Params != nil {
p = string(*v.Params)
}
fmt.Fprintf(&buf, "Received notification '%s'.\n", v.Method)
fmt.Fprintf(&buf, "Params: %s%s", p, eor)
} else { // for completeness, as it should never happen
buf = strings.Builder{} // undo common Trace
fmt.Fprintf(&buf, "[Error - %s] on write ID?%v method:%q Params:%v Result:%v Error:%v%s",
tmfmt, v.ID != nil, v.Method, v.Params != nil,
v.Result != nil, v.Error != nil, eor)
p := "null"
if v.Params != nil {
p = string(*v.Params)
}
r := "null"
if v.Result != nil {
r = string(*v.Result)
}
fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor)
}
outfd.Write([]byte(buf.String()))
}
// Got a message from the client, log it
func logIn(outfd io.Writer, data []byte) {
v, tm, tmfmt := logCommon(outfd, data)
if v == nil {
return
}
// ID Method Params => Sending request
// ID !Method Result(might be null, but !Params) => Sending response (could we get an Error?)
// !ID Method Params => Sending notification
if v.Error != nil { // does this ever happen?
id := strID(v.ID)
fmt.Fprintf(outfd, "[Error - %s] Sent #%s %s%s", tmfmt, id, v.Error, eor)
return
}
buf := strings.Builder{}
id := strID(v.ID)
fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning
if v.ID != nil && v.Method != "" && (v.Params != nil || v.Method == "shutdown") {
fmt.Fprintf(&buf, "Sending request '%s - (%s)'.\n", v.Method, id)
x := "{}"
if v.Params != nil {
x = string(*v.Params)
}
fmt.Fprintf(&buf, "Params: %s%s", x, eor)
maps.setClient(id, req{method: v.Method, start: tm})
} else if v.ID != nil && v.Method == "" && v.Params == nil {
sc := maps.server(id, true)
elapsed := tm.Sub(sc.start)
fmt.Fprintf(&buf, "Sending response '%s - (%s)' took %dms.\n",
sc.method, id, elapsed/time.Millisecond)
if v.Result == nil {
fmt.Fprintf(&buf, "Result: {}%s", eor)
} else {
fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor)
}
} else if v.ID == nil && v.Method != "" {
p := "null"
if v.Params != nil {
p = string(*v.Params)
}
fmt.Fprintf(&buf, "Sending notification '%s'.\n", v.Method)
fmt.Fprintf(&buf, "Params: %s%s", p, eor)
} else { // for completeness, as it should never happen
buf = strings.Builder{} // undo common Trace
fmt.Fprintf(&buf, "[Error - %s] on read ID?%v method:%q Params:%v Result:%v Error:%v%s",
tmfmt, v.ID != nil, v.Method, v.Params != nil,
v.Result != nil, v.Error != nil, eor)
p := "null"
if v.Params != nil {
p = string(*v.Params)
}
r := "null"
if v.Result != nil {
r = string(*v.Result)
}
fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor)
}
outfd.Write([]byte(buf.String()))
}

View File

@ -0,0 +1,86 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"context"
"encoding/json"
"log"
"../jsonrpc2"
)
const (
// RequestCancelledError should be used when a request is cancelled early.
RequestCancelledError = -32800
)
type DocumentUri = string
type canceller struct{ jsonrpc2.EmptyHandler }
type clientHandler struct {
canceller
client Client
}
type serverHandler struct {
canceller
server Server
}
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, &params); err != nil {
log.Printf("%v", err)
} else {
conn.Cancel(params.ID)
}
}
return ctx
}
func (canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
if cancelled {
return false
}
conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: id})
return true
}
func NewClient(ctx context.Context, stream jsonrpc2.Stream, client Client) (context.Context, *jsonrpc2.Conn, Server) {
ctx = WithClient(ctx, client)
conn := jsonrpc2.NewConn(stream)
conn.AddHandler(&clientHandler{client: client})
return ctx, conn, &serverDispatcher{Conn: conn}
}
func NewServer(ctx context.Context, stream jsonrpc2.Stream, server Server) (context.Context, *jsonrpc2.Conn, Client) {
conn := jsonrpc2.NewConn(stream)
client := &clientDispatcher{Conn: conn}
ctx = WithClient(ctx, client)
conn.AddHandler(&serverHandler{server: server})
return ctx, conn, client
}
func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) {
if _, ok := err.(*jsonrpc2.Error); !ok {
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
}
if err := req.Reply(ctx, nil, err); err != nil {
log.Printf("%v", err)
}
}

View File

@ -0,0 +1,137 @@
// Copyright 2018 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// this file contains protocol<->span converters
package protocol
import (
"fmt"
"../span"
errors "golang.org/x/xerrors"
)
type ColumnMapper struct {
URI span.URI
Converter *span.TokenConverter
Content []byte
}
func NewURI(uri span.URI) string {
return string(uri)
}
func (m *ColumnMapper) Location(s span.Span) (Location, error) {
rng, err := m.Range(s)
if err != nil {
return Location{}, err
}
return Location{URI: NewURI(s.URI()), Range: rng}, nil
}
func (m *ColumnMapper) Range(s span.Span) (Range, error) {
if span.CompareURI(m.URI, s.URI()) != 0 {
return Range{}, errors.Errorf("column mapper is for file %q instead of %q", m.URI, s.URI())
}
s, err := s.WithAll(m.Converter)
if err != nil {
return Range{}, err
}
start, err := m.Position(s.Start())
if err != nil {
return Range{}, err
}
end, err := m.Position(s.End())
if err != nil {
return Range{}, err
}
return Range{Start: start, End: end}, nil
}
func (m *ColumnMapper) Position(p span.Point) (Position, error) {
chr, err := span.ToUTF16Column(p, m.Content)
if err != nil {
return Position{}, err
}
return Position{
Line: float64(p.Line() - 1),
Character: float64(chr - 1),
}, nil
}
func (m *ColumnMapper) Span(l Location) (span.Span, error) {
return m.RangeSpan(l.Range)
}
func (m *ColumnMapper) RangeSpan(r Range) (span.Span, error) {
start, err := m.Point(r.Start)
if err != nil {
return span.Span{}, err
}
end, err := m.Point(r.End)
if err != nil {
return span.Span{}, err
}
return span.New(m.URI, start, end).WithAll(m.Converter)
}
func (m *ColumnMapper) PointSpan(p Position) (span.Span, error) {
start, err := m.Point(p)
if err != nil {
return span.Span{}, err
}
return span.New(m.URI, start, start).WithAll(m.Converter)
}
func (m *ColumnMapper) Point(p Position) (span.Point, error) {
line := int(p.Line) + 1
offset, err := m.Converter.ToOffset(line, 1)
if err != nil {
return span.Point{}, err
}
lineStart := span.NewPoint(line, 1, offset)
return span.FromUTF16Column(lineStart, int(p.Character)+1, m.Content)
}
func IsPoint(r Range) bool {
return r.Start.Line == r.End.Line && r.Start.Character == r.End.Character
}
func CompareRange(a, b Range) int {
if r := ComparePosition(a.Start, b.Start); r != 0 {
return r
}
return ComparePosition(a.End, b.End)
}
func ComparePosition(a, b Position) int {
if a.Line < b.Line {
return -1
}
if a.Line > b.Line {
return 1
}
if a.Character < b.Character {
return -1
}
if a.Character > b.Character {
return 1
}
return 0
}
func (r Range) Format(f fmt.State, _ rune) {
fmt.Fprintf(f, "%v:%v-%v:%v", r.Start.Line, r.Start.Character, r.End.Line, r.End.Character)
}

View File

@ -0,0 +1,221 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"context"
"encoding/json"
"log"
"../jsonrpc2"
)
type Client interface {
ShowMessage(context.Context, *ShowMessageParams) error
LogMessage(context.Context, *LogMessageParams) error
Event(context.Context, *interface{}) error
PublishDiagnostics(context.Context, *PublishDiagnosticsParams) error
WorkspaceFolders(context.Context) ([]WorkspaceFolder, error)
Configuration(context.Context, *ParamConfig) ([]interface{}, error)
RegisterCapability(context.Context, *RegistrationParams) error
UnregisterCapability(context.Context, *UnregistrationParams) error
ShowMessageRequest(context.Context, *ShowMessageRequestParams) (*MessageActionItem, error)
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 {
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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.client.ShowMessage(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "window/logMessage": // notif
var params LogMessageParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.client.LogMessage(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "telemetry/event": // notif
var params interface{}
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.client.Event(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/publishDiagnostics": // notif
var params PublishDiagnosticsParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.client.PublishDiagnostics(ctx, &params); err != nil {
log.Printf("%v", 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 {
log.Printf("%v", err)
}
return true
case "workspace/configuration": // req
var params ParamConfig
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.client.Configuration(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "client/registerCapability": // req
var params RegistrationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
err := h.client.RegisterCapability(ctx, &params)
if err := r.Reply(ctx, nil, err); err != nil {
log.Printf("%v", err)
}
return true
case "client/unregisterCapability": // req
var params UnregistrationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
err := h.client.UnregisterCapability(ctx, &params)
if err := r.Reply(ctx, nil, err); err != nil {
log.Printf("%v", err)
}
return true
case "window/showMessageRequest": // req
var params ShowMessageRequestParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.client.ShowMessageRequest(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "workspace/applyEdit": // req
var params ApplyWorkspaceEditParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.client.ApplyEdit(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
default:
return false
}
}
type clientDispatcher struct {
*jsonrpc2.Conn
}
func (s *clientDispatcher) ShowMessage(ctx context.Context, params *ShowMessageParams) error {
return s.Conn.Notify(ctx, "window/showMessage", params)
}
func (s *clientDispatcher) LogMessage(ctx context.Context, params *LogMessageParams) error {
return s.Conn.Notify(ctx, "window/logMessage", params)
}
func (s *clientDispatcher) Event(ctx context.Context, params *interface{}) error {
return s.Conn.Notify(ctx, "telemetry/event", params)
}
func (s *clientDispatcher) PublishDiagnostics(ctx context.Context, params *PublishDiagnosticsParams) error {
return s.Conn.Notify(ctx, "textDocument/publishDiagnostics", params)
}
func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder, error) {
var result []WorkspaceFolder
if err := s.Conn.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *clientDispatcher) Configuration(ctx context.Context, params *ParamConfig) ([]interface{}, error) {
var result []interface{}
if err := s.Conn.Call(ctx, "workspace/configuration", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *clientDispatcher) RegisterCapability(ctx context.Context, params *RegistrationParams) error {
return s.Conn.Call(ctx, "client/registerCapability", params, nil) // Call, not Notify
}
func (s *clientDispatcher) UnregisterCapability(ctx context.Context, params *UnregistrationParams) error {
return s.Conn.Call(ctx, "client/unregisterCapability", params, nil) // Call, not Notify
}
func (s *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowMessageRequestParams) (*MessageActionItem, error) {
var result MessageActionItem
if err := s.Conn.Call(ctx, "window/showMessageRequest", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *clientDispatcher) ApplyEdit(ctx context.Context, params *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResponse, error) {
var result ApplyWorkspaceEditResponse
if err := s.Conn.Call(ctx, "workspace/applyEdit", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// Types constructed to avoid structs as formal argument types
type ParamConfig struct {
ConfigurationParams
PartialResultParams
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,842 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocol
import (
"context"
"encoding/json"
"log"
"../jsonrpc2"
)
type Server interface {
DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error
Initialized(context.Context, *InitializedParams) error
Exit(context.Context) error
DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error
DidOpen(context.Context, *DidOpenTextDocumentParams) error
DidChange(context.Context, *DidChangeTextDocumentParams) error
DidClose(context.Context, *DidCloseTextDocumentParams) error
DidSave(context.Context, *DidSaveTextDocumentParams) error
WillSave(context.Context, *WillSaveTextDocumentParams) error
DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error
Progress(context.Context, *ProgressParams) error
SetTraceNotification(context.Context, *SetTraceParams) error
LogTraceNotification(context.Context, *LogTraceParams) error
Implementation(context.Context, *ImplementationParams) ([]Location, error)
TypeDefinition(context.Context, *TypeDefinitionParams) ([]Location, error)
DocumentColor(context.Context, *DocumentColorParams) ([]ColorInformation, error)
ColorPresentation(context.Context, *ColorPresentationParams) ([]ColorPresentation, error)
FoldingRange(context.Context, *FoldingRangeParams) ([]FoldingRange, error)
Declaration(context.Context, *DeclarationParams) ([]DeclarationLink, error)
SelectionRange(context.Context, *SelectionRangeParams) ([]SelectionRange, error)
Initialize(context.Context, *ParamInitia) (*InitializeResult, error)
Shutdown(context.Context) error
WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit, error)
Completion(context.Context, *CompletionParams) (*CompletionList, error)
Resolve(context.Context, *CompletionItem) (*CompletionItem, error)
Hover(context.Context, *HoverParams) (*Hover, error)
SignatureHelp(context.Context, *SignatureHelpParams) (*SignatureHelp, error)
Definition(context.Context, *DefinitionParams) ([]Location, error)
References(context.Context, *ReferenceParams) ([]Location, error)
DocumentHighlight(context.Context, *DocumentHighlightParams) ([]DocumentHighlight, error)
DocumentSymbol(context.Context, *DocumentSymbolParams) ([]DocumentSymbol, error)
CodeAction(context.Context, *CodeActionParams) ([]CodeAction, error)
Symbol(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation, error)
CodeLens(context.Context, *CodeLensParams) ([]CodeLens, error)
ResolveCodeLens(context.Context, *CodeLens) (*CodeLens, error)
DocumentLink(context.Context, *DocumentLinkParams) ([]DocumentLink, error)
ResolveDocumentLink(context.Context, *DocumentLink) (*DocumentLink, error)
Formatting(context.Context, *DocumentFormattingParams) ([]TextEdit, error)
RangeFormatting(context.Context, *DocumentRangeFormattingParams) ([]TextEdit, error)
OnTypeFormatting(context.Context, *DocumentOnTypeFormattingParams) ([]TextEdit, error)
Rename(context.Context, *RenameParams) (*WorkspaceEdit, error)
PrepareRename(context.Context, *PrepareRenameParams) (*Range, error)
ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{}, error)
}
func (h serverHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
if delivered {
return false
}
if ctx.Err() != nil {
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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidChangeWorkspaceFolders(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "initialized": // notif
var params InitializedParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.Initialized(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "exit": // notif
if err := h.server.Exit(ctx); err != nil {
log.Printf("%v", err)
}
return true
case "workspace/didChangeConfiguration": // notif
var params DidChangeConfigurationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidChangeConfiguration(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/didOpen": // notif
var params DidOpenTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidOpen(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/didChange": // notif
var params DidChangeTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidChange(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/didClose": // notif
var params DidCloseTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidClose(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/didSave": // notif
var params DidSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidSave(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/willSave": // notif
var params WillSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.WillSave(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "workspace/didChangeWatchedFiles": // notif
var params DidChangeWatchedFilesParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.DidChangeWatchedFiles(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "$/progress": // notif
var params ProgressParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.Progress(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "$/setTraceNotification": // notif
var params SetTraceParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.SetTraceNotification(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "$/logTraceNotification": // notif
var params LogTraceParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.LogTraceNotification(ctx, &params); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/implementation": // req
var params ImplementationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Implementation(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/typeDefinition": // req
var params TypeDefinitionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.TypeDefinition(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/documentColor": // req
var params DocumentColorParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.DocumentColor(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/colorPresentation": // req
var params ColorPresentationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.ColorPresentation(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/foldingRange": // req
var params FoldingRangeParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.FoldingRange(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/declaration": // req
var params DeclarationParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Declaration(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/selectionRange": // req
var params SelectionRangeParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.SelectionRange(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "initialize": // req
var params ParamInitia
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Initialize(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", 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 {
log.Printf("%v", err)
}
return true
case "textDocument/willSaveWaitUntil": // req
var params WillSaveTextDocumentParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.WillSaveWaitUntil(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/completion": // req
var params CompletionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Completion(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "completionItem/resolve": // req
var params CompletionItem
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Resolve(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/hover": // req
var params HoverParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Hover(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/signatureHelp": // req
var params SignatureHelpParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.SignatureHelp(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/definition": // req
var params DefinitionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Definition(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/references": // req
var params ReferenceParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.References(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/documentHighlight": // req
var params DocumentHighlightParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.DocumentHighlight(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/documentSymbol": // req
var params DocumentSymbolParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.DocumentSymbol(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/codeAction": // req
var params CodeActionParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.CodeAction(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "workspace/symbol": // req
var params WorkspaceSymbolParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Symbol(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/codeLens": // req
var params CodeLensParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.CodeLens(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "codeLens/resolve": // req
var params CodeLens
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.ResolveCodeLens(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/documentLink": // req
var params DocumentLinkParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.DocumentLink(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "documentLink/resolve": // req
var params DocumentLink
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.ResolveDocumentLink(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/formatting": // req
var params DocumentFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Formatting(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/rangeFormatting": // req
var params DocumentRangeFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.RangeFormatting(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/onTypeFormatting": // req
var params DocumentOnTypeFormattingParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.OnTypeFormatting(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/rename": // req
var params RenameParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.Rename(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "textDocument/prepareRename": // req
var params PrepareRenameParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.PrepareRename(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
case "workspace/executeCommand": // req
var params ExecuteCommandParams
if err := json.Unmarshal(*r.Params, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
resp, err := h.server.ExecuteCommand(ctx, &params)
if err := r.Reply(ctx, resp, err); err != nil {
log.Printf("%v", err)
}
return true
default:
return false
}
}
type serverDispatcher struct {
*jsonrpc2.Conn
}
func (s *serverDispatcher) DidChangeWorkspaceFolders(ctx context.Context, params *DidChangeWorkspaceFoldersParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeWorkspaceFolders", params)
}
func (s *serverDispatcher) Initialized(ctx context.Context, params *InitializedParams) error {
return s.Conn.Notify(ctx, "initialized", params)
}
func (s *serverDispatcher) Exit(ctx context.Context) error {
return s.Conn.Notify(ctx, "exit", nil)
}
func (s *serverDispatcher) DidChangeConfiguration(ctx context.Context, params *DidChangeConfigurationParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeConfiguration", params)
}
func (s *serverDispatcher) DidOpen(ctx context.Context, params *DidOpenTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didOpen", params)
}
func (s *serverDispatcher) DidChange(ctx context.Context, params *DidChangeTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didChange", params)
}
func (s *serverDispatcher) DidClose(ctx context.Context, params *DidCloseTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didClose", params)
}
func (s *serverDispatcher) DidSave(ctx context.Context, params *DidSaveTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/didSave", params)
}
func (s *serverDispatcher) WillSave(ctx context.Context, params *WillSaveTextDocumentParams) error {
return s.Conn.Notify(ctx, "textDocument/willSave", params)
}
func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *DidChangeWatchedFilesParams) error {
return s.Conn.Notify(ctx, "workspace/didChangeWatchedFiles", params)
}
func (s *serverDispatcher) Progress(ctx context.Context, params *ProgressParams) error {
return s.Conn.Notify(ctx, "$/progress", params)
}
func (s *serverDispatcher) SetTraceNotification(ctx context.Context, params *SetTraceParams) error {
return s.Conn.Notify(ctx, "$/setTraceNotification", params)
}
func (s *serverDispatcher) LogTraceNotification(ctx context.Context, params *LogTraceParams) error {
return s.Conn.Notify(ctx, "$/logTraceNotification", params)
}
func (s *serverDispatcher) Implementation(ctx context.Context, params *ImplementationParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/implementation", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TypeDefinitionParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/typeDefinition", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentColor(ctx context.Context, params *DocumentColorParams) ([]ColorInformation, error) {
var result []ColorInformation
if err := s.Conn.Call(ctx, "textDocument/documentColor", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) ColorPresentation(ctx context.Context, params *ColorPresentationParams) ([]ColorPresentation, error) {
var result []ColorPresentation
if err := s.Conn.Call(ctx, "textDocument/colorPresentation", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) FoldingRange(ctx context.Context, params *FoldingRangeParams) ([]FoldingRange, error) {
var result []FoldingRange
if err := s.Conn.Call(ctx, "textDocument/foldingRange", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Declaration(ctx context.Context, params *DeclarationParams) ([]DeclarationLink, error) {
var result []DeclarationLink
if err := s.Conn.Call(ctx, "textDocument/declaration", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) SelectionRange(ctx context.Context, params *SelectionRangeParams) ([]SelectionRange, error) {
var result []SelectionRange
if err := s.Conn.Call(ctx, "textDocument/selectionRange", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Initialize(ctx context.Context, params *ParamInitia) (*InitializeResult, error) {
var result InitializeResult
if err := s.Conn.Call(ctx, "initialize", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Shutdown(ctx context.Context) error {
return s.Conn.Call(ctx, "shutdown", nil, nil)
}
func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSaveTextDocumentParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/willSaveWaitUntil", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionParams) (*CompletionList, error) {
var result CompletionList
if err := s.Conn.Call(ctx, "textDocument/completion", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Resolve(ctx context.Context, params *CompletionItem) (*CompletionItem, error) {
var result CompletionItem
if err := s.Conn.Call(ctx, "completionItem/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Hover(ctx context.Context, params *HoverParams) (*Hover, error) {
var result Hover
if err := s.Conn.Call(ctx, "textDocument/hover", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *SignatureHelpParams) (*SignatureHelp, error) {
var result SignatureHelp
if err := s.Conn.Call(ctx, "textDocument/signatureHelp", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Definition(ctx context.Context, params *DefinitionParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/definition", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) References(ctx context.Context, params *ReferenceParams) ([]Location, error) {
var result []Location
if err := s.Conn.Call(ctx, "textDocument/references", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *DocumentHighlightParams) ([]DocumentHighlight, error) {
var result []DocumentHighlight
if err := s.Conn.Call(ctx, "textDocument/documentHighlight", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentSymbolParams) ([]DocumentSymbol, error) {
var result []DocumentSymbol
if err := s.Conn.Call(ctx, "textDocument/documentSymbol", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionParams) ([]CodeAction, error) {
var result []CodeAction
if err := s.Conn.Call(ctx, "textDocument/codeAction", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation, error) {
var result []SymbolInformation
if err := s.Conn.Call(ctx, "workspace/symbol", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) ([]CodeLens, error) {
var result []CodeLens
if err := s.Conn.Call(ctx, "textDocument/codeLens", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) ResolveCodeLens(ctx context.Context, params *CodeLens) (*CodeLens, error) {
var result CodeLens
if err := s.Conn.Call(ctx, "codeLens/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLinkParams) ([]DocumentLink, error) {
var result []DocumentLink
if err := s.Conn.Call(ctx, "textDocument/documentLink", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) ResolveDocumentLink(ctx context.Context, params *DocumentLink) (*DocumentLink, error) {
var result DocumentLink
if err := s.Conn.Call(ctx, "documentLink/resolve", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) Formatting(ctx context.Context, params *DocumentFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/formatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *DocumentRangeFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/rangeFormatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *DocumentOnTypeFormattingParams) ([]TextEdit, error) {
var result []TextEdit
if err := s.Conn.Call(ctx, "textDocument/onTypeFormatting", params, &result); err != nil {
return nil, err
}
return result, nil
}
func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) (*WorkspaceEdit, error) {
var result WorkspaceEdit
if err := s.Conn.Call(ctx, "textDocument/rename", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (*Range, error) {
var result Range
if err := s.Conn.Call(ctx, "textDocument/prepareRename", params, &result); err != nil {
return nil, err
}
return &result, nil
}
func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{}, error) {
var result interface{}
if err := s.Conn.Call(ctx, "workspace/executeCommand", params, &result); err != nil {
return nil, err
}
return result, nil
}
type CancelParams struct {
/**
* The request id to cancel.
*/
ID jsonrpc2.ID `json:"id"`
}
// Types constructed to avoid structs as formal argument types
type ParamInitia struct {
InitializeParams
WorkDoneProgressParams
}

View File

@ -0,0 +1,110 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package span
import (
"strconv"
"strings"
"unicode/utf8"
)
// Parse returns the location represented by the input.
// All inputs are valid locations, as they can always be a pure filename.
// The returned span will be normalized, and thus if printed may produce a
// different string.
func Parse(input string) Span {
// :0:0#0-0:0#0
valid := input
var hold, offset int
hadCol := false
suf := rstripSuffix(input)
if suf.sep == "#" {
offset = suf.num
suf = rstripSuffix(suf.remains)
}
if suf.sep == ":" {
valid = suf.remains
hold = suf.num
hadCol = true
suf = rstripSuffix(suf.remains)
}
switch {
case suf.sep == ":":
return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{})
case suf.sep == "-":
// we have a span, fall out of the case to continue
default:
// separator not valid, rewind to either the : or the start
return New(NewURI(valid), NewPoint(hold, 0, offset), Point{})
}
// only the span form can get here
// at this point we still don't know what the numbers we have mean
// if have not yet seen a : then we might have either a line or a column depending
// on whether start has a column or not
// we build an end point and will fix it later if needed
end := NewPoint(suf.num, hold, offset)
hold, offset = 0, 0
suf = rstripSuffix(suf.remains)
if suf.sep == "#" {
offset = suf.num
suf = rstripSuffix(suf.remains)
}
if suf.sep != ":" {
// turns out we don't have a span after all, rewind
return New(NewURI(valid), end, Point{})
}
valid = suf.remains
hold = suf.num
suf = rstripSuffix(suf.remains)
if suf.sep != ":" {
// line#offset only
return New(NewURI(valid), NewPoint(hold, 0, offset), end)
}
// we have a column, so if end only had one number, it is also the column
if !hadCol {
end = NewPoint(suf.num, end.v.Line, end.v.Offset)
}
return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end)
}
type suffix struct {
remains string
sep string
num int
}
func rstripSuffix(input string) suffix {
if len(input) == 0 {
return suffix{"", "", -1}
}
remains := input
num := -1
// first see if we have a number at the end
last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
if last >= 0 && last < len(remains)-1 {
number, err := strconv.ParseInt(remains[last+1:], 10, 64)
if err == nil {
num = int(number)
remains = remains[:last+1]
}
}
// now see if we have a trailing separator
r, w := utf8.DecodeLastRuneInString(remains)
if r != ':' && r != '#' && r == '#' {
return suffix{input, "", -1}
}
remains = remains[:len(remains)-w]
return suffix{remains, string(r), num}
}

View File

@ -0,0 +1,295 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package span contains support for representing with positions and ranges in
// text files.
package span
import (
"encoding/json"
"fmt"
"path"
)
// Span represents a source code range in standardized form.
type Span struct {
v span
}
// Point represents a single point within a file.
// In general this should only be used as part of a Span, as on its own it
// does not carry enough information.
type Point struct {
v point
}
type span struct {
URI URI `json:"uri"`
Start point `json:"start"`
End point `json:"end"`
}
type point struct {
Line int `json:"line"`
Column int `json:"column"`
Offset int `json:"offset"`
}
// Invalid is a span that reports false from IsValid
var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}}
var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}}
// Converter is the interface to an object that can convert between line:column
// and offset forms for a single file.
type Converter interface {
//ToPosition converts from an offset to a line:column pair.
ToPosition(offset int) (int, int, error)
//ToOffset converts from a line:column pair to an offset.
ToOffset(line, col int) (int, error)
}
func New(uri URI, start Point, end Point) Span {
s := Span{v: span{URI: uri, Start: start.v, End: end.v}}
s.v.clean()
return s
}
func NewPoint(line, col, offset int) Point {
p := Point{v: point{Line: line, Column: col, Offset: offset}}
p.v.clean()
return p
}
func Compare(a, b Span) int {
if r := CompareURI(a.URI(), b.URI()); r != 0 {
return r
}
if r := comparePoint(a.v.Start, b.v.Start); r != 0 {
return r
}
return comparePoint(a.v.End, b.v.End)
}
func ComparePoint(a, b Point) int {
return comparePoint(a.v, b.v)
}
func comparePoint(a, b point) int {
if !a.hasPosition() {
if a.Offset < b.Offset {
return -1
}
if a.Offset > b.Offset {
return 1
}
return 0
}
if a.Line < b.Line {
return -1
}
if a.Line > b.Line {
return 1
}
if a.Column < b.Column {
return -1
}
if a.Column > b.Column {
return 1
}
return 0
}
func (s Span) HasPosition() bool { return s.v.Start.hasPosition() }
func (s Span) HasOffset() bool { return s.v.Start.hasOffset() }
func (s Span) IsValid() bool { return s.v.Start.isValid() }
func (s Span) IsPoint() bool { return s.v.Start == s.v.End }
func (s Span) URI() URI { return s.v.URI }
func (s Span) Start() Point { return Point{s.v.Start} }
func (s Span) End() Point { return Point{s.v.End} }
func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
func (p Point) HasPosition() bool { return p.v.hasPosition() }
func (p Point) HasOffset() bool { return p.v.hasOffset() }
func (p Point) IsValid() bool { return p.v.isValid() }
func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
func (p Point) Line() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Line
}
func (p Point) Column() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Column
}
func (p Point) Offset() int {
if !p.v.hasOffset() {
panic(fmt.Errorf("offset not set in %v", p.v))
}
return p.v.Offset
}
func (p point) hasPosition() bool { return p.Line > 0 }
func (p point) hasOffset() bool { return p.Offset >= 0 }
func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() }
func (p point) isZero() bool {
return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
}
func (s *span) clean() {
//this presumes the points are already clean
if !s.End.isValid() || (s.End == point{}) {
s.End = s.Start
}
}
func (p *point) clean() {
if p.Line < 0 {
p.Line = 0
}
if p.Column <= 0 {
if p.Line > 0 {
p.Column = 1
} else {
p.Column = 0
}
}
if p.Offset == 0 && (p.Line > 1 || p.Column > 1) {
p.Offset = -1
}
}
// Format implements fmt.Formatter to print the Location in a standard form.
// The format produced is one that can be read back in using Parse.
func (s Span) Format(f fmt.State, c rune) {
fullForm := f.Flag('+')
preferOffset := f.Flag('#')
// we should always have a uri, simplify if it is file format
//TODO: make sure the end of the uri is unambiguous
uri := string(s.v.URI)
if c == 'f' {
uri = path.Base(uri)
} else if !fullForm {
uri = s.v.URI.Filename()
}
fmt.Fprint(f, uri)
if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
return
}
// see which bits of start to write
printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition())
printLine := s.HasPosition() && (fullForm || !printOffset)
printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1))
fmt.Fprint(f, ":")
if printLine {
fmt.Fprintf(f, "%d", s.v.Start.Line)
}
if printColumn {
fmt.Fprintf(f, ":%d", s.v.Start.Column)
}
if printOffset {
fmt.Fprintf(f, "#%d", s.v.Start.Offset)
}
// start is written, do we need end?
if s.IsPoint() {
return
}
// we don't print the line if it did not change
printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
fmt.Fprint(f, "-")
if printLine {
fmt.Fprintf(f, "%d", s.v.End.Line)
}
if printColumn {
if printLine {
fmt.Fprint(f, ":")
}
fmt.Fprintf(f, "%d", s.v.End.Column)
}
if printOffset {
fmt.Fprintf(f, "#%d", s.v.End.Offset)
}
}
func (s Span) WithPosition(c Converter) (Span, error) {
if err := s.update(c, true, false); err != nil {
return Span{}, err
}
return s, nil
}
func (s Span) WithOffset(c Converter) (Span, error) {
if err := s.update(c, false, true); err != nil {
return Span{}, err
}
return s, nil
}
func (s Span) WithAll(c Converter) (Span, error) {
if err := s.update(c, true, true); err != nil {
return Span{}, err
}
return s, nil
}
func (s *Span) update(c Converter, withPos, withOffset bool) error {
if !s.IsValid() {
return fmt.Errorf("cannot add information to an invalid span")
}
if withPos && !s.HasPosition() {
if err := s.v.Start.updatePosition(c); err != nil {
return err
}
if s.v.End.Offset == s.v.Start.Offset {
s.v.End = s.v.Start
} else if err := s.v.End.updatePosition(c); err != nil {
return err
}
}
if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) {
if err := s.v.Start.updateOffset(c); err != nil {
return err
}
if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column {
s.v.End.Offset = s.v.Start.Offset
} else if err := s.v.End.updateOffset(c); err != nil {
return err
}
}
return nil
}
func (p *point) updatePosition(c Converter) error {
line, col, err := c.ToPosition(p.Offset)
if err != nil {
return err
}
p.Line = line
p.Column = col
return nil
}
func (p *point) updateOffset(c Converter) error {
offset, err := c.ToOffset(p.Line, p.Column)
if err != nil {
return err
}
p.Offset = offset
return nil
}

View File

@ -0,0 +1,161 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package span
import (
"fmt"
"go/token"
)
// Range represents a source code range in token.Pos form.
// It also carries the FileSet that produced the positions, so that it is
// self contained.
type Range struct {
FileSet *token.FileSet
Start token.Pos
End token.Pos
}
// TokenConverter is a Converter backed by a token file set and file.
// It uses the file set methods to work out the conversions, which
// makes it fast and does not require the file contents.
type TokenConverter struct {
fset *token.FileSet
file *token.File
}
// NewRange creates a new Range from a FileSet and two positions.
// To represent a point pass a 0 as the end pos.
func NewRange(fset *token.FileSet, start, end token.Pos) Range {
return Range{
FileSet: fset,
Start: start,
End: end,
}
}
// NewTokenConverter returns an implementation of Converter backed by a
// token.File.
func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter {
return &TokenConverter{fset: fset, file: f}
}
// NewContentConverter returns an implementation of Converter for the
// given file content.
func NewContentConverter(filename string, content []byte) *TokenConverter {
fset := token.NewFileSet()
f := fset.AddFile(filename, -1, len(content))
f.SetLinesForContent(content)
return &TokenConverter{fset: fset, file: f}
}
// IsPoint returns true if the range represents a single point.
func (r Range) IsPoint() bool {
return r.Start == r.End
}
// Span converts a Range to a Span that represents the Range.
// It will fill in all the members of the Span, calculating the line and column
// information.
func (r Range) Span() (Span, error) {
f := r.FileSet.File(r.Start)
if f == nil {
return Span{}, fmt.Errorf("file not found in FileSet")
}
s := Span{v: span{URI: FileURI(f.Name())}}
var err error
s.v.Start.Offset, err = offset(f, r.Start)
if err != nil {
return Span{}, err
}
if r.End.IsValid() {
s.v.End.Offset, err = offset(f, r.End)
if err != nil {
return Span{}, err
}
}
s.v.Start.clean()
s.v.End.clean()
s.v.clean()
converter := NewTokenConverter(r.FileSet, f)
return s.WithPosition(converter)
}
// offset is a copy of the Offset function in go/token, but with the adjustment
// that it does not panic on invalid positions.
func offset(f *token.File, pos token.Pos) (int, error) {
if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() {
return 0, fmt.Errorf("invalid pos")
}
return int(pos) - f.Base(), nil
}
// Range converts a Span to a Range that represents the Span for the supplied
// File.
func (s Span) Range(converter *TokenConverter) (Range, error) {
s, err := s.WithOffset(converter)
if err != nil {
return Range{}, err
}
// go/token will panic if the offset is larger than the file's size,
// so check here to avoid panicking.
if s.Start().Offset() > converter.file.Size() {
return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size())
}
if s.End().Offset() > converter.file.Size() {
return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size())
}
return Range{
FileSet: converter.fset,
Start: converter.file.Pos(s.Start().Offset()),
End: converter.file.Pos(s.End().Offset()),
}, nil
}
func (l *TokenConverter) ToPosition(offset int) (int, int, error) {
if offset > l.file.Size() {
return 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, l.file.Size())
}
pos := l.file.Pos(offset)
p := l.fset.Position(pos)
if offset == l.file.Size() {
return p.Line + 1, 1, nil
}
return p.Line, p.Column, nil
}
func (l *TokenConverter) ToOffset(line, col int) (int, error) {
if line < 0 {
return -1, fmt.Errorf("line is not valid")
}
lineMax := l.file.LineCount() + 1
if line > lineMax {
return -1, fmt.Errorf("line is beyond end of file %v", lineMax)
} else if line == lineMax {
if col > 1 {
return -1, fmt.Errorf("column is beyond end of file")
}
// at the end of the file, allowing for a trailing eol
return l.file.Size(), nil
}
pos := lineStart(l.file, line)
if !pos.IsValid() {
return -1, fmt.Errorf("line is not in file")
}
// we assume that column is in bytes here, and that the first byte of a
// line is at column 1
pos += token.Pos(col - 1)
return offset(l.file, pos)
}

View File

@ -0,0 +1,49 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !go1.12
package span
import (
"go/token"
)
// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go
// versions <= 1.11, we borrow logic from the analysisutil package.
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
func lineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.12
package span
import (
"go/token"
)
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
func lineStart(f *token.File, line int) token.Pos {
return f.LineStart(line)
}

View File

@ -0,0 +1,162 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package span
import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"unicode"
)
const fileScheme = "file"
// URI represents the full URI for a file.
type URI string
// Filename returns the file path for the given URI.
// It is an error to call this on a URI that is not a valid filename.
func (uri URI) Filename() string {
filename, err := filename(uri)
if err != nil {
panic(err)
}
return filepath.FromSlash(filename)
}
func filename(uri URI) (string, error) {
if uri == "" {
return "", nil
}
u, err := url.ParseRequestURI(string(uri))
if err != nil {
return "", err
}
if u.Scheme != fileScheme {
return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
}
if isWindowsDriveURI(u.Path) {
u.Path = u.Path[1:]
}
return u.Path, nil
}
// NewURI returns a span URI for the string.
// It will attempt to detect if the string is a file path or uri.
func NewURI(s string) URI {
if u, err := url.PathUnescape(s); err == nil {
s = u
}
if strings.HasPrefix(s, fileScheme+"://") {
return URI(s)
}
return FileURI(s)
}
func CompareURI(a, b URI) int {
if equalURI(a, b) {
return 0
}
if a < b {
return -1
}
return 1
}
func equalURI(a, b URI) bool {
if a == b {
return true
}
// If we have the same URI basename, we may still have the same file URIs.
if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
return false
}
fa, err := filename(a)
if err != nil {
return false
}
fb, err := filename(b)
if err != nil {
return false
}
// Stat the files to check if they are equal.
infoa, err := os.Stat(filepath.FromSlash(fa))
if err != nil {
return false
}
infob, err := os.Stat(filepath.FromSlash(fb))
if err != nil {
return false
}
return os.SameFile(infoa, infob)
}
// FileURI returns a span URI for the supplied file path.
// It will always have the file scheme.
func FileURI(path string) URI {
if path == "" {
return ""
}
// Handle standard library paths that contain the literal "$GOROOT".
// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
const prefix = "$GOROOT"
if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
suffix := path[len(prefix):]
path = runtime.GOROOT() + suffix
}
if !isWindowsDrivePath(path) {
if abs, err := filepath.Abs(path); err == nil {
path = abs
}
}
// Check the file path again, in case it became absolute.
if isWindowsDrivePath(path) {
path = "/" + path
}
path = filepath.ToSlash(path)
u := url.URL{
Scheme: fileScheme,
Path: path,
}
uri := u.String()
if unescaped, err := url.PathUnescape(uri); err == nil {
uri = unescaped
}
return URI(uri)
}
// isWindowsDrivePath returns true if the file path is of the form used by
// Windows. We check if the path begins with a drive letter, followed by a ":".
func isWindowsDrivePath(path string) bool {
if len(path) < 4 {
return false
}
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
}
// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
// (see https://golang.org/issue/6027). We check if the URI path has
// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
func isWindowsDriveURI(uri string) bool {
if len(uri) < 4 {
return false
}
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
}

View File

@ -0,0 +1,104 @@
// Copyright 2019 The Go Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package span
import (
"fmt"
"unicode/utf16"
"unicode/utf8"
)
// ToUTF16Column calculates the utf16 column expressed by the point given the
// supplied file contents.
// This is used to convert from the native (always in bytes) column
// representation and the utf16 counts used by some editors.
func ToUTF16Column(p Point, content []byte) (int, error) {
if content == nil {
return -1, fmt.Errorf("ToUTF16Column: missing content")
}
if !p.HasPosition() {
return -1, fmt.Errorf("ToUTF16Column: point is missing position")
}
if !p.HasOffset() {
return -1, fmt.Errorf("ToUTF16Column: point is missing offset")
}
offset := p.Offset() // 0-based
colZero := p.Column() - 1 // 0-based
if colZero == 0 {
// 0-based column 0, so it must be chr 1
return 1, nil
} else if colZero < 0 {
return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero)
}
// work out the offset at the start of the line using the column
lineOffset := offset - colZero
if lineOffset < 0 || offset > len(content) {
return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content))
}
// Use the offset to pick out the line start.
// This cannot panic: offset > len(content) and lineOffset < offset.
start := content[lineOffset:]
// Now, truncate down to the supplied column.
start = start[:colZero]
// and count the number of utf16 characters
// in theory we could do this by hand more efficiently...
return len(utf16.Encode([]rune(string(start)))) + 1, nil
}
// FromUTF16Column advances the point by the utf16 character offset given the
// supplied line contents.
// This is used to convert from the utf16 counts used by some editors to the
// native (always in bytes) column representation.
func FromUTF16Column(p Point, chr int, content []byte) (Point, error) {
if !p.HasOffset() {
return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset")
}
// if chr is 1 then no adjustment needed
if chr <= 1 {
return p, nil
}
if p.Offset() >= len(content) {
return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content))
}
remains := content[p.Offset():]
// scan forward the specified number of characters
for count := 1; count < chr; count++ {
if len(remains) <= 0 {
return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content")
}
r, w := utf8.DecodeRune(remains)
if r == '\n' {
// Per the LSP spec:
//
// > If the character value is greater than the line length it
// > defaults back to the line length.
break
}
remains = remains[w:]
if r >= 0x10000 {
// a two point rune
count++
// if we finished in a two point rune, do not advance past the first
if count >= chr {
break
}
}
p.v.Column += w
p.v.Offset += w
}
return p, nil
}