diff --git a/cmd/cmd.go b/cmd/cmd.go index 315c1b2..8594e8f 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,6 +4,9 @@ import ( "fmt" "log/slog" "os" + + "github.com/harry-hov/gnopls/internal/env" + "github.com/harry-hov/gnopls/internal/lsp" "github.com/spf13/cobra" ) @@ -15,6 +18,14 @@ func GnoplsCmd() *cobra.Command { SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { slog.Info("Initializing Server...") + env := &env.Env{ + GNOROOT: os.Getenv("GNOROOT"), + GNOHOME: env.GnoHome(), + } + err := lsp.RunServer(cmd.Context(), env) + if err != nil { + return err + } return nil }, diff --git a/cmd/serve.go b/cmd/serve.go index 52638c1..18e879f 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -2,6 +2,10 @@ package cmd import ( "log/slog" + "os" + + "github.com/harry-hov/gnopls/internal/env" + "github.com/harry-hov/gnopls/internal/lsp" "github.com/spf13/cobra" ) @@ -13,6 +17,17 @@ func CmdServe() *cobra.Command { Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { slog.Info("Initializing Server...") + env := &env.Env{ + GNOROOT: gnoroot, + GNOHOME: env.GnoHome(), + } + if env.GNOROOT == "" { + env.GNOROOT = os.Getenv("GNOROOT") + } + err := lsp.RunServer(cmd.Context(), env) + if err != nil { + return err + } return nil }, diff --git a/go.mod b/go.mod index 1b9b9b8..683e5ba 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,22 @@ go 1.21.1 require ( github.com/google/go-github v17.0.0+incompatible github.com/spf13/cobra v1.5.0 + go.lsp.dev/jsonrpc2 v0.10.0 + go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 + go.lsp.dev/protocol v0.12.0 ) require ( github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.3.4 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.4 // indirect + go.lsp.dev/uri v0.3.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 39ac77f..37abcf4 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,59 @@ +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc= +github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= +go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= +go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= +go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= +go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= +go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= +go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= +go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/lsp/error.go b/internal/lsp/error.go new file mode 100644 index 0000000..f531ac3 --- /dev/null +++ b/internal/lsp/error.go @@ -0,0 +1,12 @@ +package lsp + +import ( + "context" + "fmt" + + "go.lsp.dev/jsonrpc2" +) + +func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) +} diff --git a/internal/lsp/run.go b/internal/lsp/run.go new file mode 100644 index 0000000..bb4ae78 --- /dev/null +++ b/internal/lsp/run.go @@ -0,0 +1,23 @@ +package lsp + +import ( + "context" + "errors" + "io" + "os" + + "github.com/harry-hov/gnopls/internal/env" + "go.lsp.dev/jsonrpc2" + "go.lsp.dev/pkg/fakenet" +) + +func RunServer(ctx context.Context, env *env.Env) error { + conn := jsonrpc2.NewConn(jsonrpc2.NewStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout))) + handler := BuildServerHandler(conn, env) + stream := jsonrpc2.HandlerServer(handler) + err := stream.ServeStream(ctx, conn) + if errors.Is(err, io.EOF) { + return nil + } + return err +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go new file mode 100644 index 0000000..5c2c6fe --- /dev/null +++ b/internal/lsp/server.go @@ -0,0 +1,102 @@ +package lsp + +import ( + "context" + "encoding/json" + "log/slog" + "os" + "path/filepath" + + "go.lsp.dev/jsonrpc2" + "go.lsp.dev/protocol" + + "github.com/harry-hov/gnopls/internal/env" + "github.com/harry-hov/gnopls/internal/version" +) + +type server struct { + conn jsonrpc2.Conn + env *env.Env + +} + +func BuildServerHandler(conn jsonrpc2.Conn, env *env.Env) jsonrpc2.Handler { + dirs := []string{} + if env.GNOROOT != "" { + dirs = append(dirs, filepath.Join(env.GNOROOT, "examples")) + dirs = append(dirs, filepath.Join(env.GNOROOT, "gnovm/stdlibs")) + } + server := &server{ + conn: conn, + + env: env, + + } + + return jsonrpc2.ReplyHandler(server.ServerHandler) +} + +func (s *server) ServerHandler(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + switch req.Method() { + case "exit": + return s.Exit(ctx, reply, req) + case "initialize": + return s.Initialize(ctx, reply, req) + case "initialized": + return s.Initialized(ctx, reply, req) + case "shutdown": + return s.Shutdown(ctx, reply, req) + default: + return jsonrpc2.MethodNotFoundHandler(ctx, reply, req) + } +} + +func (s *server) Initialize(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + var params protocol.InitializeParams + if err := json.Unmarshal(req.Params(), ¶ms); err != nil { + return sendParseError(ctx, reply, err) + } + + return reply(ctx, protocol.InitializeResult{ + ServerInfo: &protocol.ServerInfo{ + Name: "gnopls", + Version: version.Version, + }, + Capabilities: protocol.ServerCapabilities{ + TextDocumentSync: protocol.TextDocumentSyncOptions{ + Change: protocol.TextDocumentSyncKindFull, + OpenClose: true, + Save: &protocol.SaveOptions{ + IncludeText: true, + }, + }, + CompletionProvider: &protocol.CompletionOptions{ + TriggerCharacters: []string{"."}, + ResolveProvider: false, + }, + HoverProvider: true, + ExecuteCommandProvider: &protocol.ExecuteCommandOptions{ + Commands: []string{ + "gnopls.version", + }, + }, + DocumentFormattingProvider: true, + }, + }, nil) +} + +func (s *server) Initialized(ctx context.Context, reply jsonrpc2.Replier, _ jsonrpc2.Request) error { + slog.Info("initialized") + return reply(ctx, nil, nil) +} + +func (s *server) Shutdown(ctx context.Context, reply jsonrpc2.Replier, _ jsonrpc2.Request) error { + slog.Info("shutdown") + return reply(ctx, nil, s.conn.Close()) +} + +func (s *server) Exit(ctx context.Context, reply jsonrpc2.Replier, _ jsonrpc2.Request) error { + slog.Info("exit") + os.Exit(1) + return nil +}