Neovimに移行したしせっかくなのでGoでプラグインを書いてみた

github.com

Google翻訳APIを使ってテキストを翻訳するプラグイン。
作ったあとにVim & Go界隈で著名なhaya14busaさんがほぼ同じプラグインを作ってることに気づきました。

trans_nvim_previe_log

neovim/go-clientを使っていてNeovimでしか動きません。
(python-clientの方だとNeovim/Vim8で手軽に両立するやり方がある模様)
プラグインのインストールにGoが必要です。

サンプルが手薄だったり実装の参考になりそうな他のプラグインがzchee/nvim-goくらいしかみつからなかった(とはいえめっちゃ参考になった)ので、わりとneovim/go-clientのコードとにらめっこしながら書きました。

苦労したところは、previewってどうやってつくるんだろう?とかvim的な知識の足りなさ。一体何をどうするのが正解なのか未だによく分からんです。
ちなみに自分の行き着いたpreviewの作り方は次のとおり。

trans.go#L175:L203

  1. silent pclose
    • previewをとりあえず閉じる
  2. silent pedit +set noswapfile buftype=nofile translated
    • ファイルなしでpreviewを開く
  3. wincmd P
    • previewに移動する
  4. neovim/go-clientのメソッドを使って文字列をクリアして書き込む
  5. wincmd p
    • previewから抜ける

果たしてこれで合っているのか...?という気持ちですが、今のところ動いてるので良し。もしダメだったらpull-reqください。
あと誰かこの泥臭い部分を隠蔽するいい感じのラッパー作ってください!

おわり

Goだけで完結できるわけではない(vimのコマンドを直接叩いたりする)
それでもvim scriptを書くより敷居は低いし楽しい。

twitchtv/twirp を試した

github.com

gRPCのようなフレームワークで、違いはHTTP 1.1で動くこととJSONをサポートしてること。

インストール

protoc-gen-twirpの他にprotocとprotoc-gen-goも必要。

$ go get github.com/twitchtv/twirp/protoc-gen-twirp

$ brew install protobuf
$ go get github.com/golang/protobuf/protoc-gen-go

protoファイル

まずprotoファイルを書く。

$ mkdir proto
$ vi proto/hello.proto
syntax = "proto3";
package utahta.twirp.example.helloworld;
option go_package = "helloworld";

service HellowWorld {
  rpc Hello(HelloReq) returns (HelloResp);
}

message HelloReq {
  string subject = 1;
}

message HelloResp {
  string test = 1;
}

protocする

protoファイルからgoファイルをつくる。

$ mkdir helloworld
$ protoc --proto_path=./proto --twirp_out=./helloworld --go_out=./helloworld ./proto/hello.proto
$ ls helloworld
hello.pb.go    hello.twirp.go

サーバを書く

$ mkdir server
$ vi server/main.go
package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/utahta/twirp-example/helloworld"
)

type Server struct{}

func (s *Server) Hello(ctx context.Context, req *helloworld.HelloReq) (*helloworld.HelloResp, error) {
    return &helloworld.HelloResp{
        Test: fmt.Sprintf("Subject: %s", req.Subject),
    }, nil
}

func main() {
    s := &Server{}
    handler := helloworld.NewHellowWorldServer(s, nil)
    http.ListenAndServe(":8881", handler)
}

クライアントを書く

$ mkdir client
$ vi client/main.go
package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/utahta/twirp-example/helloworld"
)

func main() {
    c := helloworld.NewHellowWorldProtobufClient("http://localhost:8881", http.DefaultClient)
    resp, err := c.Hello(context.Background(), &helloworld.HelloReq{Subject: "hello twirp"})
    if err != nil {
        panic(err)
    }
    fmt.Printf("%#v\n", resp)
}

最終的に次のようなディレクトリ構成になった。

.
├── client
│   └── main.go
├── helloworld
│   ├── hello.pb.go
│   └── hello.twirp.go
├── proto
│   └── hello.proto
└── server
    └── main.go

実行する

まずサーバを実行。

$ go run server/main.go

次にクライアントを実行する。 すると結果が返ってくる。

$ go run client/main.go
&helloworld.HelloResp{Test:"Subject: hello twirp"}

curlで実行する

JSONに対応しているのでcurlを使ってさくっとリクエストできる。

$ curl -H 'Content-Type:application/json' -X POST -d '{"subject":"hello curl"}' "http://127.0.0.1:8881/twirp/utahta.twirp.example.helloworld.HellowWorld/Hello"

雑感

シュッと書いたらProtocolBuffersとJSONで会話できるようになってすごい。便利。
学習コストの低さがなによりいい。

2018年にもなって xargs でハマった

BSD と GNU 版の挙動の違いにハマった。

以下、ミニマムに試した結果。

環境
BSD xargs: macOS 10.12.6
GNU xargs: ubuntu 18.04 xargs (GNU findutils) 4.7.0-git

同じ

BSD xargs

$ echo "a b c" | xargs -n1
a
b
c

GNU xargs

$ echo "a b c" | xargs -n1
a
b
c

異なる

BSD xargs

$ echo "a b c" | xargs -n1 -I {} echo {}
a
b
c

GNU xargs

$ echo "a b c" | xargs -n1 -I {} echo {}
a b c

このように異なるおかげで、a, b, c と1つずつ処理しているつもりが、GNU版では一度に処理されてしまって、意図しない動きになっていた。

macOS では動くのに CI で動かない状態だったので、気づくのにすこし時間がかかった。つらい。

man をみたところ、GNU版は、-Iを使うとセパレータが改行になるらしい。ヘー。

面倒くさくなったので、xargs を使わないように改修した。