2012年12月

emacs使いがvimに入門して得た全ノウハウを公開します

Vim Advent Calendar 2012 28日目の記事です。

こんにちは @sonots です。つい最近まで10年来の emacs ユーザーだった私ですが、最近の若者は vim を使うらしいという噂をきいて(嘘)、vim に浮気してみました。ここ1ヶ月ぐらいの話ですが、vim でのruby開発環境を構築してからは、そちらのほうが便利になってずっとvimで開発を続けています(あくまで当社比ですが)。ということで、今日の記事では私が構築した ruby の vim 開発環境について書きたいと思います。といっても、実は ruby 専用の vim plugin は入れてないので、他の言語向けにも使えるかと思います。

最終更新日:2013/10/18

移行した理由

先ほど、最近の若者は vim を使うらしいという噂で移ったなどと適当なことを書きましたが、本当の理由は Ctrl-o (古いマークへジャンプ) と Ctrl-i (新しいマークへジャンプ)の機能にあります。

Ctrl-] で関数ジャンプした後に、Ctrl-o でまるでブラウザの戻るボタンをクリックしたように、前のページに戻ったり、Ctrl-i でブラウザの進むボタンをクリックしたように、先のページに進んだりすることができます(追記。関数ジャンプだけではなく、新しいファイルを開いた後、ファイル検索機能でファイルの指定行に飛んだ場合などにも使えます)。最近一緒に仕事をしたアメリカの vimmer エンジニアがこの機能を使ってサクサクとファイルを開いては戻ってコードを説明している姿をみて、感銘を受けました。

emacs にはこういう機能および拡張がなかったと認識しているので(自分が知らないだけかもしれませんが) 、この機能を使いたくて vim に移行しました。

私の設定

github.com:sonots/.dotfiles/.vimrc

以下では私の設定のうち、頻繁に使っている機能およびプラグインについて書いていきたいと思います。

プラグインの管理

vim プラグインの管理には vundleプラグインを利用しています。

.vimrc に例えば

set rtp+=~/.vim/vundle.git/
call vundle#rc()
Bundle 'thinca/vim-ref'

のように記述しておき、vim を開いて :BundleInstall と打つと Bundle 行で指定した vim プラグインをオートダウンロードしてインストールまでやってくれます。ruby の bundler みたいで大変便利です。

きちんとした使い方は本家のドキュメントがあるのでそちらを参照するのが良いかと思われます。

関数ジャンプ

関数ジャンプには ctags と vim 標準の ctags サポート機能を利用しています。例えば mac の場合は

$ brew install ctags

とインストールして、

$ cd ~/proj
$ ctags -R .

のようにして ruby プロジェクト内でタグファイルを用意。.vimrc で

set tags=.tags

と記述しておき、.tags ファイルのあるディレクトリで vim を起動すると、メソッドにカーソルが当たっている状態で Ctrl-] とタイプすることで、そのメソッドの定義場所にジャンプできます。

キーワード補完

キーワード補完には neocomplcache プラグインを入れて利用しています。

687474703a2f2f692e696d6775722e636f6d2f39304767372e706e67

こんなかんじで文字を打つとキャッシュされているキーワードにヒットする文字を補完候補としてポップアップで表示してくれます。ruby の場合は、さらに標準メソッドを補完してくれたり、omni 補完にも対応しているので、例えば

"a".

まで打つと String クラスのメソッド一覧を候補に出してくれたりします。

ポップアップの選択は、Ctrl-n で次の候補、Ctrl-p で前の候補を選択して、RET で確定、Ctrl-[ でキャンセルができます。

neocmplcache のおすすめ設定では、候補が表示されて RET で確定した時に改行してしまいますが、私はそれが嫌だったので、改行しないように修正を加えています。

"------------------------------------
" neocmplcache
"------------------------------------
" Disable AutoComplPop.
let g:acp_enableAtStartup = 0
" Use neocomplcache.
let g:neocomplcache_enable_at_startup = 1
" Use smartcase.
let g:neocomplcache_enable_smart_case = 1
" Use camel case completion.
let g:neocomplcache_enable_camel_case_completion = 1
" Use underbar completion.
let g:neocomplcache_enable_underbar_completion = 1
" Set minimum syntax keyword length.
let g:neocomplcache_min_syntax_length = 3
let g:neocomplcache_lock_buffer_name_pattern = '\*ku\*'
 
" Define dictionary.
let g:neocomplcache_dictionary_filetype_lists = {
    \ 'default' : '',
    \ 'vimshell' : $HOME.'/.vimshell_hist',
    \ 'scheme' : $HOME.'/.gosh_completions'
    \ }
 
" Define keyword.
if !exists('g:neocomplcache_keyword_patterns')
  let g:neocomplcache_keyword_patterns = {}
endif
let g:neocomplcache_keyword_patterns['default'] = '\h\w*'
 
" Plugin key-mappings.
"imap <C-k>     <Plug>(neocomplcache_snippets_expand)
"smap <C-k>     <Plug>(neocomplcache_snippets_expand)
"inoremap <expr><C-g>     neocomplcache#undo_completion()
"inoremap <expr><C-l>     neocomplcache#complete_common_string()
 
" SuperTab like snippets behavior.
"imap <expr><TAB> neocomplcache#sources#snippets_complete#expandable() ? "\<Plug>(neocomplcache_snippets_expand)" : pumvisible() ? "\<C-n>" : "\<TAB>"
 
" Recommended key-mappings.
" <CR>: close popup and save indent.
"inoremap <expr><CR>  neocomplcache#smart_close_popup() . "\<CR>"
" <TAB>: completion.
"inoremap <expr><TAB>  pumvisible() ? "\<C-n>" : "\<TAB>"
" <C-h>, <BS>: close popup and delete backword char.
inoremap <expr><C-h> neocomplcache#smart_close_popup()."\<C-h>"
inoremap <expr><BS> neocomplcache#smart_close_popup()."\<C-h>"
"inoremap <expr><C-y>  neocomplcache#close_popup()
"inoremap <expr><C-e>  neocomplcache#cancel_popup()
 
" Custom key-mappings
inoremap <expr><CR>  pumvisible() ? neocomplcache#smart_close_popup() : "\<CR>"
"inoremap <expr><C-[>  pumvisible() ? neocomplcache#cancel_popup() : "\<ESC>"
 
" AutoComplPop like behavior.
"let g:neocomplcache_enable_auto_select = 1
 
" Shell like behavior(not recommended).
"set completeopt+=longest
"let g:neocomplcache_enable_auto_select = 1
"let g:neocomplcache_disable_auto_complete = 1
"inoremap <expr><TAB>  pumvisible() ? "\<Down>" : "\<TAB>"
"inoremap <expr><CR>  neocomplcache#smart_close_popup() . "\<CR>"
 
" Enable omni completion.
autocmd FileType css setlocal omnifunc=csscomplete#CompleteCSS
autocmd FileType html,markdown setlocal omnifunc=htmlcomplete#CompleteTags
autocmd FileType javascript setlocal omnifunc=javascriptcomplete#CompleteJS
autocmd FileType python setlocal omnifunc=pythoncomplete#Complete
autocmd FileType xml setlocal omnifunc=xmlcomplete#CompleteTags
 
" Enable heavy omni completion.
"if !exists('g:neocomplcache_omni_patterns')
"  let g:neocomplcache_omni_patterns = {}
"endif
"let g:neocomplcache_omni_patterns.ruby = '[^. *\t]\.\w*\|\h\w*::'
"autocmd FileType ruby setlocal omnifunc=rubycomplete#Complete
"let g:neocomplcache_omni_patterns.php = '[^. \t]->\h\w*\|\h\w*::'
"let g:neocomplcache_omni_patterns.c = '\%(\.\|->\)\h\w*'
"let g:neocomplcache_omni_patterns.cpp = '\h\w*\%(\.\|->\)\h\w*\|\h\w*::'

ファイルエクスプローラ(1)

ファイルエクスプローラには、unite プラグインを利用しています。

unite プラグインは emacs でいう anything.el のようなもので、過去に開いたファイルの一覧、vim 起動ディレクトリ以下のファイル一覧、その他もろもろ(設定次第)を1つの画面に表示して、検索できるインターフェースです。

私は開いたファイル(バッファ)のファイラとして主に利用しています。以下の設定でいう Ctrl-u Ctrl-u を使うことが多いです。

"------------------------------------
" unite.vim
"------------------------------------
" 入力モードで開始する
let g:unite_enable_start_insert=0
" バッファ一覧
noremap <C-U><C-B> :Unite buffer<CR>
" ファイル一覧
noremap <C-U><C-F> :UniteWithBufferDir -buffer-name=files file<CR>
" 最近使ったファイルの一覧
noremap <C-U><C-R> :Unite file_mru<CR>
" レジスタ一覧
noremap <C-U><C-Y> :Unite -buffer-name=register register<CR>
" ファイルとバッファ
noremap <C-U><C-U> :Unite buffer file_mru<CR>
" 全部
noremap <C-U><C-A> :Unite UniteWithBufferDir -buffer-name=files buffer file_mru bookmark file<CR>
" ESCキーを2回押すと終了する
au FileType unite nnoremap <silent> <buffer> <ESC><ESC> :q<CR>
au FileType unite inoremap <silent> <buffer> <ESC><ESC> <ESC>:q<CR>
" Unite-grep
nnoremap <silent> ,ug :Unite grep:%:-iHRn<CR>

ファイルエクスプローラ(2)

ファイルエクスプローラには、もう1つ ctrlp.vim プラグインも利用しています。

unite でいいじゃないか、と言われそうですが、本当は migemo 検索のプラグインを追加して、vim 起動ディレクトリ以下のファイル一覧から、ファイル名を多段絞り込み検索して利用したかったのですが、mac でうまく migemo プラグインが入らなくて挫折してしまいました。

他のプラグインがないか探したところ ctrlp プラグインも密かに人気があって、さらに / 区切りで多段絞り込みができたので、こちらも利用しています。設定は以下のようにしています。

ctrlp なのに Ctrl-k で起動するようにしていたり ^ ^;

"------------------------------------
" ctrlp.vim
"------------------------------------
set runtimepath^=~/.vim/bundle/ctrlp.vim
" let g:ctrlp_max_height          = &lines " 目一杯に一覧
let g:ctrlp_max_height          = 10 " 10行
let g:ctrlp_jump_to_buffer      = 2 " タブで開かれていた場合はそのタブに切り替える
let g:ctrlp_clear_cache_on_exit = 1 " 終了時キャッシュをクリアする
let g:ctrlp_mruf_max            = 500 " MRUの最大記録数
"let g:ctrlp_highlight_match     = [1, 'IncSearch'] " 絞り込みで一致した部分のハイライト
let g:ctrlp_open_new_file       = 1 " 新規ファイル作成時にタブで開く
let g:ctrlp_open_multi          = '10t' " 複数ファイルを開く時にタブで最大10まで開く
let g:ctrlp_match_window_reversed = 0 " Change the listing order of the files in the match window.
let g:ctrlp_mruf_default_order = 0 " Set this to 1 to disable sorting when searching in MRU mode:
let g:ctrlp_map = '<c-k>' " Start CtrlP by Ctrl-k :p
let g:ctrlp_cmd = 'CtrlP'
let g:ctrlp_prompt_mappings = {
  \ 'PrtBS()':              ['<c-h>', '<bs>', '<c-]>'],
  \ 'PrtDelete()':          ['<del>'],
  \ 'PrtDeleteWord()':      ['<c-w>'],
  \ 'PrtClear()':           ['<c-u>'],
  \ 'PrtSelectMove("j")':   ['<c-n>', '<c-j>', '<down>'],
  \ 'PrtSelectMove("k")':   ['<c-p>', '<c-k>', '<up>'],
  \ 'PrtHistory(-1)':       [],
  \ 'PrtHistory(1)':        [],
  \ 'AcceptSelection("e")': ['<cr>', '<2-LeftMouse>'],
  \ 'AcceptSelection("h")': ['<c-x>', '<c-cr>', '<c-s>'],
  \ 'AcceptSelection("t")': ['<c-t>', '<MiddleMouse>'],
  \ 'AcceptSelection("v")': ['<c-v>', '<RightMouse>'],
  \ 'ToggleFocus()':        ['<s-tab>'],
  \ 'ToggleRegex()':        ['<c-r>'],
  \ 'ToggleByFname()':      ['<c-d>'],
  \ 'ToggleType(1)':        ['<c-f>', '<c-up>'],
  \ 'ToggleType(-1)':       ['<c-b>', '<c-down>'],
  \ 'PrtExpandDir()':       ['<tab>'],
  \ 'PrtInsert("w")':       ['<F2>', '<insert>'],
  \ 'PrtInsert("s")':       ['<F3>'],
  \ 'PrtInsert("v")':       ['<F4>'],
  \ 'PrtInsert("+")':       ['<F6>'],
  \ 'PrtCurStart()':        ['<c-a>'],
  \ 'PrtCurEnd()':          ['<c-e>'],
  \ 'PrtCurLeft()':         ['<left>', '<c-^>'],
  \ 'PrtCurRight()':        ['<c-l>', '<right>'],
  \ 'PrtClearCache()':      ['<F5>'],
  \ 'PrtDeleteMRU()':       ['<F7>'],
  \ 'CreateNewFile()':      ['<c-y>'],
  \ 'MarkToOpen()':         ['<c-z>'],
  \ 'OpenMulti()':          ['<c-o>'],
  \ 'PrtExit()':            ['<esc>', '<c-c>', '<c-g>'],
  \ }

ファイル検索

ファイル検索には vim 標準の :vimgrep を利用しています。.vimrc に

" grep後にcwinを表示
autocmd QuickFixCmdPost make,grep,grepadd,vimgrep,vimgrepadd cwin

の設定を追加して、

:vimgrep hoge **/*.rb

のように vim ディレクトリ以下を grep 検索して利用しています。

だったのですが、ack.vim プラグインを見つけてからは

:Ack hoge

ですむので、そちらを使うようになりました。※ ただし、ack のインストールが必要です。

おまけ - 挿入モードでemacsのように移動する

vimを使い初めて最初の頃は vim の操作になれなくて、emacs のように Ctrl-p, Ctrl-n, Ctrl-b, Ctrl-f で移動できるように以下のような設定をしてしまいました。最初のなれなかった頃は大変お世話になりました。今は反省している ^ ^;

"------------------------------------
" Insert mode like emacs
"------------------------------------
" Use <tab> to indent
inoremap <tab> <C-o>==<End>
inoremap <C-p> <Up>
inoremap <C-n> <Down>
inoremap <C-b> <Left>
inoremap <C-f> <Right>
inoremap <C-e> <End>
inoremap <C-a> <Home>
inoremap <C-h> <Backspace>
inoremap <C-d> <Del>
" カーソル位置の行をウィンドウの中央に来るようにスルロール
inoremap <C-l> <C-o>zz
" カーソル以降の文字を削除
inoremap <C-k> <C-o>D
" カーソル以前の文字を削除
inoremap <C-u> <C-o>d0
" アンドゥ
inoremap <C-x>u <C-o>u
" 貼りつけ
inoremap <C-y> <C-o>P
" カーソルから単語末尾まで削除
inoremap <F1>d <C-o>dw
" ファイルの先頭に移動
inoremap <F1>< <Esc>ggI
" ファイルの末尾に移動
inoremap <F1>> <Esc>GA
" 下にスクロール
inoremap <C-v> <C-o><C-f>
" 上にスクロール
inoremap <F1>v <C-o><C-b>
" Ctrl-Space で補完
" Windowsは <Nul>でなく <C-Space> とする
inoremap <Nul> <C-n>
" 保存
inoremap <C-x>s <Esc>:w<CR>a
inoremap <C-x>c <Esc>:wq<CR>

vim チートシート

vi の基本操作ぐらいは知っていましたが、vim の追加機能については初心者だったので、チートシートを用意していました。せっかくなのでついでにさらします。

  • :vs で画面を垂直分割、:sp で水平分割。C-w w で画面を移動して、C-w q で閉じる。ターミナルのサイズを変えた時は C-w = でサイズを自動調整。C-w h, C-w j, C-w k, C-w l でそれぞれ左、下、上、右の画面に移動できる。
  • C-[ で挿入モードを抜ける。Escより楽。
  • >> でインデント
  • u で undo. C-r で redo
  • 矩形カット:Ctrl-v で矩形ビジュアル、V でビジュアルに入って、d でカット。y でペースト y でヤンク(コピー)。p でペースト
  • 一括コメント: Ctrl-v で矩形ビジュアル入って、I (大文字i) で挿入モード、文字を打って、Esc. 文字を打つ代わりに d をおせば一括アンコメント
  • 一括インデント: 上の文字の代わりに >> を打てば一括インデント
  • ペースト時に autoindent しない: :set paste で autoindent を無効化。:set nopaste で戻す
  • C-] でタグジャンプ (ctags)。C-o で戻る、C-i で進む。
  • C-u C-u でファイル一覧開いて (unite)、C-o で戻る、C-i で進む。

まとめ

以上、emacs使いの rubyist が vim に入門してみて今までに得た vim のノウハウを全公開してみました。

今後の展望として、

  1. カレントバッファが分かりにくいので、ステータスバーを目立たせたい (以前カーソル行にアンダーラインを引いて目立たせていたが負荷がかかるのかカーソル移動の速度が極端に落ちたため現在オフにしている。その代替として)
  2. neocomplcache から ctags を利用するなどして、まだ開いていないファイル中のキーワード補完にも対応したい
  3. unite で migemo を利用したい (以前トライした時に mac で migemo がうまく入らなかった)

などを導入したいと思っているので、良いやり方を知っている方がいらっしゃったら是非教えてください><

Vim Advent Calendar 2012 明日は@igrepさんです。

Ruby Advent Calendar 2012 15日目の記事を書いた

書いた!

bundler-auto-updateの紹介 #Ruby - Qiita http://qiita.com/items/bf4bb24f8e4acb60f45d

Git Advent Calender 2012 9日目の記事を書いた

書いた!

dotfiles (設定ファイル)の管理に git submodule を使う http://qiita.com/items/adae6cda0bb7a05f8905

A Ruby and Fluentd committer working at DeNA. 記事本文および記事中のコード片は引用および特記あるものを除いてすべて修正BSDライセンスとします。 #ruby #fluentd #growthforecast #haikanko #yohoushi #specinfra #serverspec #focuslight
はてぶ人気エントリー