Skip to content

Instantly share code, notes, and snippets.

@icholy
Last active September 11, 2023 00:35
Show Gist options
  • Save icholy/23670692d6405e676611fa49081a5f06 to your computer and use it in GitHub Desktop.
Save icholy/23670692d6405e676611fa49081a5f06 to your computer and use it in GitHub Desktop.
package main
import (
"context"
"encoding/json"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/sanity-io/litter"
"go.lsp.dev/jsonrpc2"
"go.lsp.dev/protocol"
"go.lsp.dev/uri"
)
type ResultStatus struct {
Result interface{} `json:"result"`
Status bool `json:"status"`
}
type CmdReadWriteCloser struct {
r io.ReadCloser
w io.WriteCloser
cmd *exec.Cmd
}
func NewCmdReadWriteCloser(cmd *exec.Cmd) (io.ReadWriteCloser, error) {
r, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
w, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
return &CmdReadWriteCloser{r, w, cmd}, nil
}
func (rwc *CmdReadWriteCloser) Read(data []byte) (int, error) {
n, err := rwc.r.Read(data)
// os.Stdout.Write(data[:n])
return n, err
}
func (rwc *CmdReadWriteCloser) Write(data []byte) (int, error) {
// os.Stdout.Write(data)
return rwc.w.Write(data)
}
func (rwc *CmdReadWriteCloser) Close() error { return nil }
func main() {
cmd := exec.Command("typescript-language-server",
"--stdio",
"--log-level", "4",
"--tsserver-log-file", "tsserver.log",
"--tsserver-log-verbosity", "verbose",
)
cmd.Stderr = os.Stderr
rwc, err := NewCmdReadWriteCloser(cmd)
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
stream := jsonrpc2.NewStream(rwc)
conn := jsonrpc2.NewConn(stream)
ctx := context.Background()
// need to handle messages or else protocol.Call doesn't return
conn.Go(ctx, func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
switch req.Method() {
case protocol.MethodWorkDoneProgressCreate:
var params protocol.WorkDoneProgressCreateParams
if err := json.Unmarshal(req.Params(), &params); err != nil {
return err
}
litter.Dump(params)
return reply(ctx, &ResultStatus{Status: true}, nil)
case protocol.MethodProgress:
var params protocol.ProgressParams
if err := json.Unmarshal(req.Params(), &params); err != nil {
return err
}
litter.Dump(params)
if values, ok := params.Value.(map[string]interface{}); ok {
if kind, ok := values["kind"].(string); ok {
if kind == "end" {
return cmd.Process.Kill()
}
}
}
}
return nil
})
// initialize
var initializeRes protocol.InitializeResult
err = protocol.Call(ctx, conn,
protocol.MethodInitialize,
protocol.InitializeParams{
Capabilities: protocol.ClientCapabilities{
Window: &protocol.WindowClientCapabilities{
WorkDoneProgress: true,
},
Workspace: &protocol.WorkspaceClientCapabilities{
WorkspaceFolders: true,
},
},
},
&initializeRes,
)
// initialized
err = conn.Notify(ctx, protocol.MethodInitialized, protocol.InitializedParams{})
if err != nil {
log.Fatal(err)
}
// textDocument/didOpen
filename, err := filepath.Abs("main.ts")
if err != nil {
log.Fatal(err)
}
err = conn.Notify(ctx, protocol.MethodTextDocumentDidOpen, protocol.DidOpenTextDocumentParams{
TextDocument: protocol.TextDocumentItem{
URI: uri.File(filename),
},
})
if err != nil {
log.Fatal(err)
}
// block on process
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"go.lsp.dev/jsonrpc2"
"go.lsp.dev/protocol"
)
func main() {
out, err := os.Create("server.log")
if err != nil {
log.Fatal(err)
}
defer out.Close()
stream := jsonrpc2.NewStream(struct {
io.Reader
io.Writer
io.Closer
}{
Reader: os.Stdin,
Writer: os.Stdout,
Closer: io.NopCloser(nil),
})
conn := jsonrpc2.NewConn(stream)
ctx := context.Background()
conn.Go(ctx, func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error {
switch req.Method() {
case protocol.MethodInitialize:
var params protocol.InitializeParams
if err := json.Unmarshal(req.Params(), &params); err != nil {
return err
}
return reply(ctx, protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
TextDocumentSync: protocol.TextDocumentSyncKindIncremental,
},
}, nil)
case protocol.MethodTextDocumentDidChange:
var params protocol.DidChangeTextDocumentParams
if err := json.Unmarshal(req.Params(), &params); err != nil {
return err
}
var buf bytes.Buffer
if err := json.Indent(&buf, req.Params(), "", " "); err != nil {
return err
}
if _, err := fmt.Fprintln(out, req.Method(), buf.String()); err != nil {
return err
}
}
return nil
})
<-conn.Done()
if err := conn.Err(); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment