勝手にvital.vim API Reference

※追記:この記事には古い内容が含まれます。最新版ではこの通りに動かない可能性がありますのでご注意ください。

前置き

この記事は Vim Advent Calendar 2011 : ATND 39日目の記事です。
前日は@wiredoolさんのMercurialなVimライフ - まる-おぶ-ざ-でいでした。
今日はvital.vimというプラグインの紹介をします。

概要

vital.vimとはVim scriptを効率的に書くための汎用ライブラリ(プラグインのプラグイン)です。
Vim scriptの標準APIだと物足りない」「他の言語ならこう書けるのに」といった不満を解消します。
プラグイン開発者だけでなく、自作オレオレスクリプトを書いている人にも有用なプラグインだと思います。
たぶん。
どういったものがあるかは後述するAPIをご参照ください。

前から気にはなっていたのですが、help含めて導入向けの情報が少ないので手を出さずにいました。
執筆時点(2012/1/8)では、vital.vimの参考情報が以下記事しかないように思えます。
vital.vimを使っている情報はいくつかありますが、導入向けという意味で。

vim-jp/vital.vim · GitHub
vital.vim 開発中 - 永遠に未完成

そういった背景もあり、今回のネタとするためにぼくが分かる範囲で調べてみました。

ぼくの環境はMac OS X 10.7です。
vital.vim導入に関しては環境による違いはないと思いますが、
以下に載せる端末のコマンドは各自の環境にて読み替えてください。

導入

  • プラグインに組み込む場合

まずは組み込み対象となるプラグインディレクトリを作ります。
今回の例では何も処理はしないので適当にhelloとでもしておきます。

/Users/kanno% mkdir -p .vim/after/hello

次にGithubにて公開されているvital.vimのソースを取得します。

/Users/kanno% git clone git://github.com/ujihisa/vital.vim.git

Vundleなどでプラグイン管理されている方はそちらでダウンロードしてもよいでしょう。
手順に違いはありません。
私はVundleを使っていますので、.vimrcに以下の設定を書いてBundleInstallで取得できます。

Bundle 'ujihisa/vital.vim'

※追記:最新版ではvitalize.rbはなくなっているそうです。
vital.vim 近況 - 永遠に未完成
落としてきたソースにはvitalize.rbというrubyファイルがありますのでこれを実行します。
もちろん実行にはrubyが必要です。
コマンドは「ruby vitalize.rb {vital.vimのディレクトリ} {組み込み対象のプラグインディレクトリ}」です。

/Users/kanno% ruby .vim/bundle/vital.vim/vitalize.rb .vim/bundle/vital.vim .vim/after/hello
78377c
/Users/kanno% 

バージョン番号が出力されています。
ぼくが上記を書いた時から更新されているため、最新では異なるバージョン番号が出力されるはずです。
プラグインフォルダにvital.vimのディレクトリが作成されていれば成功です。

/Users/kanno% tree .vim/after/hello
.vim/after/hello
└── autoload
    ├── vital
    │   ├── _78377c
    │   │   ├── data
    │   │   │   ├── list.vim
    │   │   │   ├── ordered_set.vim
    │   │   │   └── string.vim
    │   │   ├── functor.vim
    │   │   ├── locale
    │   │   │   └── message.vim
    │   │   ├── mapping.vim
    │   │   ├── prelude.vim
    │   │   ├── system
    │   │   │   ├── cache.vim
    │   │   │   ├── file.vim
    │   │   │   └── filepath.vim
    │   │   └── web
    │   │       ├── html.vim
    │   │       ├── http.vim
    │   │       ├── json.vim
    │   │       └── xml.vim
    │   ├── _78377c.vim
    │   └── hello.vital
    └── vital.vim
7 directories, 17 files
/Users/kanno% 

これにより自分のプラグインをGithubなどで公開した場合でも、
必要なvital.vimモジュールが同梱されることになります。
プラグイン使用者が別途vital.vimをインストールしなくても使えるようになっているわけですね。
すばらしいです。

  • プラグインに組み込まない場合

.vimrc内で使いたい場合や、サンプルスクリプト内でとりあえず使ってみたい場合などです。
rubyがなくても動きます。
rubyを入れる必要があるからプラグインに組み込むのは億劫」という人などは、
ひとまずこちらで試してみるといいかもしれません。

Githubにて公開されているvital.vimのソースを取得します。
落としてきたソースは他プラグインと同様にruntimepath(rtp)が通っている場所に置いてください。
私はVundleを使っていますのでそちらを利用しました。
「プラグインを組み込む場合」を参照してください。

使い方

まずはvital#of('プラグイン名')として初期化します。

" 組み込みの場合は対象のプラグイン名を指定します
let V = vital#of('hello')

" 組み込みでない場合は'vital'と指定します
" この場合は対象プラグインがruntimepathに設定されていなければエラーとなります
let V = vital#of('vital')

vital.vimの直接的な使い方とは関係ありませんが、
実際はlet Vではなくlet s:Vというようにスクリプトローカルな変数に設定する方がいいと思います。
もちろん変数名はVでなくても構いません。

こうしてvitalオブジェクトを取得したらば、そのインスタンスから必要なモジュールを取得します。
取得にはimportメソッドを用います。引数にはインポート対象のモジュール名を指定します。
以下の例ではDataパッケージ(?)のOrderedSetモジュールを取得しています。

let s:V = vital#of('vital')
let s:O = s:V.import('Data.OrderedSet')
let s:set = s:O.new()

インスタンス化して使用するものであればnew()を呼び出してから使用できます。
staticに使用できるものであればその必要はありません。
詳しくは後述のAPIを参照してください。

API の説明に入る前の注意 - quickrun.vimを使う場合

プログラマのお供といえばquickrun.vimですが、今回vital.vimを試す上でちょっと良く分からない挙動になりました。
以下のコードを実行してみます。

" vitalモジュールを読み込むけど何もしないで1だけ出力
let s:V = vital#of('vital')
let s:M = s:V.import('Data.OrderedSet')
echo 1

1の表示だけを期待するのですが、quickrunのバッファには違うものが出力されます。

1: /Applications/MacVim.app/Contents/Resources/vim/vimrc
2: /Applications/MacVim.app/Contents/Resources/vim/plugins/kaoriya/encode_japan.vim
3: /Applications/MacVim.app/Contents/Resources/vim/plugins/kaoriya/autoload/kaoriya/switch.vim
4: /Applications/MacVim.app/Contents/Resources/vim/runtime/vimrc_example.vim
5: /Applications/MacVim.app/Contents/Resources/vim/runtime/syntax/syntax.vim
…中略…
function 142_Util_neighbor_match(context, lnum, pattern, ...)
function 8_SQL()
function 103_set_default(var, val)
1

なんか色々と大量に出力されます。vital#of('vital')の実行だけで出ます。
今回そこを調べるのは横道になるので早々に調査を止めて次のように対応しました。
1.まず読み込み実行

let s:V = vital#of('vital')
let s:M = s:V.import('Data.OrderedSet')

2.読み込み部分をコメントアウト(Vimが生きてれば変数は残っているので)

" let s:V = vital#of('vital')
" let s:M = s:V.import('Data.OrderedSet')

3.処理を書く

" let s:V = vital#of('vital')
" let s:M = s:V.import('Data.OrderedSet')
echo s:M.xxx()

ということを一応載せておきます。
こういうことを書いておけば誰かが調査/解決してくれるはず

API

※必ずvital.vimの最新版を使うようにしましょう。古いバージョンを使っていてバグを招いたとしても自己責任でお願いします。

執筆時点(2012/1/8)で提供されているAPIについて紹介します。
vital.vim/autoload/vital/__latest__/配下の各ファイルを対象として調べました。

また、各APIの説明はソースと実動作からぼくが勝手に解釈して書いているものです。
間違いが含まれていたとしてもvital.vim側には何一つ責任はありません。
くれぐれもvital.vim作者に「挙動が違うんだけど」的な物言いをしないようにお願いします。

結構多いので、以下は流し読みしないと疲れます。
モジュールのメソッドはアルファベット順に並んでいます。

以下サンプルを読むにあたっての前提

以降のサンプルは全て次のコードが実行されている前提とします

let s:V = vital#of('vital')
let s:M = s:V.import('モジュール名')
" 例:let s:M = s:V.import('Data.List')

" echo に対してインラインでコメントを書きたかったので関数化しただけ
function! s:echo(msg)
  echo a:msg
endfunction

Data.List モジュール - list構造に対する操作を提供する

  • break(f, xs) - リスト xs を先頭から順に見ていき、f を満たさない要素とそれよりも後ろにある要素のリストに分割する
    • Data.List.span()と反対の動きになる
    • f はeval()実行できる文字列。文字列中にv:valと書くとそれが各要素に置き換えられる
call s:echo(s:M.break('v:val > 3', [1, 2, 3, 4, 5])) " [[1, 2, 3], [4, 5]]
call s:echo(s:M.break('v:val < 3', [1, 2, 3, 4, 5])) " [[], [1, 2, 3, 4, 5]]
  • char_range(from, to) - 文字 from と文字 to の範囲内の文字列を返す
call s:echo(s:M.char_range('b', 'd')) " ['b', 'c', 'd']
call s:echo(s:M.char_range('い', 'え')) " ['い', 'ぅ', 'う', 'ぇ', 'え'] ※マルチバイトを扱う場合は気をつけよう
call s:echo(s:M.char_range('き', 'け')) " ['き', 'ぎ', 'く', 'ぐ', 'け'] ※マルチ(ry(そもそもcharが対象だし)
call s:echo(s:M.char_range(2, 4)) " ['2', '3', '4']
  • concat(list) - 多重リスト list が持つリスト要素を結合して新しいリストを返す
call s:echo(s:M.concat([[1, 2, 3], [4, 5]])) " [1, 2, 3, 4, 5]
call s:echo(s:M.concat([1, 2, 3, 4, 5])) " エラー。多重リストでないとNG?
  • flatten(list) - 多重リスト list の要素を平坦化して新しいリストを返す
call s:echo(s:M.flatten([[1, 2, 3], [4, 5]])) " [1, 2, 3, 4, 5]
call s:echo(s:M.flatten([1, 2, 3, 4, 5])) " [1, 2, 3, 4, 5] ※s:concatと異なりエラーにならない
  • foldl(f, init, xs) - リスト xs の各要素に対して順に f を適用する。init は初期値となりメモ化される
    • Haskellのfoldl、Rubyのinject、Pythonのreduce相当のもの
    • f 文字列内にて各要素をv:val、メモ化された変数へはv:memoでアクセスできる
    • f はeval可能な文字列かFuncrefの必要がある
call s:echo(s:M.foldl('v:val - v:memo', 0, [2, 3])) " 1
call s:echo(s:M.foldl('add(v:memo, v:memo[v:val] + v:memo[v:val + 1])', [1, 1], range(10))) " [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] ※フィボナッチ
  • foldl1(f, xs) - リスト xs の先頭の要素を初期値 init とし、残りの要素のリストと共にData.List.foldl()に委譲する
call s:echo(s:M.foldl1('v:val - v:memo', [0, 2, 3])) " -1
call s:echo(s:M.foldl1('add(v:memo, v:memo[v:val] + v:memo[v:val + 1])', [[1, 1], 0, 1, 2, 3])) " [1, 1, 2, 3, 5, 8]
  • foldr(f, init, xs) - Data.List.foldl()と適用順が逆
call s:echo(s:M.foldr('v:val - v:memo', 0, [2, 3])) " -1
  • foldr1(f, xs) - Data.List.foldl1()と適用順が逆
call s:echo(s:M.foldr1('v:val - v:memo', [2, 3, 0])) " -1
  • has(list, Value) - リスト list に valueが存在すれば1、存在しなければ0を返す
call s:echo(s:M.has(['a', 'b', 'c'], 'a')) " 1
call s:echo(s:M.has(['a', 'b', 'c'], 'A')) " 0
  • has_index(list, index) - リスト list の添字 index に要素がある場合は1、ない場合は0を返す
    • index は0始まりである
    • index は正数しか認められない。負数の場合は0が返る
call s:echo(s:M.has_index([1, 1, 1], 2)) " 1  
call s:echo(s:M.has_index([1, 1, 1], -2)) " 0  
  • sort(list, expr) - リスト list を expr の結果に沿って並び替える
    • expr はeval可能な文字列かFuncrefの必要がある
    • 文字列の場合はa:aやa:bとして比較要素を取り扱う
call s:echo(s:M.sort([1, 4, 2, 5, 3], 'a:a > a:b')) " [1, 2, 3, 4, 5]
call s:echo(s:M.sort([1, 4, 2, 5, 3], 'a:a < a:b')) " [5, 4, 3, 2, 1]

" ※比較関数自体がスクリプトローカルだと実行時に見つからなくてエラーになるので注意
" ※スクリプトローカル関数のまま渡したい場合はFunctor.localfunc()を参照
function! Asc(a, b)
  return a:a > a:b
endfunction
call s:echo(s:M.sort([1, 4, 2, 5, 3], function('Asc'))) " [1, 2, 3, 4, 5]
  • sort_by(list, expr) - リスト list の各要素に expr を適用した結果でソートを行う
    • Rubyのsort_by相当のもの
    • 予め expr を適用したリストを用意することで計算量を減らすことができる
    • expr は式を表す文字列(FuncrefはNG)でありv:valによって現在の要素にアクセスできる
call s:echo(s:M.sort_by(['b', 'a', 'd', 'e', 'c'], 'v:val')) " ['a', 'b', 'c', 'd', 'e']
call s:echo(s:M.sort_by(['aa', 'a', 'aaaa', 'aaaaa', 'aaa'], 'strlen(v:val)')) " ['a', 'aa', 'aaa', 'aaaa', 'aaaaa']
call s:echo(s:M.sort_by([[2,3], [1,2], [2,2], [5,1]], 'v:val[0] * v:val[1]')) " [[1, 2], [2, 2], [5, 1], [2, 3]]
  • span(f, xs) - リスト xs の先頭から順に見ていき、f を満たす要素とそれよりも後ろにある要素のリストに分割する
    • Data.List.break()と反対の動きになる
    • f はeval()実行できる文字列。文字列中にv:valと書くとそれが各要素に置き換えられる
call s:echo(s:M.span('v:val > 3', [1, 2, 3, 4, 5])) " [[], [1, 2, 3, 4, 5]]
call s:echo(s:M.span('v:val < 3', [1, 2, 3, 4, 5])) " [[1, 2], [3, 4, 5]]
  • uniq(list, ...) - リスト list 内の重複を排除して新しいリストを返す。
    • 第二引数に評価式を与えると、各要素に適用した値によって重複判定が行われる
call s:echo(s:M.uniq([1, 2, 3, 2, 5])) " [1, 2, 3, 5]
call s:echo(s:M.uniq(['a', 'A', 'b', 'c', 'b'])) " ['a', 'A', 'b', 'c']
" ※辞書の値による重複判定
call s:echo(s:M.uniq([{'key': 1}, {'key':2}, {'key':1}, {'key':3}], 'v:val.key')) " [{'a': 1}, {'a': 2}, {'a': 3}]

Data.OrderedSet モジュール - is Collection Utilities Library.(添付のhelpより抜粋)

  • help(vital-data-ordered_set)があるのでそちらを参照
    • ちょっとだけ書くとこんな感じ
let s:set = s:M.new()
call s:set.push('a')
call s:set.push('b')
call s:set.push('b')

call s:echo(s:set.to_list()) " ['a', 'b']

Data.String モジュール - 文字列に対する操作を提供する

  • chop(str) - 文字列 str の最後の一文字を削除する
call s:echo(s:M.chop('Hello')) " Hell
call s:echo(s:M.chop('へろー')) " へろ
call s:echo(s:M.chop("Hello\n")) " Hello
  • nr2byte(nr) - 文字コードをよく知らない人が適当に数字を与えて出力結果を楽しむ
call s:echo(s:M.nr2byte(64)) " @
call s:echo(s:M.nr2byte(234)) " 〓〓
call s:echo(s:M.nr2byte(7321)) " 〓&#178;<99>
  • nr2enc_char(charcode) - 文字コードをよく知らない人が適当に数字を与えて出力結果を楽しむ
call s:echo(s:M.nr2enc_char(64)) " @
call s:echo(s:M.nr2enc_char(234)) " 〓
call s:echo(s:M.nr2enc_char(7321)) " &#7321;
  • nr2hex(nr) - 数字? nr を16進数に変換する
call s:echo(s:M.nr2hex(64)) " 40
call s:echo(s:M.nr2hex(234)) " EA
  • replace(str, from, to) - 文字列 str に含まれる from を全て to に置き換える
call s:echo(s:M.replace('Hello hoge hoge', 'hoge', 'piyo')) " Hello piyo piyo
  • replace_once(str, from, to) - 文字列 str に含まれる from を最初のものだけ to に置き換える
call s:echo(s:M.replace_once('Hello hoge hoge', 'hoge', 'piyo')) " Hello piyo hoge
  • scan(str, pattern) - 文字列 str に pattern が含まれるか調べて、一致した文字列のリストを返す
call s:echo(s:M.scan("Hello hoge", 'o')) " ['o', 'o']
call s:echo(s:M.scan("hage hige piyo hoge fuga", "h.ge")) " ['hage', 'hige', 'hoge']
  • split_leftright(haystack, needle) - 文字列 haystack の中で最初に現れる needle より前の部分と後ろの部分で分けたリストを返す
call s:echo(s:M.split_leftright('hoge_piyo', '_')) " ['hoge', 'piyo']
call s:echo(s:M.split_leftright('hogepiyo', '_')) " ['', '']
  • strchars(str) - 文字列 str の文字数を返す
    • 標準関数strcharsがない実行環境でも正常に動作する(っぽい)
call s:echo(s:M.strchars('hoge')) " 4
call s:echo(s:M.strchars('あ')) " 1
  • wrap(str) - columnsオプション値(&columns)の画面内に収まるように文字列 str を分割したリストを返す
    • str が複数行に渡っている場合、各行に対して評価が行われる

Locale.Message モジュール - is a very simple message localization library.(添付のhelpより抜粋)

  • help(vital-locale-message)があるのでそちらを参照

System.Cache モジュール - キャッシュの作成や読み書きに関する操作を提供する

使い方はどれも似たようなものなのでひとつだけ例を示す

let s:cache_dir = '/Users/kanno/tmp/cache'
let s:filename = '/Users/kanno/tmp/base.txt'
call s:echo(s:M.getfilename(s:cache_dir, s:filename)) " /Users/kanno/tmp/cache/=+Users=+kanno=+tmp=+base.txt
  • check_old_cache(cache_dir, filename) - cache_dir 直下にあるハッシュ化した filename キャッシュが、オリジナルのファイルより古ければ削除する
    • check とあるのに delete まで行うのは直感に反する気がした。やっていることはdelete_old_cacheでは
  • create_hash(dir, str) - ハッシュ化した str を返す。dir はハッシュアルゴリズムの判別に用いられる
  • delete(cache_dir, filename) - cache_dir 直下のハッシュ化した filename キャッシュを削除する
  • filereadable(cache_dir, filename) - cache_dir 直下のハッシュ化した filename キャッシュが読み込み可能ならば1を、それ以外は0を返す
    • 読み込み可能の判別は標準関数filereadableで評価する
  • getfilename(cache_dir, filename) - cache_dir 直下のハッシュ化した filename キャッシュパスを返す
    • cache_dir の末尾がパス区切り文字で終わっていなければ勝手に付けてくれる親切設計
    • cache_dir が存在しなければ中間ディレクトリも含めて作成される
  • readfile(cache_dir, filename) - cache_dir 直下のハッシュ化した filename キャッシュを読み込む
    • 読み込み不可の場合は空リストを返す
  • writefile(cache_dir, filename, list) - cache_dir 直下のハッシュ化した filename キャッシュに list を書き込む

System.File モジュール - 環境に応じたファイル操作を提供する

  • open(filename) - ファイル filename をstartやopenなど実行環境用のコマンドで開く。実行環境に対応していない場合は例外が発生する
  • move_file(src, dest) - ファイル src を dest に移動する
    • mvコマンドがある場合はmove_file_exe、ない場合はmove_file_pureへ委譲する
  • move_file_exe(src, dest) - 実行環境の mvコマンドを用いて src を dest へ移動する。引数はそれぞれshellescape()されて実行される。成功時は1、失敗時は0が返る
  • move_file_pure(src, dest) - 標準関数rename()を用いて src を dest へ移動する
    • ただしrename()と異なり(他APIと統一するため)、成功時は1、失敗時は0が返ることに注意
  • copy_file(src, dest) - src を dest にコピーする
    • cpコマンドがある場合はcopy_file_exe、ない場合はcopy_file_pureへ委譲する
  • copy_file_exe(src, dest) - 実行環境の cpコマンドを用いて src を dest へコピーする。引数はそれぞれshellescape()されて実行される。成功時は1、失敗時は0が返る
  • copy_file_pure(src, dest) - 標準関数readfile()およびwritefile()を用いて src を dest へコピーする
    • 読み書きはバイナリモードで行われる
  • mkdir_nothrow(...) - 標準関数mkdir()を実行するが例外を無視する。成功時は1を返し、失敗時は1を返す

System.Filepath モジュール - 環境に応じたファイルパスに対する操作を提供する

  • separator() - 実行時環境のパス区切り文字列を返す
    • 内部処理ではshellslashの設定値を見て切り替えているので、このオプションに依存する
call s:echo(s:M.separator()) " /
  • unify_separator(path) - 文字列 path に含まれる全てのパス区切り文字列を / に統一する
    • 内部処理ではshellslashの設定値を見て切り替えているので、このオプションに依存する
" +shellslash な環境ならば hoge/piyo/fuga.vim(たぶん。試してない)
" +shellslash な環境でなければ hoge\piyo/fuga.vim
call s:echo(s:M.unify_separator('hoge\piyo/fuga.vim')) 
  • split(path) - 文字列 path を実行時のパス区切り文字列で分割した文字列のリストを返す
    • 内部処理ではshellslashの設定値を見て切り替えているので、このオプションに依存する
call s:echo(s:M.split('hoge/piyo/fuga.vim')) " ['hoge', 'piyo', 'fuga.vim']
  • join(...) - 引数をパス区切り文字列にて結合する
" hoge/piyo/fuga.vim(どれも出力結果同じ)
call s:echo(s:M.join('hoge', 'piyo', 'fuga.vim')) 
call s:echo(s:M.join('hoge', ['piyo', 'fuga.vim'])) 
call s:echo(s:M.join(['hoge', 'piyo', 'fuga.vim'])) 
  • is_absolute(path) - 文字列 path が絶対パスなら1、相対パスなら0を返す
    • Windowsかどうかで内部処理が変わるが、win16/win32/win64をhas関数によるチェックのためshellslashオプションの有無は関係ない
call s:echo(s:M.is_absolute('hoge/piyo/fuga.vim')) " 0
call s:echo(s:M.is_absolute('/hoge/piyo/fuga.vim')) " 1
  • dirname(path) - 文字列 path のディレクトリ名を取得する
    • 末尾がパス区切り文字列でも正常に動作するので安心
" hoge/piyo(どれも出力結果同じ)
call s:echo(s:M.dirname('hoge/piyo/fuga.vim')) 
call s:echo(s:M.dirname('hoge/piyo/fuga.vim/')) 
  • remove_last_separator(path) - 文字列 path の末尾にあるパス区切り文字列を削除する
" hoge/piyo/fuga.vim(どれも出力結果同じ)
call s:echo(s:M.remove_last_separator('hoge/piyo/fuga.vim')) 
call s:echo(s:M.remove_last_separator('hoge/piyo/fuga.vim/')) 
call s:echo(s:M.remove_last_separator('hoge/piyo/fuga.vim////')) 
  • is_case_tolerant() - 実行環境がファイル名の大文字小文字を考慮する場合0、それ以外は1を返す

is_case_tolerant()の件は言ってることが逆ですね...

×is_case_tolerant()はファイル名の大文字小文字を考慮するんだったら1を返します
○is_case_tolerant()はファイル名の大文字小文字を考慮するんだったら0を返します

Web.Html モジュール - HTMLに関する操作を提供する

mattn/webapi-vim · GitHubから移植されている

call s:echo(s:decodeEntityReference('&gt;, &lt;, &quot;, &apos;, &nbsp;, &yen;, &#x0d;, &amp;, &raquo;, &laquo;')) " >, <, ", ',  , ¥, &#x0d;, &, >, <
call s:echo(s:encodeEntityReference('>,<,",'', ,\n,&')) " &gt;,&lt;,&quot;,&apos;,&nbsp;,\n,&amp;
  • parse(content) - 文字列 content でWeb.Xml.parse()した結果を返す
  • parseFile(fname) - ファイル fname の内容でWeb.Html.parse()に委譲する
  • parseURL(url) - url のGETレスポンスの内容でWeb.Html.parse()に委譲する

Web.Http モジュール - HTTPに関する操作を提供する

mattn/webapi-vim · GitHubから移植されている

  • decodeURI(str) - 文字列 str をURLデコードする
call s:echo(s:M.decodeURI('?q=Vim+Vim')) " ?q=Vim Vim
" http%3A//d.hatena.ne.jp/kanno_kanno/searchdiary%3Fword%3DVim%20script
call s:echo(s:M.escape('http://d.hatena.ne.jp/kanno_kanno/searchdiary?word=Vim script')) 
  • encodeURI(items) - 辞書 items の各要素を'key=value'としてURLエンコードする
call s:echo(s:M.encodeURI({'word': 'Vim script', 'lang': 'ja'})) " lang=ja&word=Vim%20script
  • encodeURIComponent(items) - 辞書 items の各要素を'key=value'としてURLエンコードする
    • たぶんJavaScriptのencodeURIComponent()と同じ
call s:echo(s:M.encodeURIComponent({'word': 'Vim script', 'lang': 'ja'})) " lang=ja&word=Vim+script
  • get(url, ...) - url に対してGETを送る。curlコマンドがインストールされていなければいけない
    • 移植元のwebapi-vimではwgetコマンドにも対応されているが、まだこちらには取り込まれていない
    • 第二引数にリクエストパラメータを渡すことが出来る
    • 第三引数にヘッダー情報を渡すことが出来る。curlコマンドの-Hに渡す値となる。詳しくはcurlのヘルプを参照
" 出力結果は長くなるから割愛
call s:echo(s:M.get('http://d.hatena.ne.jp/kanno_kanno/searchdiary', {'word': 'Vim script'})) 
  • post(url, ...) - url に対してPOSTを送る。curlコマンドがインストールされていなければいけない
    • 移植元のwebapi-vimではwgetコマンドにも対応されているが、まだこちらには取り込まれていない
    • 第二引数にPOSTのBODYを渡すことが出来る
    • 第三引数にヘッダー情報を渡すことが出来る。curlコマンドの-Hに渡す値となる。詳しくはcurlのヘルプを参照

Web.Json モジュール - JSONに対する操作を提供する

mattn/webapi-vim · GitHubから移植されている

  • decode(json) - json をデコードする
let s:json = "[
\ {
\ \"name\": \"HogeTaro\",
\ \"age\" : 51,
\ \"like\":[\"apple\" , \"banana\"]
\}
\]"
call s:echo(s:M.decode(s:json)) " [{'age': 51, 'name': 'HogeTaro', 'like': ['apple', 'banana']}]
let s:val = {'name':'HogeTaro', 'age':51, 'like': ['apple', 'banana']}
call s:echo(s:M.encode(s:val)) " [{"age":51,"name":"HogeTaro","like":["apple","banana"]}]

Web.Xml モジュール - XMLに対する操作を提供する

mattn/webapi-vim · GitHubから移植されている

  • decodeEntityReference(str) - 実体参照 str から表示文字列へとデコードする
call s:echo(s:M.decodeEntityReference('&amp;, &gt;, &lt;, &#34;')) " &, >, <, "
call s:echo(s:M.encodeEntityReference('&, >, <, "')) " &amp;, &gt;, &lt;, &#34;
  • createElement(name) - ノード名に name を持つXMLノードを作成する
  • parse(xml) - 文字列 xml を解析しDOMオブジェクトを返す
" こういうXMLを操作してみる
" <?xml version="1.0" encoding="Shift-Jis" ?> 
" <venture> 
"   <company> 
"     <name>HogeName</name> 
"     <url>http://www.hoge.co.jp/</url> 
"   </company> 
"   <company> 
"     <name>PiyoName</name> 
"     <url>http://www.piyo.co.jp/info/</url> 
"   </company> 
" </venture>

let s:xml = "<?xml version=\"1.0\" encoding=\"Shift-Jis\" ?> \n<venture> \n  <company> \n    <name>HogeName</name> \n    <url>http://www.hoge.co.jp/</url> \n  </company> \n  <company> \n    <name>PiyoName</name> \n    <url>http://www.piyo.co.jp/info/</url> \n  </company> \n</venture>"
" http://www.hoge.co.jp/
" http://www.piyo.co.jp/info/
for node in s:M.parse(s:xml).childNodes('company')
  call s:echo(node.childNode('url').value()) 
endfor
  • parseFile(fname) - ファイル fname の内容でWeb.Xml.parse()に委譲する
  • parseURL(url) - url のGETレスポンスの内容でWeb.Xml.parse()に委譲する

Functor モジュール - 関数型言語のように関数オブジェクトを扱うための操作を提供する

  • call(callable, args, ...) - 標準関数call()に処理を委譲しますが、第一引数はFunctor.wrap()の戻り値が渡される
" [1, 2, 3](どれも出力結果は同じ)
call s:echo(s:M.call('add', [[1,2], 3])) 
call s:echo(s:M.call({'do': function('add')}, [[1,2], 3])) 
  • wrap(callable) - コールバック callable を持つFunctorオブジェクトを生成する
    • callable はFuncrefか、function名か辞書でないといけない
    • 辞書の場合はキーにdo・値にFuncref/function名を持つ必要がある。値がfunction名の場合はFuncrefへと変換される
" 以下のどれでも同じ結果になる
let s:f = s:M.wrap('add')
" let s:f = s:M.wrap(function('add'))
" let s:f = s:M.wrap({'do': 'add'})
" let s:f = s:M.wrap({'do': function('add')})
call s:echo(s:f.do([1,2], 3)) " [1, 2, 3]
  • bind(callable, this) - 辞書 this のキーdo にコールバック関数 callable を束縛する
    • this のシャローコピーが使われるので、このメソッド内ではthisに副作用は起きない
    • 具体的なユースケースはよく分かっていない
let s:a = {'v': 10}
let s:b = s:M.bind('add', s:a)
call s:echo(s:a) " {'v': 10}
call s:echo(s:b) " {'do': function('add'), 'v': 1}
call s:echo(s:b.do([1], s:b.v)) " [1, 10]
  • curry(callable, v) - コールバック関数 callable の第一引数に v を束縛したFunctorを返す
    • これカリー化(curry)じゃなくて部分適用(partial)に思うけどどうなんだろ
function! Sum(a, b, c)
  return a:a + a:b + a:c
endfunction
let s:sum1 = s:M.curry('Sum', 3)
call s:echo(s:sum1.do(2, 3)) " 8
let s:sum2 = s:M.curry(s:sum1, 2)
call s:echo(s:sum2.do(3)) " 8
call s:echo(s:sum2.do(5)) " 10
  • localfunc(funcname, sid) - スクリプトローカル関数 funcname グローバルなFuncrefへと変換する
    • 関数名 funcname とスクリプト番号 sid からSNR文字列を生成する
    • :help SNR
call s:echo(s:M.localfunc('__sid', 256)) " <SNR>256___sid

Mapping モジュール - キーマッピングやモードに対する操作を提供する

  • execute_abbr_command(mode, dict, lhs, rhs) - 引数をMappng.get_abbr_command()に渡した結果を用いてexecuteする
  • execute_map_command(mode, dict, lhs, rhs) - 引数をMappng.get_map_command()に渡した結果を用いてexecuteする
  • execute_unmap_command(mode, dict, lhs) - 引数をMappng.get_unmap_command()に渡した結果を用いてexecuteする
  • get_abbr_command(...) - abbreviate設定用のコマンド文字列を取得する
    • 引数はモード、辞書型のオプション、左辺値、右辺値となる。サンプル参照
" iabbr <buffer><silent><script> hoge piyopiyo
call s:echo(s:M.get_abbr_command('i', {'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1}, 'hoge', 'piyopiyo')) 
  • get_all_modes() - 全てのモードを返す。のだと思う
  • get_all_modes_list() - Mappng.get_all_modes()の各文字を要素にしたリストを返す
  • get_map_command(...) - map設定用のコマンド文字列を取得する
    • 引数はモード、辞書型のオプション、左辺値、右辺値となる。サンプル参照
" imap <buffer><silent><script> hoge piyopiyo
call s:echo(s:M.get_map_command('i', {'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1}, 'hoge', 'piyopiyo')) 
  • get_unabbr_command(...) - abbreviate解除用のコマンド文字列を取得する
    • 引数はモード、辞書型のオプション、左辺値となる。サンプル参照
" iunabbr <buffer><silent><script> hoge
call s:echo(s:M.get_unabbr_command('i', {'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1}, 'hoge')) 
  • get_unmap_command(...) - map解除用のコマンド文字列を取得する
    • 引数はモード、辞書型のオプション、左辺値となる。サンプル参照
" iunmap <buffer><silent><script> hoge
call s:echo(s:M.get_unmap_command('i', {'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1}, 'hoge')) 
  • is_mode_char(char) - char がモードを表す文字であれば1、それ以外は0を返す
  • options_chars2dict(chars) - モード情報を chars から辞書へと変換する
" {'script': 0, 'silent': 0, 'noremap': 1, 'unique': 1, 'expr': 1, 'buffer': 0}
call s:echo(s:M.options_chars2dict('eu')) 
  • options_chars2raw(chars) - モード情報を chars から評価文字列へと変換する
" <expr><unique>
call s:echo(s:M.options_chars2raw('eu')) 
  • options_dict2chars(dict) - モード情報を dict から文字列へと変換する
" bsSr
call s:echo(s:M.options_dict2chars({'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1})) 
  • options_dict2raw(dict) - モード情報を dict から評価文字列へと変換する
" <buffer><silent><script>
call s:echo(s:M.options_dict2raw({'script': 1, 'silent': 1, 'noremap': 0, 'unique': 0, 'expr': 0, 'buffer': 1})) 

Prelude モジュール - 変数の型や実行環境などの判断条件に関する操作を提供する

  • escape_file_searching(buffer_name) - buffer_name に現れるファイル検索上の特殊文字列をエスケープする。ちょっと自信ない
call s:echo(s:M.escape_file_searching('* [ ] ? { } ,')) " \* \[ \] \? \{ \} \, 
  • escape_pattern(str) - 文字列 str に現れる正規表現上の特殊文字列をエスケープする。ちょっと自信ない
call s:echo(s:M.escape_pattern('~, ", \, ., ^, $, [, ], *')) " \~, \", \\, \., \^, \$, \[, \], \*
  • get_last_status() - vimprocがインストールされていればvimproc#get_last_statusを返し、それ以外はv:shell_errorを返す
  • getchar(...) - 標準関数getchar()と基本同じ。ただし常に文字列を返す
  • getchar_safe(...) - 標準関数getchar()と基本同じ。ただし処理の前後で標準関数inputsave()と標準関数inputstore()を行う
  • glob(...) - 標準関数glob()と同じだが、戻り値がリストとなる
  • globpath(...) - 標準関数globpath()と同じだが、戻り値がリストとなる
  • has_vimproc() - 実行環境のruntimepathにvimproc.vimがあれば1、それ以外は0を返す
  • iconv(expr, from, to) - 標準関数iconv()と基本同じ。入力チェックや実行結果のチェックが行われているのでより安全に扱える
    • 次のいずれかの条件の場合 expr が返る
      • from もしくは to が空文字
      • from と to が同じ文字列
      • 標準関数iconv()の実行結果が空文字
  • input_helper(funcname, args) - funcname の処理前後で標準関数inputsave()と標準関数inputstore()を行う
  • input_safe(...) - 標準関数input()と基本同じ。ただし処理の前後で標準関数inputsave()と標準関数inputstore()を行う
  • is_cygwin() - 実行環境がCygwinであれば1、それ以外は0を返す
  • is_dict(Value) - Value が辞書であれば1、それ以外は0を返す
  • is_float(Value) - Value が浮動小数であれば1、それ以外は0を返す
  • is_funcref(Value) - Value が関数参照であれば1、それ以外は0を返す
  • is_integer(Value) - Value が整数であれば1、それ以外は0を返す
  • is_list(Value) - Value がリストであれば1、それ以外は0を返す
  • is_mac() - 実行環境がMacであれば1、それ以外は0を返す
  • is_number(Value) - Value が整数であれば1、それ以外は0を返す
  • is_numeric(Value) - Value が整数もしくは浮動小数であれば1、それ以外は0を返す
  • is_string(Value) - Value が文字列であれば1、それ以外は0を返す
  • is_windows() - 実行環境がWindowsであれば1、それ以外は0を返す
  • path2directory(path) - 文字列 path からディレクトリ名を取得する
    • System.Filepath.dirname()と似ている。末尾がパス区切り文字列の場合の挙動が異なる
    • ややこしいので統一するか、別々にしておくにしても、どちらかのモジュールに寄せた方がいいのではと思ったりする
call s:echo(s:M.path2directory('hoge/piyo/fuga.vim')) " hoge/piyo
call s:echo(s:M.path2directory('hoge/piyo/fuga.vim/')) " hoge/piyo/fuga.vim ※これは期待通り?
  • path2project_directory(path) - path が属するプロジェクトのルートディレクトリを返す
    • 条件1:path から親ディレクトリを参照していき、'.git'、'.bzr'、'.hg'のいずれかがあるディレクトリ
    • 条件2:path から親ディレクトリを参照していき、下記ファイルのいずれかがあるディレクトリ
    • 条件3:path に'/src/'という文字列が含まれている場合、そのsrcディレクトリ
      • 直感的にはsrcの親ディレクトリが返ると思ったけど、慣習によって違うのかな
  • print_error(message) - 文字列 message をエラー文字列として1行ずつechomsg関数に渡す
  • set_default(var, val) - 条件に一致する場合は変数名 var からなる変数に val を代入する。条件は「var が未定義」もしくは「var の型と val の型が異なる」のいずれかに該当した場合
    • 変数 var の設定はvital.vim側のスクリプト内で処理されるため、グローバルにアクセスできる必要がある
    • なお、同等のことが標準関数get()でも行えると思うので、そちらでも良い気がする。こちらは戻り値で制御するのでスクリプトスコープでも大丈夫
    • Vim-users.jp - Hack #239: グローバル変数を安全に参照する
unlet! g:val
call s:M.set_default('g:val', 1)
call s:echo(g:val) " 1

call s:M.set_default('g:val', 2)
call s:echo(g:val) " 1 ※g:valが存在し、デフォルト値と型も同じため再代入はされない

call s:M.set_default('g:val', 'a')
call s:echo(g:val) " a ※g:valが存在するが、デフォルト値と型が異なるため最代入される
  • set_dictionary_helper(variable, keys, pattern) - keys の各要素について辞書 variable のキーに存在しなければ、キーを現在の要素、値を pattern として追加する
    • keysはリストではなく、カンマ区切りの文字列である。空白はtrimされないので注意すること
let s:dict = {'c':1, 'e':5}
call s:M.set_dictionary_helper(s:dict, 'a,b,c,d,e', 20)
call s:echo(s:dict) " {'a': 20, 'b': 20, 'c': 1, 'd': 20, 'e': 5}

call s:M.set_dictionary_helper(s:dict, 'a,b,c ,d,e', 20)
call s:echo(s:dict) " {'a': 20, 'b': 20, 'c': 1, 'd': 20, 'e': 5, 'c ': 20}
  • smart_execute_command(action, word) - 「action `=word`」としてexecuteする。word が空の場合は action のみ実行される。:help `=
  • strchars(str) - 文字列 str の長さを返す
  • strwidthpart(str, width) - 文字列 str のうち表示セル幅 width に収まる範囲を返す。width 分は前方から数える
call s:echo(s:M.strwidthpart('ほげぴよ', 4)) " ほげ
  • strwidthpart_reverse(str, width) - 文字列 str のうち表示セル幅 width に収まる範囲を返す。width 分は後方から数える
call s:echo(s:M.strwidthpart_reverse('ほげぴよ', 4)) " ぴよ
  • substitute_path_separator(path) - 文字列 path に含まれる \\ を / に変換する
    • System.Filepath.unify_separatorと似ているが、あちらがshellslashオプションに依存し、こちらはWindows環境かどうかで判断する。
    • ややこしいので統一するか、別々にしておくにしても、どちらかのモジュールに寄せた方がいいのではと思ったりする
  • system(str, ...) - vimprocがインストールされていればvimproc#system()を、そうでなければ標準関数systemに対して str を渡して実行する
    • 第二引数がある場合はその値もvimproc#system()/system()に渡される
    • 第三引数がある場合でvimprocがインストールされていればvimproc#system()には渡されるが、system()の場合は無視される
    • 出力結果は&termencodingに設定値があり、かつ&encodingと同じであればPrelude.iconv()が適用される
  • truncate(str, width) - 文字列 str を width で切り詰める。余った分は半角空白で埋められる
call s:echo(s:M.truncate('hoge', 2)) " ho
call s:echo(s:M.truncate('hoge', 5)) " hoge 
call s:echo(s:M.truncate('hoあge', 10)) " hoあge    
  • truncate_smart(str, max, footer_width, separator) - 文字列 str を max で切り詰める
    • 余った分は半角空白で埋められる
    • 超過している場合は str の末尾から footer_width 分だけ残し、省略部を separator で表示する
call s:echo(s:M.truncate_smart('abcdefghijklmnopqrstu', 15, 5, '...')) " abcdefg...qrstu
call s:echo(s:M.truncate_smart('abcdefghijklmnopqrstu', 15, 0, '...')) " abcdefghijkl...
  • wcswidth(str) - 文字列 str のスクリーン上での表示セル幅を返す。
    • Vim7.3以上なら標準関数strwidth()に処理を委譲し、下位バージョンなら独自処理を行う
call s:echo(s:M.wcswidth('ほげ')) " 4

まとめ

読み進めていてモジュール名や関数名から「大体こんな動きだろうな」と想像しやすかったです。
名前やパッケージ構成の大事さを改めて実感しました。
ひとが書いたVim scriptを読み進めたおかげで、Vim力が少しあがった気がします。気持ちは大事です。

また可読性が高いことや、他言語では標準で提供されている関数を実装しているあたりなど、
プログラミングの勉強にもなりました。
たくさん学ぶことがあったし楽しかったので、チャレンジしてよかったなと思います。
Vim script力を上げたい人は、ひとが書いたソースを読むとイイね。

ちなみにぼくはまだプラグインや便利スクリプトを一回も書いたことがないので、
実際に普段の中でvital.vimを使ったことはありません ;)

以上、Vim Advent Calendar 39日目でした。