Goでprotobufの定義をimportせずにフィールドの値を使う
以下のようなコードでreq
にあるフィールドを使いたい場合、
opt := grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // ここでreqから特定のフィールド値を取得したい return nil })
型が定義されているパッケージをインポートして、
if v, ok := req.(*pb.Req); ok { // v.Bodyでフィールドにアクセスできる }
のように変換することになる。
この時、インポートしたくない or インポートできないのであれば、protoc
が生成する定義にはGetterも同時に生成されるため、対象フィールドのGetterがあるinterfaceで型アサーションすると手軽にできる。
if v, ok := req.(interface{ GetBody() []byte }); ok { // v.GetBody()でフィールドの値を取得できる }
あまり使い所はないかもしれないが、google.golang.org/appengineは定義がinternal配下にあるため、例えばテストでWithAPICallFuncを使う際など、以下のようにしてtaskqueue.Addのリクエスト(TaskQueueAddRequest)からフィールドの値を取得できる。
var got []byte ctx = appengine.WithAPICallFunc(ctx, func(ctx context.Context, service, method string, in, out proto.Message) error { if service == "taskqueue" && method == "Add" { if v, ok := in.(interface{ GetBody() []byte }); ok { got = v.GetBody() } } return nil }) DoSomething(ctx) // 関数内でtaskqueue.Add()が呼ばれる if !reflect.DeepEqual(got, want) { t.Errorf("body = %q, want %q", got, want) }
なお、フィールドがインポートできない型になっているとinterfaceも作れないのでこの方法は使えない。
それでも必要な場合はencoding/json
パッケージでMarshal
してからmap[string]interface{}
や独自型にUnmarshal
すれば良い。
gopls(v0.4.0)の機能
goplsは更新頻度が高く、何ができるのかをちゃんと把握できていなかったので、LSPのメソッドベースで今の時点(v0.4.0)の機能をざっと調べてみることにしました。
補完(textDocument/completion)
入力中の変数や定数、関数といった各種定義などをbuiltinも含めて補完してくれます。
この機能のためにgopls
を使っているという人もけっこう多いはず。
基本的に文脈に合わせた候補を返したり、優先度が高くなるようになっていますが、
- おかしな候補が返ってくる
- 期待する候補が返ってこない
という場合はコントリビュート(issue報告や修正する)チャンスかもしれません。
例えば、
- スコープに存在しない定義は候補にならない
- スコープ外の変数など
- カーソル位置で使えないキーワードは候補にならない
iota
: const入力中のみrange
: for入力中のみbreak
: for、switch、select内continue
: for内- など
- 型が合わないものは候補から外されることがある
- 代入時
のような制御があります。
また、以下の場合は補完で入力される内容自体が変化します。
- 参照が要求される場合、値には
&
がつく
// vへの代入時、 var v *int i := 1 v = // iは&iになる // func f(i *int)を呼び出す際、 f( // iは&iに変換される
- 値が要求される場合、参照には
*
がつく
// iへの代入時、 var v *int var i int = // vは*vになる // func f(i int)を呼び出す際、 f( // vは*vに変換される
- 可変長引数の場合に
...
が展開される
func fn(v ...int) {} func do(v []int) { fn( // vはv...になる }
- 引数の型がわかる場合はその型が補完される
func(...[]int)
を呼び出す際は[]int{}
が候補になるvar _ []int = make()
の第1引数は[]int
が候補になる
ただし、プレースホルダー*1やスニペット*2が有効になっていないと変換されなかったり、候補として表示されません。
VS Codeはデフォルトで有効になっているはずですが、Vimの場合はプラグインによっては有効になっていないかもしれません。
そしてシグネチャを補完・展開する際にはこれらが必須となっているため、もしうまく動かない時は設定を確認してみましょう。
他にも、次の設定で補完の動作を変えることができます。
initializationOptions.matcher
- デフォルトは
"fuzzy"
なので多少違っている候補も返ってきますが、 "caseSensitive"
や"caseInsensitive"
に変更できます
- デフォルトは
initializationOptions.completeUnimported
- デフォルトは
true
なので未importのパッケージからも候補が返ってきますが、 false
で無効化できます
- デフォルトは
initializationOptions.deepCompletion
- デフォルトは
true
なので構造体のフィールドも候補として返ってきますが、 false
で無効化できます
- デフォルトは
// deepCompletion=trueだと type param struct{ i int } func fn(i int) {} func do(p param) { fn( // p.iが候補として表示される }
それ以外の特殊な動作としては、
if err != nil
のスニペット展開error
を返す関数内のみ
- 公開される
var
のgodoc補完- 次のバージョンでは
const
やfunc
、type
も対象になる
- 次のバージョンでは
があります。
ジャンプ系
自分の場合は補完よりこちらを多用しています。
ただ、definition
とtypeDefinition
は違いがわからないまま使っていました。
textDocument/definition
カーソル位置にあるシンボルの定義元にジャンプする時に使用します。
関数内の変数はその関数内で定義(宣言)された場所にジャンプします。
type myType struct{} func fn() { t := myType{} // ← t.method() // tが宣言されたのは1行上 }
コマンドラインからはgopls query definitions
で使えます。
※次のバージョンからquery
が不要になる
gopls query definition internal/lsp/definition.go:14:67
textDocument/typeDefinition
カーソル位置にあるシンボルの型定義にジャンプする時に使用します。
関数内の変数は実際の型のある場所にジャンプします。
type myType struct{} // ← func fn() { t := myType{} t.method() // tの型定義はfnの上にあるmyType }
textDocument/implementation
カーソル位置にあるシンボルの、
- インタフェースから型
- 型からインタフェース
にジャンプする時に使用します。
コマンドラインからはgopls implementation
で使えます。
gopls implementation internal/lsp/implementation.go:14:10
textDocument/references
カーソル位置にあるシンボルが参照されている(使われている)場所にジャンプする時に使用します。
コマンドラインからはgopls references
で使えます。
gopls references internal/lsp/references.go:14:18
textDocument/documentSymbol
現在開いているファイルのシンボル一覧を取得します。
結果はジャンプするためや、ファイルのアウトライン表示のためにも使われます。
コマンドラインからはgopls symbols
で使えます。
※CLIでうまく動かない? → https://go-review.googlesource.com/c/tools/+/232557
workspace/symbol
プロジェクト(リポジトリ本体と依存パッケージ)からシンボル一覧を検索します。
※LSPの仕様では依存パッケージは含まれないはずですが、その方が便利なのでそういう実装になっています
今のところ結果は100件までに制限されています。
※一度に全部返してしまうとクライアントが高負荷で固まってしまうことがあるため
また、検索方法は補完と同じでinitializationOptions.matcher
に従います。
コマンドラインからはgopls workspace_symbol
で使えます。
gopls workspace_symbol WorkspaceSymbols
Vimからはtagfunc
経由でctags
のように使うこともできます。
設定例は下記参照。
daisuzu.hatenablog.com
その他
Diagnostic
以外はほとんど使っていませんでした。。。
しかし、今回調べたことでrename
がgorenameの代わりになるくらい完成度が高くなっていたり、SuggestedFix
の種類がだんだん増えていることがわかったのは大きな収穫でした。
Diagnostic
go vet
(golang.org/x/tools/go/analysis/passes/...
)やgofmt -s
、staticcheckなどのチェックを行います。
実行するチェッカーは以下のオプションでカスタマイズできます。
initializationOptions.analyses
initializationOptions.staticcheck
結果にSuggestedFixが含まれている場合、codeAction
経由で修正可能です。
SuggestedFix
の例:
return
の返り値の数があっていない時に追加したり、削除したり- 2回目以降の
:=
を=
に変更したり - 未定義変数の
<変数名> :=
を挿入したり
コマンドラインからはgopls check
で使えます。
gopls check internal/lsp/testdata/lsp/primarymod/analyzer/bad_test.go
textDocument/codeAction
SuggestedFix
の実行やimportの追加・削除(go.mod
への追加も含む)を行います。
コマンドラインからはgopls fix
やgopls imports
で使えます。
textDocument/codeLens
調べてもイマイチよくわかっていない機能ですが、現状は//go:generate
コメントのある行でgo generate
が実行できるよ、という情報を返してくれるようです。
また、go.modを開いている場合は依存パッケージのアップデートができるかどうかを教えてくれるようです。
textDocument/hover
カーソル位置のシグネチャや型などの情報を返してくれます。
textDocument/signatureHelp
カーソル位置のシグネチャやヘルプを返してくれます。
コマンドラインからはgopls signature
で使えます。
※<position>
の指定方法がわからず...は関数呼び出しの()
内の位置を指定する必要がある
gopls signature internal/lsp/signature_help.go:21:53
textDocument/documentHighlight
カーソル位置のシンボルが使われている場所をハイライトします。
コマンドラインからはgopls highlight
で使えます。
gopls highlight internal/lsp/highlight.go:17:2
textDocument/documentLink
現在開いているファイルがimportしているパッケージの https://pkg.go.dev へのリンクや、
<org>/<repo>#<number>
形式のコメントからGitHub Issuesへのリンクを返してくれます。
コマンドラインからはgopls links
で使えます。
gopls links internal/lsp/link.go
textDocument/formatting
現在開いているファイルを整形します。
コマンドラインからはgopls format
で使えます。
# -dでdiffを表示する gopls format -d internal/lsp/testdata/lsp/primarymod/format/bad_format.go.in
textDocument/rename
カーソル位置のシンボルをリネームします。
コマンドラインからはgopls rename
で使えます。
# -dでdiffを表示する gopls rename -d internal/lsp/rename.go:14:18 doRename
リネーム可能かどうかを調べるためにはtextDocument/prepareRenameで確認できます。
コマンドラインからはgopls prepare_rename
で使えます。
# rangeが表示されればリネーム可能
gopls prepare_rename internal/lsp/rename.go:14:18
textDocument/foldingRange
コードを折り畳む範囲を返してくれます。
コマンドラインからはgopls folding_ranges
で使えます。
gopls folding_ranges internal/lsp/folding_range.go
workspace/executeCommand
codeLens
の結果からgo generate
やgo get
の実行をしてくれるようです。
また、diagnostic
の結果からgo mod tidy
の実行をしてくれるようです。
未実装
以下のメソッドはまだ実装されていません。
GoでREST APIを呼ぶテストにhttp.FileServerを使う
例えばGET /users/:id
のようなAPIの場合、
testdata └── users └── 1
のように、testdata配下のusersディレクトリにidをファイル名としたJSONファイルを配置しておきます。
そうすると、
http.FileServer(http.Dir("testdata"))
で、testdata配下のパスとリクエストのパスが一致するファイルが
- 存在すれば
200 OK
でそのファイルの中身 - 存在しなければ
404 Not Found
を返すサーバが作れます。
http.Get(os.Getenv("API_URL") + "/users/" + strconv.FormatInt(id, 10))
のような呼び出し方をしていればhttptest
を使って次のようにテストが書けます。
package api import ( "net/http" "net/http/httptest" "os" "reflect" "testing" ) func Test_getUser(t *testing.T) { ts := httptest.NewServer(http.FileServer(http.Dir("testdata"))) defer ts.Close() if err := os.Setenv("API_URL", ts.URL); err != nil { t.Fatal(err) } defer os.Unsetenv("API_URL") type args struct { id int64 } tests := []struct { name string args args want *user wantErr bool }{ { name: "OK", args: args{id: 1}, want: &user{ID: 1, Name: "Alice"}, wantErr: false, }, { name: "NotFound", args: args{id: 2}, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getUser(tt.args.id) if (err != nil) != tt.wantErr { t.Errorf("getUser() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getUser() = %v, want %v", got, tt.want) } }) } }
POSTメソッドの場合、
- エンドポイントが
POST /users
のような形式になるのと、 201 Created
や500 Internal Server Error
が返せないため、
http.FileServerよりはhttp.HandlerFuncを使った方が良いでしょう。
また、リクエストパスとファイルが1対1にならない時も同様です。
packagestestのExported.Expectを使ってGoのソースからマーカーを収集する
golang.org/x/tools/go/packages/packagestestパッケージは静的解析など、Goのソースを読み込んで何かしらの処理を行うようなツールのテスト用にダミープロジェクトを作るためのパッケージです。
そういったツールを作る際、テストで期待する結果を完全に決め打ちで_test.go
に書いておいても良いかもしれませんが、パターンを増やす時にはダミーのソースとそれに対応する期待結果をそれぞれ追加していかなければいけないので、数が増えてくるとメンテナンスが大変になってしまいます。
そのため、ダミーのソースの中にマーカーを埋め込んでおいて、それを元に期待する結果をテスト時に組み立てるようにすると、パターンを追加する時の変更箇所が1つになり、またテストコード自体の見通しも良くなります。
具体例はgoplsのテストを見るのが良いですが、簡単にまとめると、
1) テスト関数の本体でpackagestest.TestAllを呼び、
func Test(t *testing.T) {
packagestest.TestAll(t, test)
}
2) test
の中でpackagestest.Exportを使ってダミープロジェクトをセットアップし、
// Moduleにはtestdataにあるモジュールを指定しても良い exported := packagestest.Export(t, e, []packagestest.Module{ { Name: "example.com/pkg", // モジュール名 Files: map[string]interface{}{ "foo/foo.go": `package foo const Foo = 100 //@Foo, aaa(Foo, "const") `, "bar/bar.go": `package bar import "example.com/pkg/foo" const Bar = foo.Foo * 10 //@bbb("Bar", Foo) `, }, }, }) defer exported.Cleanup()
3) その戻り値のExpectメソッドでマーカーを収集する、
if err := exported.Expect(map[string]interface{}{ // @aaa()用 "aaa": func(pos token.Pos, arg string) { t.Logf("pos = %v, arg = %v", pos, arg) }, // @bbb()用 "bbb": func(pos token.Pos, arg token.Pos) { t.Logf("pos = %v, arg = %v", pos, arg) }, }); err != nil { t.Fatal(err) }
という流れになります。
あとは収集したマーカーを使って期待する結果を作ってあげればOKです。
ただExpect
メソッドがどうやって位置に変換しているのか、少しわかりにくかったのでメモを残しておきます。
- マーカーの文字列から
token.Pos
に変換する場合
// OK: ↓をそのまま文字列にする const Foo = 100 //@aaa("Foo", "const") // NG: 同じ行にない場合はエラー //@aaa("Foo", "const") const Foo = 100
- マーカーの識別子から
token.Pos
に変換する場合
// OK: ↓識別子をマーカーにしておく const Foo = 100 //@Foo, aaa(Foo, "const") // どこかにマーカーがあれば別の場所でも使える const Bar = foo.Foo * 10 //@bbb("Bar", Foo) // NG: Barはマーカーになっていないのでエラー const Bar = foo.Foo * 10 //@bbb(Bar, Foo)
// NG: 識別子にドットは使えないのでfoo.Fooにはできない const Bar = foo.Foo * 10 //@bbb("Bar", foo.Foo) // OK: かわりにmarkで識別子の名前を"fooFoo"にしておくと、 const Foo = 100 //@mark("fooFoo", Foo) // 別の場所でfooFooが使えるようになる const Bar = foo.Foo * 10 //@bbb("Bar", fooFoo)
- 別のマーカーを流用して
token.Pos
に変換する場合
// ↓を type A string //@type("AString", "A") // ↓で識別子として使いたい type Alias = A //@bbb("Alias", AString)
Expect
でbbb
が呼ばれる前に、type
をMarkするExpect
を呼んでおく。
if err := exported.Expect(map[string]interface{}{ "type": func(name string, r packagestest.Range, _ []string) { exported.Mark(name, r) }, }); err != nil { t.Fatal(err) }
もし需要がありそうならゴリラ.Goのネタにするかもしれません。
goplsでworkspace/symbolが使いたい
現状の最新版(gopls/v0.2.2-pre2)では未実装。
を書いていたら欲しくなったので第1回 ゴリラ合宿で実装したのがコチラ。
symbolの探索はServerのsessionのViews()から
Snapshotを取ってきて、
KnownPackages()のCompiledGoFiles()をParse()
して*ast.Fileを引っ張り出して、
あとは地道にWorkspaceSymbolParamsにマッチするものを探していくようにした。
ただ、WorkspaceSymbolParams
の仕様*1がよくわかっておらず、実装の仕方も上記のやり方で良かったのか全然わかっていない。
また、最初は素直にスライスを作るように実装してみたところ、Vimから:tselect
などを実行すると
- タグが重複したり、
- 実行するたびに順番が変わったり、
するので、
重複を排除した上で、Vimで指定した文字列に近く、現在のパッケージに近いものを優先するようにしてみた。
とりあえず自分用としては使えそうなので色々と試してみることにする。
*1:特にQuery
VimのtagfuncでLSPを使う
この記事はVim Advent Calendar 2019の6日目の記事です。
今年の4月にv8.1.1228でtagfuncという機能が追加されました。
こちらは:tag
や:tselect
などのタグ系コマンド*1を実行した時、tagsファイルを検索する代わりに呼ばれる関数を設定するためのオプションです。
設定する関数の形式としては次のようなものです。
" pattern: タグ検索中に使用されたタグ識別子 " flags: 関数の挙動を制御するためのフラグのリスト " 'c' -> ノーマルモードのコマンドで呼び出された " 'i' -> インサートモードのタグ補完で呼び出された " info: 以下の情報を持つ辞書 " { " 'buf_ffname': 'フルファイル名', " 'user_data': 'カスタムデータ文字列', " } function! MyTagFunc(pattern, flags, info) " タグ情報のリストを返す " もしくはv:nullを返すとtagsファイルが使われる return [ \ { \ 'name': 'タグ名', \ 'filename': 'タグが定義されているファイル名', \ 'cmd': 'ファイル内のタグを見つけるためのExコマンド', \ 'kind': 'タグの種類(Optional)', \ 'user_data': 'カスタムデータ文字列(Optional)', \ }, \] endfunction
ヘルプにはtaglist()
の結果を並べ替える関数が例として記載されていますが、LSPを使ってタグ情報のリストを生成できればタグジャンプがすごく便利になりそうです。
VimConf 2019ではsettagstack()
を使ってタグスタックを直接操作する方法を紹介しましたが、こちらは、
- タグ系のコマンドを使うために、
- キーマップの変更が必須
- Exコマンドはユーザ定義コマンドを定義する必要がある
- Vim scriptでタグスタックの管理をしなければならない
といったものでした。
これに対し、tagfunc
はオプションを設定するだけで、
- 標準のキーマップ、Exコマンドがそのまま使える
- タグ補完の時にも使える
- タグスタックの管理をVim本体に任せられる
と、すごく良さそうです。
試しに任意の文字列で検索するworkspace/symbolをvim-lspから呼び出してみます。
function! MyTagFunc(pattern, flags, info) abort let l:servers = filter(lsp#get_whitelisted_servers(), \ 'lsp#capabilities#has_workspace_symbol_provider(v:val)') if len(l:servers) == 0 echoerr 'not supported: workspace/symbol' return [] endif " タグ補完の場合はa:patternの先頭に \< がつくので削除する " (サーバ側が正規表現に対応していないかもしれないので...) let l:query = a:flags =~# 'i' ? \ substitute(a:pattern, '^\\<', '', '') : a:pattern let l:ctx = {'result': []} for l:server in l:servers call lsp#send_request(l:server, { \ 'method': 'workspace/symbol', \ 'params': { \ 'query': l:query, \ }, \ 'sync': 1, \ 'on_notification': function('s:make_taglist', [l:ctx]), \ }) endfor return l:ctx.result endfunction " ctx.resultにタグ情報を追加する func s:make_taglist(ctx, data) abort for result in a:data.response.result call add(a:ctx.result, { \ 'name': result.name, \ 'filename': lsp#utils#uri_to_path(result.location.uri), \ 'cmd': string(result.location.range.start.line + 1), \ }) endfor endfunction
上記関数をset tagfunc=MyTagFunc
で設定すると、ctagsとほぼ同じ使い勝手のタグジャンプができるようになります。
ただ、workspace/symbol
はtextDocument/definitionなどのようなカーソル位置を起点に対象を探すメソッドとは異なり、100%目当ての場所にジャンプできるとは限りません。
確実にジャンプしたい時はtextDocument/definition
を呼べるような実装にできれば良いのですが、MyTagFunc
の引数からだと、それがExコマンドの引数に指定したタグ名なのか、カーソル位置のキーワードなのかを判別できないので、メソッドの呼び分けをするのは非常に困難です。
そのため、tagfunc
に設定する関数で全部をやるのではなく、
CTRL-]
とCTRL-W ]
はtextDocument/definition
*2を呼ぶ- 定義元にジャンプしたい
- それ以外は
tagfunc
からworkspace/symbol
を呼ぶ- 同名の別メソッド/関数やインタフェースの実体などを探したい
というように、用途に応じて使い分けるのがオススメです!
結局settagstack()
とtagfunc
を両方使うことに。。。
[2019/12/06 17:00追記]
flags
で判別可能でした。
@presukuさん、ありがとうございます!
MyTagFuncのflagsで呼び分けできるような?
— presuku (@presuku) 2019年12月6日
C-]とかだと c で :tag aaa みたいなコマンドだと空だった。そういう事じゃないのかな… tagfuncでなるべく完結したいなー。
tagfunc
だけで完結させるにはMyTagFunc
を次のようにします。
function! MyTagFunc(pattern, flags, info) abort let l:ctx = {'result': []} if a:flags ==# 'c' " ノーマルモード(CTRL-]など)の場合はカーソル位置の定義に飛ぶ let l:method = 'textDocument/definition' let l:servers = filter(lsp#get_whitelisted_servers(), \ 'lsp#capabilities#has_definition_provider(v:val)') let l:ctx.pattern = a:pattern let l:params = { \ 'textDocument': lsp#get_text_document_identifier(), \ 'position': lsp#get_position(), \ } else " Exコマンド(:tag {name}など)やタグ補完の場合はa:patternで探す let l:method= 'workspace/symbol' let l:servers = filter(lsp#get_whitelisted_servers(), \ 'lsp#capabilities#has_workspace_symbol_provider(v:val)') " タグ補完の場合はa:patternの先頭に \< がつくので削除する " (サーバ側が正規表現に対応していないかもしれないので...) let l:query = a:flags =~# 'i' ? \ substitute(a:pattern, '^\\<', '', '') : a:pattern let l:params = { \ 'query': l:query, \ } endif if len(l:servers) == 0 echoerr 'not supported: ' . l:method return [] endif for l:server in l:servers call lsp#send_request(l:server, { \ 'method': l:method, \ 'params': l:params, \ 'sync': 1, \ 'on_notification': function('s:make_taglist', [l:ctx, l:method]), \ }) endfor return l:ctx.result endfunction " ctx.resultにタグ情報を追加する func s:make_taglist(ctx, method, data) abort for result in a:data.response.result if a:method ==# 'workspace/symbol' let l:name = result.name let l:location = result.location else let l:name = a:ctx.pattern let l:location = result endif call add(a:ctx.result, { \ 'name': l:name, \ 'filename': lsp#utils#uri_to_path(l:location.uri), \ 'cmd': string(l:location.range.start.line + 1), \ }) endfor endfunction
ただ一番利用頻度の高いgoplsがworkspace/symbol
に対応していないのでまだ常用できていません。。。
VimConf 2019に行ってきた
今年もVimConf 2019に行ってきました。
今回はテーマが “how to be more productive with Vim?”
ということもあったのか、比較的Vim暦が浅い人の登壇が多かったような気もしますが、その割には発表内容が素晴らしいものばかりだったので、やっぱりVimを使う人は(良い意味で)ヤバい人ばっかりだなと思いました。
毎年のことですが、運営のみなさま大変お疲れ様でした。本当にありがとうございます。
おかげでとても楽しく過ごすことができました。
発表メモ
Vim Renaissance by Prabir Shrestha
愛用しているvim-lspの作者によるキーノート。
色々なエディタ・IDE・プログラミング言語があった状況からLSPが登場し、そして未来への革命へ、的な。
乱立していると辛いとか、選択肢を広げるようにしているところとか、わかる部分もあれば自分の英語力だとちゃんと理解できているのか不安になるところもあったり...
多くの人が使えるようにするのは大事なことですけど、そこにもトレードオフがあったりするので難しいところですよね。
We can have nice things by Justin M. Keyes
2つ目のセッションはneovim作者によるキーノート。
neovimはあんまり使っていないんですが、その思想とか実装を直接聞くことができてすごく興味深かったです。
UI周りに関する作り込みとかもそうですけど、特に 「Legacy paradox」 の「表面化にある価値のあるモノ」についてはなるほどなーと思いました。
Your Vim is Only for You by mopp
午後の最初のセッション。
自分のVimを改善していくフローは今までそうしてきたことも多々あるし、今後もそうありたいと思っていることなのでとても共感できました。
Grown up from Vim User to Vim plugin developer side by IK
Vimを使い始めてからvim-jpに参加するまでの流れ。
課題を自分が直すしかない!みたいなところは思い当たることがあったり...
最後の「The next OSS contributer is YOU!!!」はとてもやる気が湧き起こる言葉ですね。
Usage and manipulation of the tag stack by daisuzu
こちらを発表してきました。 speakerdeck.com
make test by m-nishi
Vim本体のテストについて。
ターミナルが小さくてfailするのはやったことありますw
自分が以前blogに書いたことを見てくれていたりして、そういったことがこの発表に繋がっているのかなと思うとなんだか嬉しいです。
My Vim life by gorilla0513
1年前にVimを使い初めてやってきたことの軌跡。
ゴリラ.vimとか本とか、近くで関わっていて良く知ってる事もありますけど、ゴリラさんの情熱と行動力は本当にスゴいし尊敬しています!
Using Vim at Work! by Danish Prakash
心理学 + Vimといったとても興味深い発表でした。
UIについては(自分が思うように使えれば良いや、とか思ったりして)あんまり深く考えなかったりもしたけど、
- キーボードよりマウスの方が効率的な事がある
- テキストよりもビジュアル情報の方が6万倍速い
と言われると確かになーとなりました。
Let's Play with Vanilla Vim by Hezby Muhammad
プラグインを使わないVanilla Vimの話。
Vimを使いこなすと基本機能だけでもなんとかなってしまう事も多々あるんですよね。
:find
とか補完とかタグジャンプとか。
1つ前の発表でも少し出てきましたが、まさか他の発表でタグジャンプがあるとは思っていませんでした。
そして :normal
で I
とか A
とか使うやり方はやった事なかったです。
13 Vim plugins I use every day by Tatsuhiro Ujihisa
毎日使っているプラグインの紹介。
ライブコーディングでサーバをパパッと作っていましたが、流れるようなVim捌きが本当に凄かったです!
My dark plugins development history ~ over 10 years ~ by Shougo
shougo wareの開発の歴史。
最近は使わなくなってしまいましたが...第二世代くらいまではすごくお世話になっていました!
今回で最後の登壇になるかもしれないというのがちょっと信じられません。
来年でなくてもまたいつか発表していただければなと思います。