Vimのプラグイン作成に慣れていなくても手を動かしてみよう

#Development#Vim

この記事は Vim Advent Calendar 2020 - Qiita の4日目の記事です。

昨日は id:daisuzu さんの編集を加速するVimのquickfix機能でした。

こんにちは、こんばんは。久しぶりにVimに関連する記事を書いています。

最後に書いたVimの記事は4年前のVim をあまりカスタマイズしなくなった話なのですが、この記事中にも下記のように最後にVimの記事を書いたのが4年前ということが判明しました。

ちなみに、 ctrlp の記事を4年前の Vim Advent Calendar で書いています。

どうやら、次にVimの記事を書くのは2024年になる可能性が高いです。

さて、最近になってやっと社内でVimが盛り上がるようになってきました(ただし、二人のみ)。Vim については上記の記事でも書いた通り、カスタマイズすることがあまり無いのですが、自分で欲しいと思った機能はささっと .vimrc に検証レベルのPoCコードを書いています。

検証レベルが終わればちゃんとプラグインとして整備するのですが、それの前段階で軽く書いているVim scriptです。

例えば、VimでGoの実装をしているときに https://golang.org/pkg/ のページでドキュメントを見に行くのを省略するのに、 :GoDoc [pkg-path] で開くようにしています。

Vim GoDoc

今回はこちらの紹介をしようと思います。

GoDocコマンドを作成する

:GoDoc コマンドは fatih/vim-go に元々入っていたコマンドだった気がします。そこからGoの開発環境をシンプルにしようと思い、 vim-go を外した際になくなった機能だったのをササっと作った記憶が残っています。

作りは非常にシンプルになっています。そのコードを紹介します。

GoDoc関数

GoDoc コマンドの呼び出し先になる関数を定義します。

function s:GoDoc(bang, args)
  let arg = join(split(a:args), '.')
  silent! let res = system('go doc -cmd -all '.arg.' 2>/dev/null')
  if v:shell_error != 0
    let err = systemlist('go doc '.arg.' 1>/dev/null')
    echohl ErrorMsg | echomsg err | echohl None
    return
  endif
  call s:OpenReadOnlyBuffer('' , arg, res)
  endif
  setlocal ft=godoc
  nnoremap <buffer> <silent> q :q<cr>
endfunction

処理のメインとなるVim scriptは下記のシステムコマンドを叩いているところです。

silent! let res = system('go doc -cmd -all '.arg.' 2>/dev/null')

go doc コマンドはパッケージ名を指定して、ドキュメントを出力することができます。デフォルトだと関数の詳細な説明などが省かれているので -all をオプションに指定しています。-cmd はコマンドのパッケージも出力するという指定です。

パッケージの指定とありますが、指定方法は go doc 通りなので他にも指定方法があります。

Usage of [go] doc:
	go doc
	go doc <pkg>
	go doc <sym>[.<methodOrField>]
	go doc [<pkg>.]<sym>[.<methodOrField>]
	go doc [<pkg>.][<sym>.]<methodOrField>
	go doc <pkg> <sym>[.<methodOrField>]

godoc net/http.Get

OpenReadOnlyBuffer関数

これは GoDoc 関数で得られた go doc の結果を展開するバッファを指定しています。

function s:OpenReadOnly(target, name, body)
  execute a:target.' '.a:name
  setlocal buflisted modifiable modified noreadonly
  cal append(line('^'), split(a:body, '\n'))
  silent! $d
  setlocal noswapfile nobuflisted buftype=nofile bufhidden=unload
  setlocal nomodifiable nomodified readonly
  setlocal nonumber nobinary nolist
  execute 'normal! 1G'
endfunction

function s:OpenReadOnlyTab(type, name, body)
  cal s:OpenReadOnly('tabnew!', a:name, a:body)
endfunction

function s:OpenReadOnlyBuffer(type, name, body)
  cal s:OpenReadOnly(a:type =~# '^\(v\|vert\)' ? 'vsplit!' : 'split!', a:name, a:body)
endfunction

バッファやタブとかも実は用意していますが、基本的にはバッファに展開をしています。

GoList

GoList で候補となるパッケージを取得するようにします。 go list ではなくて、単純に $GOROOT から関連するパッケージ名を取得していて、力技を感じますね。

function s:GoList()
  return systemlist('{ cd $(go env GOROOT)/src && find . -type d } | sed -e "s#^\./##" | grep -v "^\(\.\|vendor\)"')
endfunction
command GoList cal s:GoList()

これも目を瞑りましょう。

コマンド定義

実行関数として GoDoc を定義し、補完候補として GoList を少し改良して定義しています。

function! s:CompleteGoDoc(arg_lead, cmdline, cursor_pos)
    return filter(copy(s:GoList()), 'stridx(v:val, a:arg_lead)==0')
endfunction
command -nargs=1 -bang -complete=customlist,s:CompleteGoDoc GoDoc cal s:GoDoc('<bang>', <f-args>)

これで :GoDoc net/http のように Vim でGoのドキュメントを参照することができます。

CtrlP extention

CtrlPをずっと使い続けているので、CtrlPでも呼び出せるようにしています。

" CtrlPGoDoc
command! CtrlPGoDoc cal ctrlp#init(ctrlp#godoc#id())
nnoremap <c-e>g :<c-u>CtrlPGoDoc<cr>

ただ、これ今見てみるとかなり適当に作っているのに気づきました。。 fu! ctrlp#godoc#id が定義されていないので、他のCtrlP extentionが影響受けているはずです。良くない。

しっかりとしたCtrlPのExtentionの作り方は8年前に記事を書いているのでよければ参照してください。

おわりに

最初は「バイナリエディタとしてVimを使う」という記事を書こうとしたのですが、念のため調べてみたら同じような記事がたくさんあったので、小ネタにしていた GoDoc コマンドをそのまま本ネタにしました。

プラグインとして作るのも面倒だな、とかあまりプラグインを書くことになれていない人も、躊躇わないで .vimrc の中に書いてみる良い例ではないでしょうか。

人によっては開発中のものを packpath で上手く指定したところを読み込んでいる人もいると思いますが、初心者の方へは、まずは自分でVim scriptで遊んでみるのをおすすめします。

とりあえず、本記事で書いたものは自分用に早いうちにプラグイン化と共にリファクタリングをして整理しようと思います。今更ながら恥ずかしい!

それではまた4年後にVimの記事を書きます。


ちなみに、バイナリエディタのTipsを置いておきますね。

コマンド関連:

$ vim -b a.out # open a.out running on Binary Mode
# Make a hexdump
:%!xxd

# The reverse
:%!xxd -r

ちなみに、ターミナルで閲覧したいだけなら xxd コマンドを使えばOK。

Animated GIF-downsized large (2)

B!

Shintaro Kaneko

December 04, 2020