Skip to content

Commit

Permalink
Add optional Go context to GLSP context
Browse files Browse the repository at this point in the history
See #27

Also various cleanups and improvements to example server
  • Loading branch information
tliron committed Mar 19, 2024
1 parent 57fe885 commit c852321
Show file tree
Hide file tree
Showing 22 changed files with 203 additions and 179 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ import (

const lsName = "my language"

var version string = "0.0.1"
var handler protocol.Handler
var (
version string = "0.0.1"
handler protocol.Handler
)

func main() {
// This increases logging verbosity (optional)
Expand Down
12 changes: 7 additions & 5 deletions common.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package glsp

import (
contextpkg "context"
"encoding/json"
)

type NotifyFunc func(method string, params any)
type CallFunc func(method string, params any, result any)

type Context struct {
Method string
Params json.RawMessage
Notify NotifyFunc
Call CallFunc
Method string
Params json.RawMessage
Notify NotifyFunc
Call CallFunc
Context contextpkg.Context // can be nil
}

type Handler interface {
Handle(context *Context) (r any, validMethod bool, validParams bool, err error)
Handle(context *Context) (result any, validMethod bool, validParams bool, err error)
}
15 changes: 8 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/tliron/glsp

go 1.21
go 1.22

require (
github.com/gorilla/websocket v1.5.1
github.com/pkg/errors v0.9.1
github.com/sourcegraph/jsonrpc2 v0.2.0
github.com/tliron/commonlog v0.2.8
github.com/tliron/commonlog v0.2.15
)

require (
Expand All @@ -19,9 +19,10 @@ require (
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/tliron/kutil v0.3.11 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.14.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/tliron/kutil v0.3.18 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
)
26 changes: 14 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U=
github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
github.com/tliron/commonlog v0.2.8 h1:vpKrEsZX4nlneC9673pXpeKqv3cFLxwpzNEZF1qiaQQ=
github.com/tliron/commonlog v0.2.8/go.mod h1:HgQZrJEuiKLLRvUixtPWGcmTmWWtKkCtywF6x9X5Spw=
github.com/tliron/kutil v0.3.11 h1:kongR0dhrrn9FR/3QRFoUfQe27t78/xQvrU9aXIy5bk=
github.com/tliron/kutil v0.3.11/go.mod h1:4IqOAAdpJuDxYbJxMv4nL8LSH0mPofSrdwIv8u99PDc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
github.com/tliron/commonlog v0.2.15 h1:vukJpUFLsHapH9kvxfPXZmp4hV3fB1nPMdDsmTJnJCo=
github.com/tliron/commonlog v0.2.15/go.mod h1:+K652hPl5OyLV7vy85F/YMAVPcQ6yOfXHG6uaX890dM=
github.com/tliron/kutil v0.3.18 h1:PWr/yifVkRyTGHjazp/TnN+h0QpLjPLpLr+KGuIehPE=
github.com/tliron/kutil v0.3.18/go.mod h1:kG1q5w/3HCqDvignwn4isRbfFogBPoOwXW4RM/xnPjU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
4 changes: 2 additions & 2 deletions protocol_3_16/base-protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (self *IntegerOrString) MarshalJSON() ([]byte, error) {
return json.Marshal(self.Value)
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *IntegerOrString) UnmarshalJSON(data []byte) error {
var value Integer
if err := json.Unmarshal(data, &value); err == nil {
Expand All @@ -68,7 +68,7 @@ func (self BoolOrString) MarshalJSON() ([]byte, error) {
return json.Marshal(self.Value)
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self BoolOrString) UnmarshalJSON(data []byte) error {
var value bool
if err := json.Unmarshal(data, &value); err == nil {
Expand Down
4 changes: 2 additions & 2 deletions protocol_3_16/base-structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ type TextDocumentEdit struct {
Edits []any `json:"edits"` // TextEdit | AnnotatedTextEdit
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *TextDocumentEdit) UnmarshalJSON(data []byte) error {
var value struct {
TextDocument OptionalVersionedTextDocumentIdentifier `json:"textDocument"`
Expand Down Expand Up @@ -620,7 +620,7 @@ type WorkspaceEdit struct {
ChangeAnnotations map[ChangeAnnotationIdentifier]ChangeAnnotation `json:"changeAnnotations,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *WorkspaceEdit) UnmarshalJSON(data []byte) error {
var value struct {
Changes map[DocumentUri][]TextEdit `json:"changes"`
Expand Down
2 changes: 1 addition & 1 deletion protocol_3_16/general-messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ type ServerCapabilitiesWorkspaceFileOperations struct {
WillDelete *FileOperationRegistrationOptions `json:"willDelete,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *ServerCapabilities) UnmarshalJSON(data []byte) error {
var value struct {
TextDocumentSync json.RawMessage `json:"textDocumentSync,omitempty"` // nil | TextDocumentSyncOptions | TextDocumentSyncKind
Expand Down
2 changes: 1 addition & 1 deletion protocol_3_16/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ type Handler struct {
lock sync.Mutex
}

// glsp.Handler interface
// ([glsp.Handler] interface)
func (self *Handler) Handle(context *glsp.Context) (r any, validMethod bool, validParams bool, err error) {
if !self.IsInitialized() && (context.Method != MethodInitialize) {
return nil, true, true, errors.New("server not initialized")
Expand Down
14 changes: 7 additions & 7 deletions protocol_3_16/language-features.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ type CompletionItem struct {
Data any `json:"data,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *CompletionItem) UnmarshalJSON(data []byte) error {
var value struct {
Label string `json:"label"`
Expand Down Expand Up @@ -645,7 +645,7 @@ type Hover struct {
Range *Range `json:"range,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *Hover) UnmarshalJSON(data []byte) error {
var value struct {
Contents json.RawMessage `json:"contents"` // MarkupContent | MarkedString | []MarkedString
Expand Down Expand Up @@ -708,7 +708,7 @@ func (self MarkedString) MarshalJSON() ([]byte, error) {
return json.Marshal(self.value)
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self MarkedString) UnmarshalJSON(data []byte) error {
var value string
if err := json.Unmarshal(data, &value); err == nil {
Expand Down Expand Up @@ -954,7 +954,7 @@ type SignatureInformation struct {
ActiveParameter *UInteger `json:"activeParameter,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *SignatureInformation) UnmarshalJSON(data []byte) error {
var value struct {
Label string `json:"label"`
Expand Down Expand Up @@ -1014,7 +1014,7 @@ type ParameterInformation struct {
Documentation any `json:"documentation,omitempty"` // nil | string | MarkupContent
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *ParameterInformation) UnmarshalJSON(data []byte) error {
var value struct {
Label json.RawMessage `json:"label"` // string | [2]UInteger
Expand Down Expand Up @@ -2856,7 +2856,7 @@ type SemanticTokensClientCapabilities struct {
MultilineTokenSupport *bool `json:"multilineTokenSupport,omitempty"`
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *SemanticTokensClientCapabilities) UnmarshalJSON(data []byte) error {
var value struct {
DynamicRegistration *bool `json:"dynamicRegistration,omitempty"`
Expand Down Expand Up @@ -2933,7 +2933,7 @@ type SemanticTokensOptions struct {
Full any `json:"full,omitempty"` // nil | bool | SemanticDelta
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *SemanticTokensOptions) UnmarshalJSON(data []byte) error {
var value struct {
Legend SemanticTokensLegend `json:"legend"`
Expand Down
4 changes: 2 additions & 2 deletions protocol_3_16/text-document-synchronization.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ type DidChangeTextDocumentParams struct {
ContentChanges []any `json:"contentChanges"` // TextDocumentContentChangeEvent or TextDocumentContentChangeEventWhole
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *DidChangeTextDocumentParams) UnmarshalJSON(data []byte) error {
var value struct {
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
Expand Down Expand Up @@ -299,7 +299,7 @@ type TextDocumentSyncOptions struct {
Save any `json:"save,omitempty"` // nil | bool | SaveOptions
}

// json.Unmarshaler interface
// ([json.Unmarshaler] interface)
func (self *TextDocumentSyncOptions) UnmarshalJSON(data []byte) error {
var value struct {
OpenClose *bool `json:"openClose"`
Expand Down
40 changes: 40 additions & 0 deletions server/connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package server

import (
contextpkg "context"
"io"

"github.com/gorilla/websocket"
"github.com/sourcegraph/jsonrpc2"
wsjsonrpc2 "github.com/sourcegraph/jsonrpc2/websocket"
"github.com/tliron/commonlog"
)

func (self *Server) newStreamConnection(stream io.ReadWriteCloser) *jsonrpc2.Conn {
handler := self.newHandler()
connectionOptions := self.newConnectionOptions()

context, cancel := contextpkg.WithTimeout(contextpkg.Background(), self.StreamTimeout)
defer cancel()

return jsonrpc2.NewConn(context, jsonrpc2.NewBufferedStream(stream, jsonrpc2.VSCodeObjectCodec{}), handler, connectionOptions...)
}

func (self *Server) newWebSocketConnection(socket *websocket.Conn) *jsonrpc2.Conn {
handler := self.newHandler()
connectionOptions := self.newConnectionOptions()

context, cancel := contextpkg.WithTimeout(contextpkg.Background(), self.WebSocketTimeout)
defer cancel()

return jsonrpc2.NewConn(context, wsjsonrpc2.NewObjectStream(socket), handler, connectionOptions...)
}

func (self *Server) newConnectionOptions() []jsonrpc2.ConnOpt {
if self.Debug {
log := commonlog.NewScopeLogger(self.Log, "rpc")
return []jsonrpc2.ConnOpt{jsonrpc2.LogMessages(&JSONRPCLogger{log})}
} else {
return nil
}
}
19 changes: 10 additions & 9 deletions server/handle.go → server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ func (self *Server) handle(context contextpkg.Context, connection *jsonrpc2.Conn
Method: request.Method,
Notify: func(method string, params any) {
if err := connection.Notify(context, method, params); err != nil {
self.Log.Errorf("%s", err.Error())
self.Log.Error(err.Error())
}
},
Call: func(method string, params any, result any) {
if err := connection.Call(context, method, params, result); err != nil {
self.Log.Errorf("%s", err.Error())
self.Log.Error(err.Error())
}
},
Context: context,
}

if request.Params != nil {
Expand All @@ -41,23 +42,23 @@ func (self *Server) handle(context contextpkg.Context, connection *jsonrpc2.Conn
return nil, err

default:
// Note: jsonrpc2 will not even call this function if reqest.Params is not valid JSON,
// Note: jsonrpc2 will not even call this function if reqest.Params is invalid JSON,
// so we don't need to handle jsonrpc2.CodeParseError here
r, validMethod, validParams, err := self.Handler.Handle(&glspContext)
result, validMethod, validParams, err := self.Handler.Handle(&glspContext)
if !validMethod {
return nil, &jsonrpc2.Error{
Code: jsonrpc2.CodeMethodNotFound,
Message: fmt.Sprintf("method not supported: %s", request.Method),
}
} else if !validParams {
if err != nil {
if err == nil {
return nil, &jsonrpc2.Error{
Code: jsonrpc2.CodeInvalidParams,
Message: err.Error(),
Code: jsonrpc2.CodeInvalidParams,
}
} else {
return nil, &jsonrpc2.Error{
Code: jsonrpc2.CodeInvalidParams,
Code: jsonrpc2.CodeInvalidParams,
Message: err.Error(),
}
}
} else if err != nil {
Expand All @@ -66,7 +67,7 @@ func (self *Server) handle(context contextpkg.Context, connection *jsonrpc2.Conn
Message: err.Error(),
}
} else {
return r, nil
return result, nil
}
}
}
File renamed without changes.
6 changes: 3 additions & 3 deletions server/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"github.com/tliron/commonlog"
)

type Logger struct {
type JSONRPCLogger struct {
log commonlog.Logger
}

// jsonrpc2.Logger interface
func (self *Logger) Printf(format string, v ...any) {
// ([jsonrpc2.Logger] interface)
func (self *JSONRPCLogger) Printf(format string, v ...any) {
self.log.Debugf(strings.TrimSuffix(format, "\n"), v...)
}
5 changes: 2 additions & 3 deletions server/nodejs.go → server/run-nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ func (self *Server) RunNodeJs() error {
}
file := os.NewFile(uintptr(nodeChannelFdInt), "/glsp/NODE_CHANNEL_FD")

self.Log.Info("listening for Node.js IPC connections")
self.serveStream(file)
self.Log.Info("Node.js IPC connection closed")
self.Log.Notice("listening for Node.js IPC connections")
self.ServeStream(file, nil)
return nil
}
29 changes: 29 additions & 0 deletions server/run-stdio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package server

import (
"errors"
"os"
)

func (self *Server) RunStdio() error {
self.Log.Notice("reading from stdin, writing to stdout")
self.ServeStream(Stdio{}, nil)
return nil
}

type Stdio struct{}

// ([io.Reader] interface)
func (Stdio) Read(p []byte) (int, error) {
return os.Stdin.Read(p)
}

// ([io.Writer] interface)
func (Stdio) Write(p []byte) (int, error) {
return os.Stdout.Write(p)
}

// ([io.Closer] interface)
func (Stdio) Close() error {
return errors.Join(os.Stdin.Close(), os.Stdout.Close())
}
Loading

0 comments on commit c852321

Please sign in to comment.