欲しいものリスト

Vim script初心者講座 vol.2

vim
最近覚えたことをまとめてみます。

photo-credit: syui


前回のVim script 初心者講座 vol.1の続きです。


目次


番号 タイトル
01 Vimで使える環境変数をまとめてみた
02 覚えておきたい正規表現など
03 Vimでディレクトリ表現に使える記述をまとめる
04 Vim Plugin を作るときのディレクトリ構成
05 自動コマンドでコマンドを自動実行する方法
06 マッピング汚染を無くす方法
07 エラーメッセージを消す方法
08 続けてコマンドを実行する方法
09 よく使うsilentとredrawをひとまとめにしておく方法
10 "if !exists"でコマンドの成否を確認する
11 任意のオプションの有効/無効を切り替える


Vimで使える環境変数をまとめてみた


環境変数について


まず、環境変数というのは何でしょうか。


環境変数というのは、$HOME$PATHといった記述のことを言います。


これは、環境(OS環境など)によって左右されないディレクトリを表現するために用いられます。簡単には、ディレクトリを分かりやすく、簡単に参照するためのものです。


例えば、ホームディレクトリにあるファイルを参照したいとします。


この場合、ホームディレクトリの絶対パス(/User/syui/など)をスクリプトに書くと、個人個人の環境によって使えなくなってしまいますし、不便で、分かりにくいのです。


この点、環境変数($HOME)を使って書くと分かりやすく、おおよその環境で有効になります。


環境変数と絶対パス、相対パスの具体例は、以下のようになります。どれもホームディレクトリですが、記述方式が違います。

種類 記述方式
絶対パス /Users/syui
環境変数 $HOME
相対パス syui


ちなみに、相対パスとは、「今の現在の場所からどこに位置するのか」という意味で、絶対パスとは、「全体から見渡したとしたらどこに位置するのか」という意味です。


Vimで使える環境変数の具体例


まず、使える環境変数を並べるのではなく、具体例で考えていきたいと思います。


以下に Vim script を示しますが、1つずつ内容を見て行きたいと思います。


let $VIMRUNTIME_USER = expand('<sfile>:p:h')


記述 意味
let 変数定義に使います。簡単には、今から空箱を用意しますよ、という合図。空箱を用意しておくと、そこに放り込んだ数字や文字列を後から何度も使うことが出来ます。
$VIMRUNTIME_USER 「let」の後に記述されるものは、用意した空箱の場所を示します。つまり、「let $VIMRUNTIME_USER」を翻訳すると、「空箱は、$VIMRUNTIME_USERという場所にあります」となります。
空箱に指定した場所が「$」とその後にくる大文字で付けられていることによって、環境変数に使いたいのだなと推測できます。
さらに、「$VIMRUNTIME」という環境変数がVimにはあり、それを真似て、USER(ユーザー)用のものも作ろうという意図が推測できます。
= 代入を意味します。ここでは、用意した箱に、今からなにか放り込むよ、という合図になります。何を放り込むかは、「=」の後に示します。
expand シェルの持っている変数や環境変数を展開しますという命令。何が展開されるのかは、「expand」の後に記述されます。
<sfile>:p:h 1つずつ解説していきます。それぞれは、カレントファイル名 (<sfile>) 、フルパス (:p) 、 head (先端) (:h) を意味します。
まとめると、カレントファイル名のフルパスを取得し、その先頭を展開(expand)するという命令になります。


上記は何をしているのかというと、「環境変数を作った」というものです。あくまで個人設定なので、良いとは限りませんが、これで少しは、環境変数のことが分かれば幸いです。


では、どういう環境変数を作ったのかというと、例えば、 /Users/syui/.vim/plugin/simple.vimというファイルに $VIMRUNTIME_USERを記述すれば、その中身は、 /Users/syui/.vim/pluginになるということです。


つまり、編集しているファイル自体を、この環境変数を使って表現するなら、以下の通りになります。

$VIMRUNTIME_USER/simple.vim


通常は、 $HOME/.vim/plugin/simple.vimとするか、 <sfile>:p:h/simple.vimとしなければ、なりませんが、これが上記のような簡単な記述で済みます。


参考:
http://vim-jp.org/vimdoc-ja/starting.html#$VIMRUNTIME
http://vim-jp.org/vimdoc-ja/usr_43.html#filetype-plugin
http://vim-jp.org/vimdoc-ja/usr_44.html#44.9
http://blogaomu.com/2012/12/28/vim-expand


ここで、例えば、「expand」と「cword」を使って、カーソル下の文字列を取得し、キーワードをバッファ内全体で置換してみましょう。


nnoremap <expr> s* ':%substitute/\<' . expand('<cword>') . '\>/'


「s*」と打鍵したあと置き換え文字列と「/g<Cr>」などを打鍵すると置換が行なわれます。


また、前回の復習として、この操作をもう少し簡略化させるとしたら、以下のような感じになります。これは、カーソル元の単語を一括置換するキー「s*」を設定します。


" 関数という命令の集まり(:call 
function! s:sub_auto_cword(char)
  call feedkeys(":%s/\<C-r>\<C-w>/" . a:char . "/g\<CR>")
endfunction

" コマンドの定義(:SubAutoCword
command! -bar -nargs=* SubAutoCword call <SID>sub_auto_cword(<f-args>)

" キーマップの設定(s*
nnoremap <buffer> s* :SubAutoCword<Space>


ポイントとしては、 <C-r><C-w>です。このキーをコマンドラインスペースで押すことで、カーソル元の単語を表示してくれます。


ただし、上記の例では、ダブルクォーテーション(")で囲っているため、エスケープ表現(¥)が必要になります。


ちなみに、同じようなことがしたいのなら、キーマップを定義するだけで十分だったりします。ポイントは、 <Left>の使い方です。


nnoremap sub :%s/<C-r><C-w>//g<Left><Left>


覚えておきたい正規表現など


Vimの正規表現などの特別な表記は、 パターンと検索コマンド検索コマンドと正規表現を繰り返し読むしかないような気がします。


このような特別表記は、難解で可読性が低いですが、色々な応用が出来ます。以下は、その一例です。

"保存時に行末の空白を除去する
autocmd BufWritePre * :%s/\s\+$//ge

" 保存時にtabをスペースに変換する
autocmd BufWritePre * :%s/\t/  /ge

" linedと入力するとカーソル下の単語に一致した行を消す
nmap lined :%g/^.*<C-r><C-w>.*$/d<CR>

" linepと入力するとカーソル下の単語に一致しない行を消す
nmap linep :%v/^.*<C-r><C-w>.*$/d<CR>


ちなみに、正規表現を使った表現を設定ファイルに書き込むときは、「SID」を使って、適当に名前でも付けておくと分かりやすいかもしれません。

nnoremap <silent> <SID>(del-trail-space) :<C-u>%s/\s\+$//ge<CR>
nnoremap <silent> <SID>(tab2space)  * :%s/\t/  /ge<CR>

nmap <C-x> <SID>(del-trail-space)
nmap <C-z> <SID>(tab2space)


例えば、上の例だけでは、少し分かりにくいので、 unite.vimのキーを設定してみることにします。


nnoremap [unite] <Nop>
nmap f [unite]
nnoremap <silent> <SID>(unite-file) :<C-u>Unite -no-split -buffer-name=files file<CR>
nmap [unite]o <SID>(unite-file)


これは、何をしているのかというと、 [unite]fキーに設定し、SIDを使って、 :<C-u>Unite -no-split -buffer-name=files file<CR>コマンドに unite-fileという名前を付けた上で、それにキーバインド oを割り当てています。


この場合、[unite]が"f"なので、当該コマンドのキーマップは、 foとなります。つまり、このキーを押せば、:<C-u>Unite -no-split -buffer-name=files file<CR>が実行されることになります。


参考:
http://cynipe.hateblo.jp/entry/2012/12/01/175905


Vimで使える環境変数まとめ


環境変数 具体的な使用例 内容
$HOME $HOME/.vim/plugin/ ホームディレクトリを意味します。
$PATH $PATH/shell.sh よく「パスの通った場所にコピー」等と言われますが、「$PATH」のことを意味します。「$PATH」の中には、パスに設定した様々なディレクトリが入っています。シェル端末から「$ echo $PATH」で確認して下さい。このようにすることで、先ほど示されたディレクトリ(パスに設定したディレクトリ)の中に置いたファイルは、ディレクトリを指定しないで、直接ファイルを実行できる(直接実行できるように見せかける)というメリットがあります。
つまり、本来なら「$ /usr/bin/shell.sh」としなければならないところと、「/usr/bin」をパスに設定してるなら、「$ shell.sh」というようにファイル名だけで実行できるようになります。
$VIM $VIM/runtime Vimが使用するさまざまなファイルの置き場所を見つけるために利用できます。
$VIMRUNTIME $VIMRUNTIME/macros/matchit.vim ヘルプファイルや構文強調表示の定義ファイルのような、Vimが使用するさまざまな支援ファイルの置き場所を見つけるために使用できます。
$MYVIMRC :so $MYVIMRC 自分の「.vimrc」という設定ファイルを意味します。先の具体例は、自分の設定ファイルを読み込むというコマンドです。ちなみに、「:so」は「:source」の省略形です。これは、「ソースを読み込む」という命令であることがほとんどです。


Vimでディレクトリ表現に使える記述をまとめる


Vimでディレクトリ表現に使える記述をまとめてみます。


ディレクトリというのは、フォルダ構成のことで、主に、ファイルがどこに存在するかを示します。


ちなみに、ディレクトリ自体も実態はファイルでしかないのですが、その辺の深い部分の説明については、省略します。


また、変数を「箱」に例えた説明についても、正確には間違いです。本来なら「メモリ」という概念を元に解説しなければ、ならないのですが、この辺りも省略しています。


ここでは、できるだけ正確な記述ではなく、分かりやすい記述を心がけているからです。


よって、正確な記述を求める人は、興味が出てきたら、「コンピュータの本当の姿」などについて書かれた媒体を追ってみてください。



記述 内容 使用例、または具体例
<sfile> カレントファイル名 <sfile>:p:h
:p ファイル名を完全パスにする ファイル名が "src/version.c"、カレントディレクトリが "/home/mool/vim" のとき:

/home/mool/vim/src/version.c
:h ファイル名のヘッド (末尾の部分と全ての区切りが除かれたもの)。 ファイル名が "src/version.c"、カレントディレクトリが "/home/mool/vim" のとき:

src
:p:gs?/?\\? 「:gs?pat?sub?」の記述方式を利用。これは、"pat" に一致したものを全て "sub" に置き換えるというもの。Windows用にパスを変換するときなどに使える。 ファイル名が "src/version.c"、カレントディレクトリが "/home/mool/vim" のとき:

\home\mool\vim\src\version.c

/home/mool/vim/src/version.c


参考:
http://vim-jp.org/vimdoc-ja/cmdline.html#:<sfile>
http://vim-jp.org/vimdoc-ja/cmdline.html#filename-modifiers


Vim Plugin を作るときのディレクトリ構成


一般的なプラグインは、以下の様なディレクトリ構造で作成されていることが多いです。


~/.vim
├── README.md "プラグインの説明書。Markdown記法で記述する。インストール手順、使い方などを説明する。
├── autoload "オートロードスクリプトの置き場所。ここに置いたスクリプトは、自動で読み込まれる。
├── doc "ドキュメント。ここに置くテキストファイルは、プラグインで定義した関数などを説明する。
├── ftplugin "ファイルタイププラグインの置き場所。
└── plugin "プラグイン本体の置き場所。


ここで、例えば、 NeoBundleというプラグインの中身を見てみましょう。


~/.vim/bundle/neobundle.vim.
├── README.md
├── autoload
│   ├── neobundle
│   │   ├── autoload.vim
│   │   ├── config.vim
│   │   ├── init.vim
│   │   ├── installer.vim
│   │   ├── parser.vim
│   │   ├── sources
│   │   │   ├── github.vim
│   │   │   ├── neobundle_vim_recipes.vim
│   │   │   └── vim_scripts_org.vim
│   │   ├── types
│   │   │   ├── git.vim
│   │   │   ├── hg.vim
│   │   │   ├── nosync.vim
│   │   │   ├── raw.vim
│   │   │   └── svn.vim
│   │   └── util.vim
│   ├── neobundle.vim
│   └── unite
│       ├── kinds
│       │   └── neobundle.vim
│       └── sources
│           ├── neobundle.vim
│           ├── neobundle_install.vim
│           ├── neobundle_lazy.vim
│           ├── neobundle_log.vim
│           └── neobundle_search.vim
├── doc
│   └── neobundle.txt
├── ftdetect
│   └── vimrecipe.vim
├── plugin
│   └── neobundle.vim
├── syntax
│   └── vimrecipe.vim
├── test
│   ├── readme.md
│   ├── vimrc
│   ├── vimrc2
│   ├── vimrc3
│   └── vimrc4
└── vest
    ├── test-parse.vim
    └── test-tsort.vim


ここで、必要ないフォルダは、わざわざ作る必要がないことが分かります。


そのプラグインに必要なフォルダを慣習に従って、置くことにしましょう。


色々なプラグインを参考にし、自分にあった①プラグインの書き方、②フォルダ/ファイルの置き方、③ドキュメントの書き方などを見つけてください。


参考:
ホームディレクトリのファイル構成
Vim: Filetype pluginを極める
README.mdファイル。マークダウン記法まとめ
はじめてプラグインを作ってみた。それとhelpの書き方など
http://vim-jp.org/vimdoc-ja/usr_05.html#plugin
http://vim-jp.org/vimdoc-ja/eval.html#autoload
http://vim-jp.org/vimdoc-ja/filetype.html


自動コマンドでコマンドを自動実行する方法


autocmd


autocmdというものがあります。


簡単に言うと、自動コマンドのことです。


自動コマンドというのは、一定の条件を満たせば、裏で自動にコマンドを実行してくれるというものです。

自動コマンド 内容
:autocmd [条件A] [コマンドB] 条件Aに合致すれば、コマンドBを実行し続ける
:autocmd! 現在のグループの自動コマンドをすべて削除する


しかし、autocmdをそのまま使ってしまうと、実行された回数だけ定義されることになり、同じコマンドがいくつも登録されることになります。


これは、Vimの遅延をもたらしてしまうので、良くないみたいです。


autogroup


よって、 autocmdを使うときは、 augroupというもので、グループ化し、一旦当該コマンドを削除してから再度定義するように書くのが一般的です。


グループ化した自動コマンドは、 autocmd!で削除できます。


augroup vimrc-awrite
  autocmd!
  autocmd TextChanged * w
augroup END









また、上記の場合は、 autocmd! vimrc-writeでも自動コマンドを削除できます。


参考:
http://d.hatena.ne.jp/thinca/20100205/1265307642


eventignore


さらに、自動コマンドの条件自体を無効にするには、以下のようにします。

set ei=TextChanged


便利な条件


TextChanged


*TextChanged* TextChanged ノーマルモードでカレントバッファのテキストが変 更されたとき。つまり |b:changedtick| が更新さ れるとき。 未処理のキー入力がまだあるとき、またはオペレー ターを待機しているときは、発生しない。 注意: このイベントは頻繁に発生するので、ユーザ が予期しないことや時間のかかる処理は行わないこ と。 *TextChangedI* TextChangedI インサートモードでカレントバッファのテキストが 変更されたとき。 ポップアップメニューが表示されているときは発生 しない。 他は TextChanged と同じ。


QuickFixCmdPre


*QuickFixCmdPre* QuickFixCmdPre QuickFixコマンドが実行される前 (|:make|, |:lmake|, |:grep|, |:lgrep|, |:grepadd|, |:lgrepadd|, |:vimgrep|, |:lvimgrep|, |:vimgrepadd|, |:lvimgrepadd|, |:cscope|, |:cfile|, |:cgetfile|, |:caddfile|, |:lfile|, |:lgetfile|, |:laddfile|, |:helpgrep|, |:lhelpgrep|)。 パターンには実行されるコマンドを記述する。 |:grep| が書かれていると、'grepprg' が "internal" にセットされていても実行される。 このコマンドを使って変数 'makeprg' と 'grepprg' を設定することはできない。 このコマンドでエラーになるとQuickFixコマンドは 実行されない。



CursorHold


*CursorHold* CursorHold 'updatetime' の時間の間、ユーザがキーを押さな かったとき。ユーザーが何かキーを押すまで、再び 発生することはない (例えば、もしあなたがコーヒー を入れるためにVimの前を離れても、その間の 'updatetime' ミリ秒ごと発生することはない :-)。 タグをプレビューするためには、 |CursorHold-example| を参照。 このイベントはノーマルモードのときのみ呼ばれる。



マッピング汚染を無くす方法


プラグイン側で以下の様な定義を設定しておきます。

nnoremap <Plug>(AutoWriteStart) :AutoWriteStart<CR>


そして、ユーザー側(設定ファイル)で独自に追記するような形にしておくと、マッピング汚染がなくなります。

nmap <Leader>s <Plug>(AutoWriteStart)


この方法を採用すると、デフォルトのマッピングを潰さなくてすみますし、個人環境への対応も容易となります。


エラーメッセージを消す方法



例えば、外部コマンドを実行するとき、メッセージが表示されます。

Press ENTER or type command to continue


こういうメッセージを表示しないようにするには、 :silent [コマンド]というようにします。


例えば、以下の様な感じです。

:silent !open -a Safari<CR>


参考:
http://koyudoon.hatenablog.com/entry/20120131/1327959087


続けてコマンドを実行する方法


続けてコマンドを実行するには、コマンド定義に -bar オプションを付けなければなりません。


キーマッピングするときには、エスケープ表現を使います。 |\|にするだけなので、簡単です。


nnoremap <silent> <Leader>j :ChromeScrollDown \| :redraw<CR>


:redraw:redraw!はよく使うコマンドなので、覚えておいて損はないです。簡単には、画面を再描写するコマンドです。


ちなみに、 :redraw!で画面がちらつく場合は、以下の方法が有効だそうです。

echo ''
redraw


よく使うsilentとredrawをひとまとめにしておく方法


以下の様なコマンドを定義しておくと、非常に便利です。

command! -nargs=1 Silent
\ | execute ':silent !'.<q-args>
\ | execute ':redraw!'


ちなみに、「execute」は、「Exコマンドで実行する」という命令です。本来、連続できないコマンドを連続で実行できるので、とても便利です。


上のコマンドは、「:silent !xxxx | :redraw!」のような事が出来ます。「xxxx」は引数です。


これで、先ほど作ったコマンドである「Silent」を使って、以下の様な書き方ができるようになります。

function! s:echo_test()
  silent !echo '' \| :redraw!
endfunction

function! s:echo_test()
  Silent echo ''
endfunction


参考:
http://vim.wikia.com/wiki/Avoiding_the_%22Hit_ENTER_to_continue%22_prompts


ここで、個人的にハマったポイントは、もし独自で定義した環境変数「$VIMPLUGIN_USER」を使うとき、ダブルクォーテーション(")で囲って、エスケープ表現(¥)を使わなければなりませんでした。シングルクォーテーション(')では、ダメでしたが、「$HOME」などは、いけました。


command! -nargs=1 -bar ChromeKey
\ | execute "silent !\$VIMPLUGIN_USER/chrome_key.sh <args>"
\ | execute ':redraw!'


"if !exists"でコマンドの成否を確認する


if !exists()を使うことで、コマンドの実行結果の成否によって、命令を記述することが出来ます。


ちなみに、 ifというのは、「もし~なら」という条件式のことを言います。


また、ここでの !は「反対の結果」を意味します。


existsは以下の通りです。

exists({expr}) 結果は数値で、変数{expr}が存在すれば1となり、そうでなければ0となる。



つまり、if !exists()は、「もし括弧()内の変数が存在しなければ、 if~endifまでの命令を実行するという内容になります。


以上を前提に、例えば、以下の記述があったとします。これは、どういう命令群なのでしょ。


if !exists("g:auto_write")
  let g:auto_write = 0
endif


これは、 g:auto_writeという変数が存在していなければ、当該変数を作り、そこに「0」を代入するという命令群です。


また、オートロードコマンドを停止することにも使えます。


if exists("#vimrc-auto-chrome-scroll-up")
  silent autocmd! vimrc-auto-chrome-scroll-up
endif


これは、Vim起動時または、 $MYVIMRCを読み込んだ時に、 #vimrc-auto-chrome-scroll-upというオートロードグループが存在するかを確認(if)しています。


そして、該当オートコマンドグループ(autogroup)が存在すれば、 if~endifまでに書かれたオートコマンド(autocmd)を停止(autocmd!)するという命令を実行します。


ちなみに、 silentは、「コマンドラインスペースに表示しない」という命令です。


参考:
https://code.google.com/p/vimdoc-ja/source/browse/trunk/ja/eval.jax?r=325#2560


任意のオプションの有効/無効を切り替える


例えば、以下の様な設定があったとします。

if !exists("g:auto_write")
  let g:auto_write = 0
endif

if g:auto_write >= 1
  autocmd TextChanged * w
endif


これは、該当機能、具体的には、 if ~ endifまでに書かれた命令を let g:auto_write = 1で有効にし、 let g:auto_write = 0で無効にするということができる書き方です。


" g:auto_writeを有効にする
let g:auto_write = 1


ちなみに、上記の場合は、有効/無効の設定(let g:auto_write = 1/0)は命令文の本体よりも上に記述されなければなりません。

" g:auto_writeを有効にする
" ①
let g:auto_write = 1

" ②
if !exists("g:auto_write")
  let g:auto_write = 0
endif

" ③
if g:auto_write >= 1
  autocmd TextChanged * w
endif


上記では、何をしているのかというと、①「g:auto_write」(以下、Aとする)というグローバル変数を作り、そこに「1」を入れるという命令、②もしAが存在しなければ、Aを作り、そこに「0」を入れるという命令、③もしAの中身が「1」より大きければ、オートコマンドを実行するという命令が書かれています。


また、任意のオプションの有効/無効を簡単に切り替える設定を書いておくと、切り替えが楽になります。


オプションの有効/無効の切り替えを簡単にするには、例えば、unite.vim で選択、変更できるようにしておきましょう。


以下のように書くと、 set xxx!let xxxx =0/1で切り替えできるものについて、切り替えが簡単になります。

" let ON/OFF
" http://whileimautomaton.net/2007/01/01221300
function ToggleOption2(opt_name)
  execute "if &" . a:opt_name . "\n"
      \ . "  let &" . a:opt_name . " = 0\n"
      \ . "  echo '" . a:opt_name . " off'\n"
      \ . "else\n"
      \ . "  let &" . a:opt_name . " = 1\n"
      \ . "  echo '" . a:opt_name . " on'\n"
      \ . "endif"
endfunction
" nnoremap \ :call ToggleOption2("wrap")<CR>

" set ON/OFF
" http://d.hatena.ne.jp/t9md/20131016/1381942537
" コマンド ToggleOption の定義
command! -nargs=1 ToggleOption set <args>! <bar> set <args>?

" Unite menu:toggle
let g:unite_source_menu_menus = {}
let g:unite_source_menu_menus.toggle = {}
let g:unite_source_menu_menus.toggle.command_candidates = {
      \ 'number': "ToggleOption number",
      \ 'hlsearch': "ToggleOption hlsearch",
      \ 'wrap': "call ToggleOption2('wrap')",
      \ }

" mac の 'Command-/' で呼び出し。
nnoremap <D-/> :Unite menu:toggle -start-insert -auto-resize<CR>


機能の有効/無効を切り替えるには、例えば、 :Unite menu:toggleというコマンドを実行すれば、 unite.vimでハイライトした行で定義したコマンドが表示されます。


あとは、切り替えを行いたいものを選択しましょう。


ちなみに、 :ToggleOptionは、 set xxxx!で有効、無効を変更できる機能に限ります。


また、 :call ToggleOption2("xxxx")は、 let xxxx = 0/1で有効、無効を変更できる機能に限ります。


参考:
http://whileimautomaton.net/2007/01/01221300
http://d.hatena.ne.jp/t9md/20131016/1381942537


コメント


今度は、 vital.vimでも使ってみたいと思います。このプラグインは、 Vim Plugin の作成を便利にするプラグインみたいですね。


この記事は Vim Advent Calendar 2012 の360日目の記事として書かれました。