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:
Родитель
ab3cdcaef5
Коммит
b96c9a057e
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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[:]))
|
||||
}
|
|
@ -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()))
|
||||
}
|
|
@ -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, ¶ms); 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.client.ShowMessage(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "window/logMessage": // notif
|
||||
var params LogMessageParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.client.LogMessage(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "telemetry/event": // notif
|
||||
var params interface{}
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.client.Event(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/publishDiagnostics": // notif
|
||||
var params PublishDiagnosticsParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.client.PublishDiagnostics(ctx, ¶ms); err != nil {
|
||||
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, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.client.Configuration(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "client/registerCapability": // req
|
||||
var params RegistrationParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
err := h.client.RegisterCapability(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, nil, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "client/unregisterCapability": // req
|
||||
var params UnregistrationParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
err := h.client.UnregisterCapability(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, nil, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "window/showMessageRequest": // req
|
||||
var params ShowMessageRequestParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.client.ShowMessageRequest(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "workspace/applyEdit": // req
|
||||
var params ApplyWorkspaceEditParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.client.ApplyEdit(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
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
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidChangeWorkspaceFolders(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "initialized": // notif
|
||||
var params InitializedParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.Initialized(ctx, ¶ms); err != nil {
|
||||
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, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidChangeConfiguration(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/didOpen": // notif
|
||||
var params DidOpenTextDocumentParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidOpen(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/didChange": // notif
|
||||
var params DidChangeTextDocumentParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidChange(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/didClose": // notif
|
||||
var params DidCloseTextDocumentParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidClose(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/didSave": // notif
|
||||
var params DidSaveTextDocumentParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidSave(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/willSave": // notif
|
||||
var params WillSaveTextDocumentParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.WillSave(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "workspace/didChangeWatchedFiles": // notif
|
||||
var params DidChangeWatchedFilesParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.DidChangeWatchedFiles(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "$/progress": // notif
|
||||
var params ProgressParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.Progress(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "$/setTraceNotification": // notif
|
||||
var params SetTraceParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.SetTraceNotification(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "$/logTraceNotification": // notif
|
||||
var params LogTraceParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
if err := h.server.LogTraceNotification(ctx, ¶ms); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/implementation": // req
|
||||
var params ImplementationParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Implementation(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/typeDefinition": // req
|
||||
var params TypeDefinitionParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.TypeDefinition(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/documentColor": // req
|
||||
var params DocumentColorParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.DocumentColor(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/colorPresentation": // req
|
||||
var params ColorPresentationParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.ColorPresentation(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/foldingRange": // req
|
||||
var params FoldingRangeParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.FoldingRange(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/declaration": // req
|
||||
var params DeclarationParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Declaration(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/selectionRange": // req
|
||||
var params SelectionRangeParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.SelectionRange(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "initialize": // req
|
||||
var params ParamInitia
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Initialize(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
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, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.WillSaveWaitUntil(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/completion": // req
|
||||
var params CompletionParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Completion(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "completionItem/resolve": // req
|
||||
var params CompletionItem
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Resolve(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/hover": // req
|
||||
var params HoverParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Hover(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/signatureHelp": // req
|
||||
var params SignatureHelpParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.SignatureHelp(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/definition": // req
|
||||
var params DefinitionParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Definition(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/references": // req
|
||||
var params ReferenceParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.References(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/documentHighlight": // req
|
||||
var params DocumentHighlightParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.DocumentHighlight(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/documentSymbol": // req
|
||||
var params DocumentSymbolParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.DocumentSymbol(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/codeAction": // req
|
||||
var params CodeActionParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.CodeAction(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "workspace/symbol": // req
|
||||
var params WorkspaceSymbolParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Symbol(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/codeLens": // req
|
||||
var params CodeLensParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.CodeLens(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "codeLens/resolve": // req
|
||||
var params CodeLens
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.ResolveCodeLens(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/documentLink": // req
|
||||
var params DocumentLinkParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.DocumentLink(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "documentLink/resolve": // req
|
||||
var params DocumentLink
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.ResolveDocumentLink(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/formatting": // req
|
||||
var params DocumentFormattingParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Formatting(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/rangeFormatting": // req
|
||||
var params DocumentRangeFormattingParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.RangeFormatting(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/onTypeFormatting": // req
|
||||
var params DocumentOnTypeFormattingParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.OnTypeFormatting(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/rename": // req
|
||||
var params RenameParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.Rename(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "textDocument/prepareRename": // req
|
||||
var params PrepareRenameParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.PrepareRename(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return true
|
||||
case "workspace/executeCommand": // req
|
||||
var params ExecuteCommandParams
|
||||
if err := json.Unmarshal(*r.Params, ¶ms); err != nil {
|
||||
sendParseError(ctx, r, err)
|
||||
return true
|
||||
}
|
||||
resp, err := h.server.ExecuteCommand(ctx, ¶ms)
|
||||
if err := r.Reply(ctx, resp, err); err != nil {
|
||||
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
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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] == ':'
|
||||
}
|
|
@ -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
|
||||
}
|
Загрузка…
Ссылка в новой задаче