goplsのdaemonモードを使う
goplsはgopls -listen=<addr>
で実行するとdaemonモードで起動し、指定した<addr>
でTCP接続できるようになる。
クライアントはgopls
を使っても良いし、独自に実装することも可能。
その場合、TCP上で以下のような形式のJSON-RPCを送受信すれば良い。
(改行は\r\n
)
- リクエスト
Content-Length: <JSON部分のbyte数> {"jsonrpc":"2.0","method":"initialize","params":{},"id":1}
- レスポンス
Content-Length: <JSON部分のbyte数> {"jsonrpc":"2.0","result":{},"id":1}
接続ごとに 'initialize' と 'initialized' を送信して初期化したら、あとは 'textDocument/references' や 'callHierarchy/incomingCalls' など、呼びたいメソッドを呼べばOK。
毎回初期化しなくて済むのと、レスポンスをJSONとして扱えるので複雑なことがやりやすくなるはず。
クライアントのサンプル実装はこちら。
import ( "bytes" "encoding/json" "fmt" "net" "sync/atomic" ) type Client struct { id int64 conn net.Conn } func Connect(addr string, initializedParams map[string]interface{}) (*Client, error) { conn, err := net.Dial("tcp", addr) if err != nil { return nil, err } client := &Client{conn: conn} if _, err := client.Call("initialize", initializedParams); err != nil { return nil, err } if _, err := client.Call("initialized", map[string]interface{}{}); err != nil { return nil, err } return client, nil } type response struct { ID int64 `json:"id"` Result json.RawMessage `json:"result"` } func (c *Client) Call(method string, params interface{}) (*json.RawMessage, error) { id := atomic.AddInt64(&c.id, 1) data, err := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "method": method, "params": params, "id": id, }) if err != nil { return nil, err } if _, err := fmt.Fprintf(c.conn, "Content-Length: %v\r\n\r\n%s", len(data), data); err != nil { return nil, err } for { // Content-Lengthまで読む buf := make([]byte, 40) n, err := c.conn.Read(buf) if err != nil { return nil, err } r := bytes.NewBuffer(buf[:n]) var length int if _, err := fmt.Fscanf(r, "Content-Length: %d\r\n\r\n", &length); err != nil { continue } // bufに入りきらなかったBodyを読む body := make([]byte, length) idx := copy(body, r.Bytes()) if _, err = c.conn.Read(body[idx:]); err != nil { return nil, err } var res response if err := json.Unmarshal(body, &res); err != nil { return nil, err } if res.ID != id { // 送信したリクエストに対するレスポンス以外は無視 // (goplsからの通知を含む) continue } return &res.Result, nil } } func (c *Client) Shutdown() error { _, err := c.Call("shutdown", nil) return err }