VimConf 2024に行ってきた

前回はTinyでしたが、今回はNormal Buildに戻りました。

vimconf.org

実は前々からぼんやりと思考のVimコマンド化みたいなことを話してみたいと思っていました。
また、2020年から3年ほど英語を勉強してきた*1こともあり、いつか英語で登壇したいという想いもありました。
正直、考えがまとまりきっていなかった上に英語力にもそこまで自信はありませんでしたが、迷っていても仕方ないので、思い切って英語での登壇に挑戦することにしました。

speakerdeck.com

ただ、あまり長く喋る自信はなかったため、発表時間は15分を選択。
しかしやはり短かったようで、余裕がありそうなら話そうと思っていたページを入れるどころか、いくつか削らないといけませんでした。

削ったり入れられなかったのは以下のような内容です。

  • quickfixを自動で開くautocmd
  • quickfix関連のbuiltin関数の紹介とその応用例
  • vim-lspの紹介とquickfixとの組み合わせ
  • caddfile/caddbuffer/caddexprの紹介
  • quickfixをソートする自作コマンドの紹介
  • 自作した外部コマンドとの連携
  • 各種デモ
  • など

正確な時間は記録していませんが、体感的には例年の2倍ほど準備に時間をかけたと思います。
その甲斐あって自分としては十分かなというトークができたと思うし、今はやり切った感に溢れています。

そういえば、過去の登壇では練習だと発表時間ギリギリか少し超えてしまっていたのに、本番はいつも少し早く終わっていました。
昨夜に最後の練習していて気付いたんですが、その原因は以下のような点だったかもしれません。

  • 普段の練習の時は基本的に何も見ないようにしていた
    • スライドを見ながら喋ることでトークが途切れにくくなる
    • スピーカーノートを使うとほとんど詰まらなくなる
      • 結局当日は使えなかったけど...
  • 開始時間と終了時間しか見ていなかった
    • 本番では経過時間を意識していたため、遅れないよう話す速度を調整していた

ちなみに、今回はトークを覚えるためにスピーカーノートの内容を.txtにし、macのsayコマンドで.m4aにしたものをずっと聴いたり一緒に喋るということをやっていました。
音声は何種類か試してみましたが最終的にSiri(英語)の1、3、4の3つに落ち着きました。*2
話すスピードは未設定だと速すぎたので-rate=120と少し遅めに設定していました。

そして今日も今まで通りGoogleスライドを使いましたが、なんと会場のスクリーンにプレゼンを映しながらスピーカーノートを表示することができませんでした。
接続チェックの段階でそれに気付いたときはかなり焦りましたが*3、スピーカーノートが完成したところで満足せず、何も見ないで話せるようになるまで練習しておいたおかげで事なきを得ました。

さて、他の方々の素晴らしい発表や懇親会・二次会についても書きたい気持ちはあるのですが、そろそろ体力の限界を迎えてしまいそうなので、一旦このあたりで締めます。。。

それでは今年も素晴らしいカンファレンスを企画・運営してくださったスタッフの皆様、熱心に聞いてくださった参加者の皆様に心より感謝申し上げます。
特に、発表の後に色々な方から感想や質問をいただけて非常に嬉しかったです。
本当にありがとうございました。

*1:今はやっていない...

*2:自分の声にもできるみたいですが、セットアップが大変そうだったので使いませんでした

*3:過去の登壇ではスピーカーノートを使っていなかったので覚えていなかったのかも

VimConf 2023 Tiny に行ってきた

昨日(11/18)はVimConfに行ってきました。
前回が2019年だったので実に4年ぶりの開催でした。

vimconf.org

2017年から2019年は3回ともスピーカーでしたが、今回は久しぶりの一聴衆でした。
なので当日は全く緊張する必要がなく、とても穏やかな気持ちで参加することができました。

また、休憩時間や懇親会などで久しぶりに会えた人と話をすることができ、とても懐かしい気持ちになりました。
そして古巣の皆さんのVim愛は変わらないどころか、さらに強まっていて感慨深かったです。

ただ後になってみると、新しい所属でのVim活なんかをLTしても良かったかなと思ったりもしました。
この感覚はVimConf 2016に参加した時に似ていて、あの時も「自分も登壇したい!」という気持ちになったことを覚えています。

最後に、発表者とスタッフの方に改めて大きな感謝を伝えたいです。
特にスタッフの方は限られたリソースの中でとても大変だったと思いますが、今までのVimConfと変わらない体験を得ることができました。
来年も楽しみにしています。

gnosticでOpenAPIをProtocol Buffersに変換する

gnosticgnostic-grpcを使えばOpenAPIの.yaml(.json)を.protoに変換できる。

github.com

github.com

1. gnosticをインストールする

go install github.com/google/gnostic@latest

2. gnostic-grpcをインストールする

git clone https://github.com/google/gnostic-grpc
cd gnostic-grpc
./COMPILE-PROTOS.sh
./plugin-creation.sh

※ protocコマンドがないとエラーになるので Protocol Buffer Compiler Installation | gRPC あたりを参考にインストールしておく必要がある。

3. 変換する

gnostic --grpc-out=proto openapi.yaml

前回の記事で生成した以下のyamlは、

openapi: 3.0.3
info:
  description: Foo API Document.
  title: Foo API
  version: "1.0"
paths:
  /foo:
    post:
      operationId: PostFoo
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostFooRequest'
      responses:
        "201":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Foo'
          description: Created
        "400":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Bad Request
  /foo/{id}:
    get:
      operationId: GetFoo
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Foo'
          description: OK
        "404":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Not Found
components:
  schemas:
    ErrorResponse:
      properties:
        code:
          type: string
        message:
          type: string
      type: object
    Foo:
      properties:
        data:
          type: string
        id:
          type: string
      type: object
    PostFooRequest:
      properties:
        data:
          type: string
      type: object

次のようなprotoになる。

  • proto/openapi.proto
syntax = "proto3";

package openapi;

import "google/api/annotations.proto";

import "google/protobuf/descriptor.proto";

import "google/protobuf/empty.proto";

option go_package = ".;openapi";

message PostFooRequest {
  string data = 1;
}

//PostFooParameters holds parameters to PostFoo
message PostFooRequest {
  PostFooRequest post_foo_request = 1;
}

//GetFooParameters holds parameters to GetFoo
message GetFooRequest {
  string id = 1;
}

service Openapi {
  rpc PostFoo ( PostFooRequest ) returns ( google.protobuf.Empty ) {
    option (google.api.http) = { post:"/foo" body:"post_foo_request"  };
  }

  rpc GetFoo ( GetFooRequest ) returns ( google.protobuf.Empty ) {
    option (google.api.http) = { get:"/foo/{id}"  };
  }
}

※ OpenAPIの構成によっては変換できない場合がある。
 また、OpenAPI 3.1には対応していない。

github.com/swaggest/openapi-goを使ってOpenAPIを生成する

github.com/swaggest/openapi-goを使うとGoのコードからOpenAPIの定義(json/yaml)を生成できる。

pkg.go.dev

例:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    openapi "github.com/swaggest/openapi-go"
    "github.com/swaggest/openapi-go/openapi3"
)

type endpoint struct {
    id        string
    method    string
    path      string
    request   any
    responses []response
}

type response struct {
    status int
    body   any
}

func main() {
    r := openapi3.Reflector{
        Spec: &openapi3.Spec{
            Openapi:    "3.0.3",
            Components: &openapi3.Components{},
        },
    }

    r.Spec.Info.
        WithTitle("Foo API").
        WithDescription("Foo API Document.").
        WithVersion("1.0")

    list := []endpoint{
        {
            id:      "PostFoo",
            method:  "POST",
            path:    "/foo",
            request: new(PostFooRequest),
            responses: []response{
                {status: 201, body: new(Foo)},
                {status: 400, body: new(ErrorResponse)},
            },
        },
        {
            id:      "GetFoo",
            method:  "GET",
            path:    "/foo/{id}",
            request: new(GetFooRequest),
            responses: []response{
                {status: 200, body: new(Foo)},
                {status: 404, body: new(ErrorResponse)},
            },
        },
    }

    for _, v := range list {
        oc, err := r.NewOperationContext(v.method, v.path)
        if err != nil {
            log.Println(err)
            continue
        }

        oc.SetID(v.id)
        oc.AddReqStructure(v.request)
        for _, resp := range v.responses {
            oc.AddRespStructure(resp.body, openapi.WithHTTPStatus(resp.status))
        }

        if err := r.AddOperation(oc); err != nil {
            log.Println(err)
            continue
        }
    }

    // b, err := r.Spec.MarshalYAML()
    b, err := json.MarshalIndent(r.Spec, "", " ")
    if err != nil {
        log.Println(err)
        return
    }

    fmt.Println(string(b))
}

type PostFooRequest struct {
    Data string `json:"data"`
}

type GetFooRequest struct {
    ID string `json:"id" path:"id"`
}

type Foo struct {
    ID   string `json:"id"`
    Data string `json:"data"`
}

type ErrorResponse struct {
    Code    string `json:"code"`
    Message string `json:"message"`
}

list の中身を既存コードから生成すれば途中からでも比較的簡単にOpenAPIを始められる。

macvimをビルドする(2023/08)

だいぶ前にも書いていた。

daisuzu.hatenablog.com

ここ最近は毎朝UpdateMacVimを実行している。*1

UpdateMacVim() {
    cd $HOME/go/src/github.com/macvim-dev/macvim
    git fetch origin master
    if [ -z "`git diff FETCH_HEAD --shortstat`" ]; then
        cd -
        return 0
    fi
    git merge FETCH_HEAD
    make distclean && ConfigureMacVim && make
    rm -rf $HOME/.local/MacVim.app
    cp -R src/MacVim/build/Release/MacVim.app $HOME/.local/
    cd -
}
ConfigureMacVim() {
    ./configure \
        --enable-fail-if-missing \
        --with-features=huge \
        --enable-terminal \
        --enable-multibyte \
        --enable-python3interp \
        --enable-luainterp \
        --with-lua-prefix="$(brew --prefix lua)" \
        --enable-cscope \
        --with-tlib=ncurses \
        --with-compiledby="daisuzu <daisuzu@gmail.com>" \
        CFLAGS="-I$(brew --prefix)/include" \
        LDFLAGS="-L$(brew --prefix)/lib" \
        --prefix=$HOME/.local "$*"
}

あとは$HOME/.local/MacVim.app/Contents/binにパスを通しておけばOK。

*1:ghq getしたのでGOPATHにmacvimのリポジトリがある

Goの外部パッケージに独自の変更を加える

外部パッケージを使っていて、ちょっとした修正を試したい時は以下のような方法があります。

1. 外部パッケージをForkしてgo.modで置き換える

最も基本的なやり方なので特に理由がなければこちらの方法にするのが良いでしょう。
対象のパッケージのForkに変更を加え、以下のようにしてgo.modで置き換えます。

go mod edit -replace github.com/EXTERNAL_/PACKAGE@v1.0.0=github.com/daisuzu/PACKAGE@development
go mod tidy

なお、Forkにコミットを追加する場合はその度にgo mod tidyで擬似バージョンを更新していく必要があります。

2. 外部パッケージのコピーをリポジトリに追加してgo.modで置き換える

Forkを作りたくなかったり、試行錯誤したい場合などはリポジトリの中に対象の外部パッケージをコピーする方がやりやすいかもしれません。
その場合も同様にgo.modで置き換えます。

go mod edit -replace github.com/EXTERNAL_/PACKAGE@v1.0.0=./PATH_TO_COPY

3. 変更したファイルをリポジトリに追加してoverlayで書き換える

変更が数行程度だと上記の方法が面倒だと感じることがあるかもしれません。
その場合は変更したファイルのみをリポジトリに追加し、goコマンドのoverlayフラグで書き換えることも可能です。

以下のようなoverlay.jsonを用意してgo build -overlay=overlay.jsonのように指定します。

{
  "Replace": {
    "/GO_MOD_CACHE/PATH_TO_EXTERNAL_PACKAGE/TARGET.go": "/PATH_TO_COPY/TARGET.go"
  }
}

いずれの方法もうまくいったら本家に還元しましょう。

Vimの極意

この記事はVim Advent Calendar 2022の1日目の記事です。

今年でVimをメインエディタにして15年になります。
最近どうすれば思考する速度でテキストを編集できるようになる*1のか考えたりすることがあったので、この機会に軽くまとめてみます。

簡単な操作であれば「○○をしたい」と思った瞬間にそうなっていることもありますが、実際はそうならないことがの方が多いです。
それが何故なのかというと、複雑な編集をする際には自分のやりたいことをVimの操作に変換する必要があり、そこに時間がかかっているからだと考えました。

そこで思いついたのが、やろうとしていること自体をVimのコマンド群として捉えられるようになればさらに高速にテキストを編集できるのではないか、ということです。

具体例をあげてみると、以下のようなGoのコードでカーソルがfuncのfにある時に、戻り値の型をClientInterfaceから*Clientに変更したいと思ったら、

func NewClient() ClientInterface {
    return newDefaultClient()
}

戻り値の位置にカーソルを移動して、カーソル下の単語を*Clientに書き換えよう、と考えてVimを操作するのではなく、
最初から$bcw*Clientと考えてVimを操作します。

つまり、「あらゆる編集操作をVimのコマンドで表現できるようになる」ことがVimの極意ということになります。

実際に文章に書いて読んでみるとかなり難しそうな気がしてきましたが、
これを会得する方法としては以下の7つが考えられます。

  1. 極意のことを意識しながらVimを使い続ける
  2. 定期的にVimのヘルプを読み返してコマンドとしての語彙を増やす
  3. 指に負担を感じたら少し立ち止まってより良い方法がないか調べる
  4. VimGolfをやる
  5. 覚えにくい処理をユーザー定義コマンドやマッピングにする
    • vimrcを育てる
  6. 4で汎用性が高いものをプラグインにする
    • 既に似たようなプラグインがあればそれを使っても良い
  7. 5で有用なものをVimの本体に組み込む
    • Vim本体の実装に対する知識が必要

ある程度身に付いてくれば先のサンプルコードで戻り値の型がわからなくてもすぐに
/newD<CR><C-]>Wyiw<C-T>j%bcw<C-R>0<ESC>が出てくるようになることでしょう。

ぜひ試してみてください。

*1:実践Vim 第21章の続き