ホーム > タグ > lv3

lv3

Hack #106: 一つのウインドウに集中する

問題

通常、Vimでは縦横に画面を分割して複数のファイルを同時に閲覧しながら編集することになります。 常に一つのウインドウのみ表示して他のバッファに:bnなどで移動することもできますが、画面分割を行なうことで編集対象のウインドウ群を平面上の位置関係で認識することにより、人間にとって直感的な管理ができ、画面分割したままウインドウの移動で複数ファイルを同時に対処するという方法が好ましいシーンが多いです。

さて、ディスプレイのサイズやフォントサイズによっては、たとえばたった6分割するだけで各ウインドウがとても小さくなってしまいます。 とりわけプレゼンテーションを行なう際には、フォントサイズを通常よりもとても大きくするため、ウインドウサイズの制限がより厳しくなります。

1タブ1ファイルという1次元空間上にバッファ群を配置することで管理することもできますが、二次元空間上で行ないたい場合、どのようにすればいいのでしょうか。

解決

コマンドBigを定義します。以下の設定を~/.vimrcに記述します。

command! Big wincmd _ | wincmd |

以後、:Bigでそのウインドウサイズを可能な限り広げます。 逆に全てのウインドウサイズを同程度に戻したいときは、Vim標準機能である<C-w>=を使用します。

発展

著者は以下の関数とコマンドを定義し、<Space>aoで編集中バッファを別の単独のタブに切り出すようにしています。

" kana's useful tab function {{{
function! s:move_window_into_tab_page(target_tabpagenr)
  " Move the current window into a:target_tabpagenr.
  " If a:target_tabpagenr is 0, move into new tab page.
  if a:target_tabpagenr < 0  " ignore invalid number.
    return
  endif
  let original_tabnr = tabpagenr()
  let target_bufnr = bufnr('')
  let window_view = winsaveview()

  if a:target_tabpagenr == 0
    tabnew
    tabmove  " Move new tabpage at the last.
    execute target_bufnr 'buffer'
    let target_tabpagenr = tabpagenr()
  else
    execute a:target_tabpagenr 'tabnext'
    let target_tabpagenr = a:target_tabpagenr
    topleft new  " FIXME: be customizable?
    execute target_bufnr 'buffer'
  endif
  call winrestview(window_view)

  execute original_tabnr 'tabnext'
  if 1 < winnr('$')
    close
  else
    enew
  endif

  execute target_tabpagenr 'tabnext'
endfunction " }}}

" <space>ao move current buffer into a new tab.
nnoremap <silent> <Space>ao :<C-u>call <SID>move_window_into_tab_page(0)<Cr>
ujihisa

Hack #99: 起動に時間のかかるプラグインを探す

Vim には便利なプラグインがたくさんありますが、大量にインストールすると起動に時間がかかるようになってしまいます。そこで、Vim のプロファイリング機能を使って起動に時間のかかっているプラグインを探す方法を紹介します。

追記

Vim 7.2.269 から –startuptime と言う起動時のプロファイルを簡単に行うためのコマンドライン引数が追加されました。


vim --startuptime=result.txt

7.2.286 からは引数の形式が変更されています。


vim --startuptime result.txt

このようにすることで、Huge版でなくても手軽にプロファイルを行う事が出来ます。

本 Hack はこのパッチが公開される前に書かれたので少々複雑な手順を踏んでいますが、この方法で生成された result.txt には行単位でどれだけ時間がかかったのかも書かれているのでより詳しい調査ができる、という利点もあります。

準備

まず、+profile 機能の付いた Vim を用意する必要があります。これは Huge 版の Vim に付いている機能です。手元の Vim が Huge 版でない場合は、ソースから自分でコンパイルする必要があります。 コンパイル方法については本 Hack の範囲を超えるので省略します。

プロファイルを行う

以下のようにして vim を起動します。

vim --cmd "profile start result.txt" --cmd "profile file */plugin/*.vim" -c quit

このコマンドを実行すると、特にエラーが発生しない限り vim はすぐに終了します。そしてプロファイル結果が result.txt に出力されます。

また、ついでに自分の vimrc も確認したい、という場合は –cmd を増やせば大丈夫です。

vim --cmd "profile start result.txt" --cmd "profile file */plugin/*.vim" --cmd "profile file .vimrc" -c quit

ファイル名は適時変更してください。また、コマンドの実行タイミングにより $MYVIMRC は使えません。

結果を解析する

プロファイル結果の概要を知るために結果のファイルを加工してみます。 以下のような vim スクリプトを作成し、結果ファイルを開いてから作成したスクリプトを :source します。

let s:list = []
global/^SCRIPT/
\ call add(s:list, printf("%s\t%s",
\                         matchstr(getline(line('.')+2), '\d\+\.\d\+'),
\                         matchstr(getline('.'), 'SCRIPT\s*\zs.*$')))
new
put =reverse(sort(s:list))
1 delete _

これを実行すると新しいバッファが開かれて、そこに実行時間とスクリプトのファイル名が時間のかかった順に表示されます。 もっと細かい結果が知りたい場合はプロファイル結果を直接眺めたり別の方法で加工すると良いでしょう。 なお、今回の方法では 'runtimepath' の plugin ディレクトリ内にあるファイルのみを対象にし、各プラグインが外部の Vim スクリプトを実行している時間も含めた時間を出しました。

thinca

Hack #93: neocomplcacheを拡張する 前編

neocomplcacheはデフォルトのままでも使いやすいですが、自動補完になれてくると、自分でもneocomplcacheの機能を拡張させたいと思うでしょう。そんなときにはneocomplcacheのプラグインを作ると便利です。neocomplcacheに新たな命を吹き込みましょう。

プラグインの仕様

プラグインはautoload/neocomplcache/pluginにインストールしたものが自動的に読み込まれます。 プラグインには、絶対必要な関数がいくつかあります。ここではそれについて説明をしておきます。

neocomplcache#plugin#プラグイン名#initialize()

neocomplcacheが初期化するときに呼ばれます。ここで変数の初期化をしたり、初期設定を行います。

neocomplcache#plugin#プラグイン名#finalize()

neocomplcacheが無効化されるときに呼ばれます。ここで不要になったコマンドやautocmdを削除します。

neocomplcache#plugin#プラグイン名#get_keyword_list(cur_keyword_str)

a:cur_keyword_strにマッチするリストを返すために呼ばれます。get_keyword_list()が返す補完リストは、特定のキーを含むディクショナリのリストとなっています。詳しくは「補完リストの仕様」の項を参照してください。

補完リストの仕様

get_keyword_list()が返す補完リストは、特定のキーを含むディクショナリのリストとなっています。一部のキー以外はVim標準の補完で使用するものと同じです。

word, abbr, menu, info, icase, dup

Vim標準の補完で使用するものと同じです。word以外は省略することができます。詳しい解説は、[Hack #14: ]Insert mode補完 自作編を参照してください。 menuはどのプラグインの候補で補完しているかを[B]のような記号で表す習慣になっています。絞り込み時におかしくなるので、icaseは必ず1に設定してください。

プラグイン用ヘルパ関数

autoload/neocomplcache.vimには、プラグインから呼び出せるようにヘルパ関数が実装されています。ここでは、よく使われる関数について解説します。

neocomplcache#keyword_filter(list, cur_keyword_str)

get_keyword_list()で使用できる、単純なフィルターです。a:listの中から、a:cur_keyword_strにマッチするリストを返します。とは違って、listは変更されるとは限りません。

neocomplcache#unpack_dictionary(dict)

リストの辞書をリスト化して返します。

neocomplcache#unpack_dictionary_dictionary(dict)

辞書の辞書をリスト化して返します。

neocomplcache#keyword_escape(cur_keyword_str)

a:cur_keyword_strをマッチングに使えるようにエスケープします。

neocomplcache#get_cur_text()

現在のカーソル文字列を取得します。

neocomplcache#get_completion_length(plugin_name)

plugin_nameの自動補完する文字列長を返します。

neocomplcache#get_keyword_pattern_end(filetype)

文字列の最後にマッチするfiletypeのキーワードパターンを返します。filetypeは省略可能で、省略すると現在のバッファのfiletypeを参照します。

neocomplcache#get_keyword_pattern(filetype)

文字列にマッチするfiletypeのキーワードパターンを返します。filetypeは省略可能で、省略すると現在のバッファのfiletypeを参照します。

neocomplcache#check_skip_time()

補完開始時刻と現在の時刻を比べ、スキップするべき場合に1を返します。時間がかかりすぎる計算を中断する場合に使います。

neocomplcache#is_auto_complete()

自動補完の時に1を返します。

neocomplcache#print_caching(string)

キャッシュ時のメッセージを表示します。

neocomplcache#print_error(string)

エラーメッセージを表示します。

キャッシュ用ヘルパ関数

autoload/neocomplcache/cache.vimには、プラグインから呼び出せるキャッシュのヘルパ関数が含まれます。 量が多いので、ここでは詳しく説明することは避けますが、うまく利用すると簡単にキャッシュを使ったプラグインを記述できます。 ちなみに、neocomplcache Ver.4以降では、ほとんどのプラグインがキャッシュ用ヘルパ関数を使うように書き直されています。

プラグインのサンプル

neocomplcacheに標準添付のプラグインを参考にしても良いのですが、機能が複雑化しているため、プラグイン作りの勉強のために見るのは大変です。 thincaさんが作成したsnipMate_complte.vimはほどよい長さなので、勉強に最適でしょう。 ただし、これはneocomplcache Ver.4に対応しておらず、仕様が古いです。参考にする場合は注意しましょう。

プラグインと補完関数の違い

neocomplcache Ver.3.00より、補完関数が実装され、より自由度の高い補完が実装できるようになりました。 プラグインと補完関数の違いは、プラグインはカーソル前のキーワードから補完しますが、補完関数は独自に補完位置を決定できるというところです。 ただし、ワイルドカードに対応するためには独自に処理しなければいけないなど、ちょっと面倒な部分もあります。 補完関数の作り方についても、今後VimHacksで特集する予定です。

Shougo

Hack #89: 自動補完をさらに使いこなす

[Hack #49: ]自動補完を活用する 後編ではneocomplcacheの基本的な特徴について解説しました。ここではneocomplcacheの良くある質問について簡単に回答しようと思います。設定の参考になれば幸いです。

neocomplcacheで辞書補完をするには?

辞書ファイルへのパスは、変数g:NeoComplCache_DictionaryFileTypeListsに格納されています。 パスを設定すれば、ファイルを開いたときに自動的に読み込まれます。 例えば、私は次のように設定しています。defaultはファイルタイプに関係なく参照される辞書ファイルです。

let g:NeoComplCache_DictionaryFileTypeLists = {
            \ 'default' : '',
            \ 'vimshell' : $HOME.'/.vimshell_hist',
            \ 'scheme' : $HOME.'/.gosh_completions'
            \ }

自動補完が重い

g:NeoComplCache_KeywordCompletionStartLengthの値を増やすか、g:NeoComplCache_SkipCompletionTimeを増やす(例えば’0.3′)と少しイライラが解消されるでしょう。あまりやりすぎると、なかなか自動補完の候補が出なくなります。基本的には自動補完は重い処理なので、あまり性能の低いPCで有効にするものではありません。

Insertmodeでカーソル移動すると補完されて大変

この現象は、autocomplpop.vimでも起こります。neocomplcacheやautocomplpopは挿入モードでのカーソル位置移動をトリガーとして、補完候補をポップアップするようになっているからです。計算中にキー入力が続くと、neocomplcacheは補完をスキップしますが、挿入モードでカーソルキーを使うこと自体、Vimの使用方法として間違っています。

neocomplcacheでスニペット補完を使用するには?

まずはスニペットの展開にKey-mappingを割り当ててください。初期状態ではどのキーにも割り当てられていません。 スニペットのトリガーを補完するか直接入力してから、割り当てたスニペットの展開キーを押すことで展開されます。 展開キーを連打すると、プレースホルダの移動になります。

imap <silent>L     <Plug>(neocomplcache_snippets_expand)
smap <silent>L     <Plug>(neocomplcache_snippets_expand)

オムニ補完が重い

オムニ補完のためのキーワード設定は、変数g:NeoComplCache_OmniPatternsに格納されています。 ファイルタイプをキーとしているので、無効にしたいファイルタイプの正規表現を空にするとオムニ補完が呼ばれなくなります。 例えば、htmlのオムニ補完を無効にするためには、次のようにします。

let g:NeoComplCache_OmniPatterns['html'] = ''

他のプログラミング言語にも対応させたい

補完するためのキーワードの設定は、変数g:NeoComplCache_KeywordPatternsに格納されています。 ファイルタイプをキーとして、キーワードにマッチする正規表現を設定すれば、任意のプログラミング言語に対応します。

Vimのファイル名補完やオムニ補完の呼び出しをneocomplcache組み込みのものに置き換えたい

neocomplcacheに組み込まれているファイル名補完やオムニ補完は、クイックマッチや補完スキップに対応するなど、細かな部分で改良されています。 Vim標準の補完呼び出しを置き換えるためには、次のようにKey-mappingを定義します。

inoremap <expr><C-x><C-f>  neocomplcache#manual_filename_complete()
inoremap <expr> <C-j>  &filetype == 'vim' ? "\<C-x>\<C-v>\<C-p>" : neocomplcache#manual_omni_complete()

Vimで巨大なファイルを開くと、キャッシュ生成で待たされる

neocomplcacheはファイル読み込みを監視し、新たなファイルを開くと、自動的にキーワードをキャッシュするようになっています。 巨大なファイルの場合、このキャッシュを生成する動作が邪魔になることがあります。g:NeoComplCache_CachingLimitFileSizeの値を調節することで、巨大なファイルはキャッシュしないように設定することが可能です。初期値は1000000となっているので、約1メガバイト以上のファイルはキャッシュしません。手動でキャッシュさせるには、:NeoComplCacheCachingBufferコマンドを使用します。

補完がおかしい、クイックマッチが効かない

completeoptに問題があるのだと思います。補完動作がおかしくなるので、longestやmenuは指定しないでください。お勧めはmenuoneを指定することです。クイックマッチが正しく動作するようになります。previewを追加するかどうかはお好みで。

Shougo

Hack #82: コード片を貼付けしてインデントを正しくする

問題

Vimでコード片をヤンクして別の場所で貼付ける際に、インデントの深さが異なる場合があります。 インデントの再調整をどのようにして楽に行なうことができるでしょうか。

解決1 Vimに任せる

Vimには=で指定した範囲のインデントを整える機能が備わっています。これはファイルタイプに依存します。

例えばRubyで以下のようなコードを編集中に、

...

def fact(n)
if n == 0
0
else
n * fact(n - 1)
end
end

...

def fact(n)の行にカーソルがあり、そこから次の空行までをまとめてRubyとしての正しいインデントに整形するには=}とタイプします。 これは=} (次の空行までを示すmotion)まで適用するという意味です。

これを貼付け後に貼付けした範囲を指定して行なうことで本問題が解決します。まずPで貼付けを行ない、カーソル位置が貼付け位置の手前にある状態のまま移動せずに、=']を行い、貼付けしたコード片の末尾までを指定してインデントを整えさせます。

def fact(n)
  if n == 0
    0
  else
    n * fact(n - 1)
  end
end

必要ならば、P自体をP=']で置き換えることで、より簡単に行なうことができます。

nnoremap P P=']

解決2 手動で頑張る

解決1は=ではうまくいかないときに元のインデント情報が失われてしまい大変なことになってしまいます。このときは、><で手動でインデントの深さのみをまとめて変更させる手法をとるのが好ましいでしょう。

Pで貼付けした範囲を右にインデントするならばPのあとに>']を、左にインデントするならば<']を打鍵しましょう。続けてさらに深く/浅くするには、.を打鍵しましょう。

ujihisa

Hack #75: 正規表現で先読み/後読みを使用する

Hack #70 で、正規表現においてマッチする範囲を制限する方法を紹介しましたが、他の正規表現エンジンで同じようなことをしたい場合、肯定先読みや否定先読みと言った機能が使われます。 そして当然、Vim にも肯定先読みや否定先読みの機能は備わっています。

肯定先読み/否定先読み/肯定後読み/否定後読み

先読み、後読みとはすなわち、特定のパターンが対象のパターンの後ろに来る(先読み)、もしくは前に来る(後読み)ことを条件付けることです。否定の場合は逆に来ないことを条件付けます。

肯定の場合は単に続けて正規表現を構成した場合でもマッチする個所は同じですが、マッチする対象が異なります。先読み、後読みの部分はあくまで条件なのでマッチ対象には含まれません。

Vim では以下のパターンを置くことで直前の文字、もしくはグループを先読み、後読みに指定します。

肯定先読み\@=
否定先読み\@!
肯定後読み\@<=
否定後読み\@<!

例えば、

\w\+\%(()\)\@=

とすると直後に "()" が来る単語にマッチします。また、

\%(function\s\+\)\@<!\w\+

とすると直前に "function" と空白文字が来ない単語にマッチします。

\zs \ze との関係

\zs や \ze は肯定先読み/肯定後読みの省略形と考える事ができます。すなわち、

foo\zsbar\ze\baz

\%(foo\)\@<=bar\%(baz\)\@=

は同義になります。ただし、読み易さや処理速度を考えると可能ならば前者を使う方が良いでしょう。

Perl との比較

Vim の先読み/後読みは他の正規表現エンジンとは少し違う書き方をします。 例えば Perl の正規表現エンジンでは () でグループを作り、グループに対して先読み、後読みの指定をします。 対して Vim では、直前の文字、もしくはグループに対して先読み、後読みを指示する指定子があります。 Perl の書き方に慣れた人にとっては最初はわかりづらいかもしれませんが、慣れれば大した違いはありません。

VimPerl
肯定先読み\%(pattern\)\@=(?=pattern)
否定先読み\%(pattern\)\@!(?!pattern)
肯定後読み\%(pattern\)\@<=(?<=pattern)
否定後読み\%(pattern\)\@<!(?<!pattern)

また、Vim の後読みは Perl と違い可変長パターンも扱えます。ただし後読みは少々重いので処理速度には注意が必要です。

thinca

Hack #54: VimでSticky Shiftを実現する

Vimを使いこなすためには、自分でキーバインドを変更するのが第一歩です。ここではキーバインドの応用として、Sticky ShiftをVim上で再現する方法について考えます。

Sticky Shiftとは

普通の人にとって、Sticky Shiftは聞き慣れない用語かもしれません。Sticky Shiftとは、シフトキーを他のキーとの組み合わせで代用することです。よく使われるパターンとしては、ホームポジション上にある;をシフトキーにして、;aでAを入力するようにします。こんな面倒なことをして、メリットがあるのが疑問をお持ちの方も多いことでしょう。メリットとしては、Hoge Piyoのように、シフトキーを時々入力するような場合に、シフトキーを押したり離したりする必要がないので、小指に優しいことです。特にかな漢字変換でShiftキーを多用するskkユーザーはSticky Shiftを設定していることが多いようです。エディタの使いすぎで小指が痛くなるのはEmacsユーザーだけだという偏見が多いですが、そんなことはありません。我々Vimmerは確かにCtrlキーを押すことは少ないですが、Shiftキーを押すことが多く、知らず知らずのうちにFPを消費しています。小指の負担を和らげるためにも、VimにSticky Shiftを設定してみましょう。Shiftキーを組み合わせたキーマッピングが空くので、キーマッピングを増やすことができるという思わぬ副作用もあります。

Sticky Shiftの設定法

基本的に、キーマッピングの.vimrcに設定をするだけです。一つずつ設定すると煩雑になるので、配列を使って評価するようにすると簡単でしょう。私は次のように設定し、Insert mode, Command-line mode, Select modeでSticky Shiftが有効になるようにしています。これは英語キーボード用の設定です。日本語キーボードを使用している場合、適時設定を変更してください。
inoremap <expr> ;  <SID>sticky_func()
cnoremap <expr> ;  <SID>sticky_func()
snoremap <expr> ;  <SID>sticky_func()

function! s:sticky_func()
    let l:sticky_table = {
                \',' : '<', '.' : '>', '/' : '?',
                \'1' : '!', '2' : '@', '3' : '#', '4' : '$', '5' : '%',
                \'6' : '^', '7' : '&', '8' : '*', '9' : '(', '0' : ')', '-' : '_', '=' : '+',
                \';' : ':', '[' : '{', ']' : '}', '`' : '~', "'" : "\"", '\' : '|',
                \}
    let l:special_table = {
                \"\<ESC>" : "\<ESC>", "\<Space>" : ';', "\<CR>" : ";\<CR>"
                \}

    let l:key = getchar()
    if nr2char(l:key) =~ '\l'
        return toupper(nr2char(l:key))
    elseif has_key(l:sticky_table, nr2char(l:key))
        return l:sticky_table[nr2char(l:key)]
    elseif has_key(l:special_table, nr2char(l:key))
        return l:special_table[nr2char(l:key)]
    else
        return ''
    endif
endfunction
私の場合、;をShiftのための修飾キーとして使い、;そのものを入力したいときは;<Space>を使用するようにしています。
追記:thincaさんのアイデアにより、<expr>を使うようにしました。マッピングも減って、すっきりしますね。
慣れるととても便利なSticky Shiftですが、デメリットもあります。なんといっても大きいのは、Sticky Shiftが設定されていないアプリケーションがほぼ使用不能になることです。特に普通のシェルが使いにくくなってしまいます。シェルでもSticky Shiftが使えると良いのですが、キーバインドを設定するのはなかなか大変です。プラグイン等を駆使して、Vimを使用する時間を増やすしかないでしょう。

Shougo

Hack #29: Command-line mode補完 自作編

Command-line modeの補完はinput()による入力や、自分が定義したコマンドにも有効です。ここではCommand-line modeにおける補完の設定方法についてレクチャーします。さらに、標準では用意されていない補完関数を自作する方法まで解説します。

自作コマンドに補完を定義する:
自作コマンドに引数の補完を定義する場合、-complete=に続けて補完関数の名前を指定します。指定できる補完関数には、次のようなものがあります。
  • augroup
      autocmdのグループ名を補完します。
  • buffer
      バッファ名を補完します。
  • command
      VimのExコマンド名と、その引数を補完します。
  • dir
      ディレクトリ名を補完します。
  • event
      autocmdイベント名を補完します。
  • expression
      Vimの式を補完します。
  • file
      ファイル名を補完します。
  • shellcmd
      シェルコマンド名を補完します。
  • function
      関数名を補完します。
  • help
      ヘルプのタグを補完します。
  • highlight
      シンタックスハイライト名を補完します。
  • mapping
      キーマッピング名を補完します。
  • menu
      メニュー名を補完します。
  • option
      オプション名を補完します。
  • tag
      タグ名を補完します。
  • tag_listfiles
      タグと<C-d>でリスト表示されるファイル名を補完します。
  • var
      Vimユーザ変数を補完します。
  • custom,{func}
      関数名funcで与えられる自作補完を行います。
  • customlist,{func}
      関数名funcで与えられる自作リスト補完を行います。
  • 設定例
    command! -nargs=? -complete=dir -bang CD call s:ChangeCurrentDir('<args>', '<bang>')
    補完関数についての詳しい解説は:help command-completionを参照してください。
    input()に補完を定義する:
    input()に補完を定義するのは簡単です。オプション引数であるcompletionが補完関数の名前となっているので、ここを自作コマンドの時と同様に設定すればよいです。例えばバッファ名を補完したいときには、次のように記述します。
    let bname = input("Select buffer: ", "", "buffer")
    補完関数に求められる仕様:
    補完を自作するためには、まず補完関数を作成しなければなりません。補完関数はArgLead, CmdLine, CursorPosという三つの引数を持ちます。
  • ArgLead
      補完対象の文字列です。補完候補をフィルタリングするときに使います。
  • CmdLine
      現在入力中のCommand-line modeです。これをsplitで区切ることで、何番目の引数を補完しているのかを得ることができます。
  • CursorPos
      現在カーソルがある場所です。
  • 補完関数はこれらの引数より状況を分析して、有効な補完候補のリストを返却します。customの場合は候補を\nで区切ります。customは候補の絞り込みをVim側で行いますが、customlistでは補完関数側で行わなければなりません。
    自作補完関数についての詳しい解説は:help command-completion-customを参照してください。
    補完関数のサンプル:
    command! -nargs=+ -complete=customlist,CompletionGitCommands Git call GitCommand()
    function! CompletionGitCommands(ArgLead, CmdLine, CusorPos)
        let l:cmd = split(a:CmdLine)
        let l:len_cmd = len(l:cmd)
    
        if l:len_cmd <= 1
            " Commands name completion.
            let l:filter_cmd = printf('v:val =~ "^%s"', a:ArgLead)
            return filter(['add', 'bisect', 'branch', 'checkout', 'clone', 'commit', 'diff', 'fetch',
                        \'grep', 'init', 'log', 'merge', 'mv', 'pull', 'push', 'rebase', 'reset', 'rm', 
                        \'show', 'status', 'tag'], l:filter_cmd)
        else
            " Commands argments completion.
            let l:cmdname = l:cmd[1]
            if l:cmdname == 'add' || l:cmdname == 'mv' || l:cmdname == 'rm'
                let l:arg = get(l:cmd, 2, '')
                return split(glob(l:arg.'*'), '\n')
            else
                return []
    endfunction
        
    最後にまとめとして、補完関数のサンプルを紹介しておきます。gitのコマンドを補完するサンプルです。それだけでは面白くないので、第二引数に'mv'や'add', 'rm'といったコマンド名が与えられるとファイル名を補完するようになっています。
    Shougo

    Hack #14: Insert mode補完 自作編

    Insert modeの補完を自作することができれば、Vimの可能性が無限大に広がります。ここではInsert modeにおける補完関数の作り方についてレクチャーします。補完関数に求められる仕様は'omnifunc''completefunc'で共通なので、ここで学んだ知識はどちらの補完にも使用できます。

    補完関数に求められる仕様:
    補完を自作するためには、まず補完関数を作成しなければなりません。補完関数はfindstartとbaseという二つの引数を持ち、Vimから二度呼ばれます。
    一度目に呼び出されたとき、カーソルから補完する単語までの位置を決定します。a:findstartは1になり、a:baseは空です。補完できないときは-1を返します。
    二度目に呼ばれたとき、補完する単語のリストを返します。a:findstartは0で、a:baseには補完位置からカーソル位置までの文字列が格納されています。a:baseは参考情報として用意されているだけなので、無視してもかまいません。補完する単語が存在しない場合は空リストを返します。
    補完する単語は単純な文字列にすることもありますが、Dictionaryにしておくと、追加情報を表示できて便利です。Dictionaryの場合、キーとして次のような情報を持つことができます。
  • word
      補完する単語です。省略不可。
  • abbr
      wordのabbrevation(略称)です。このキーが存在する場合、ポップアップメニューにwordの代わりに表示されます。wordが長すぎる場合に省略表示するために使います。
  • menu
      これを設定すると、abbrの後ろに追加情報として表示されます。補完する単語のファイル名などを設定することが多いです。
  • info
      プレビューウインドウに表示する追加情報です。複数行にするときは\nで区切ります。'completeopt'にpreviewがないと無視されます。大量の情報を表示できますが、プレビューウインドウを開く処理やプレビューウインドウの内容を書き換える処理は重いので注意してください。
  • kind
      補完候補の種類を表す1文字です。主にオムニ補完で使用されます。代表的な物は変数を表す’v'や関数を表す’f'、メンバを表す’m'などです。
  • icase
      これが0以外に設定されている場合、Vimは要素の大文字小文字を区別しません。これはポップアップ表示後の要素の絞り込みに関係します。省略すると0になるので、大文字小文字の違いを区別します。アセンブリ言語やBasic以外では省略し、大文字小文字を区別した方がよいです。しかし、'ignorecase'相当の動作を自作補完関数でも行いたい場合、これを1に設定しなければなりません。
  • dup
      これが0以外に設定されている場合、wordが同じでも補完候補に追加されます。省略すると0になるので、重複は無視します。
  • ちなみに、他のキーは存在しても無視されます。
    補完関数についての詳しい解説は:help complete-functionsを参照してください。
    補完関数のサンプル:
    function! CompleteFiles(findstart, base)
        if a:findstart
            " Get cursor word.
            let cur_text = strpart(getline('.'), 0, col('.') - 1)
    
            return match(cur_text, '\f*$')
        endif
    
        let words = split(expand(a:base . '*'), '\n')
        let list = []
        let cnt = 0
        for word in words
            call add(list, { 'word' : word, 'abbr' : printf('%3d: %s', cnt, word), 'menu' : 'file_complete' })
            let cnt += 1
        endfor
    
        return list
    endfunction
        
    最後にまとめとして、補完関数のサンプルを紹介しておきます。これはカレントディレクトリに存在するファイルを補完するサンプルです。’abbr’や’menu’も使用しています。一番最初にファイル名としてマッチする部分を返し、次に呼び出されたときにファイル名にマッチする補完リストを返します。:setlocal completefunc=CompleteFilesとすると使えます。
    ファイル名補完

    ファイル名補完

    注意:
    補完関数が呼び出されるのは補完を行うキーが押されたときだけです。 Vimは英字を入力したときに、ポップアップの絞り込みを自動で行いますが、補完関数を再度呼び出すことはありません。自動で補完を行うためには、CursorMovedIにautocmdを設定し、カーソルが移動したら自動で補完関数を呼び出す必要があります。自動補完は処理が複雑になるので、この章では解説しません。ちなみに<C-h>を押したときは例外で、補完関数が再度呼び出されます。この動作が気になるようなら、
    inoremap <expr><C-h> pumvisible() ? "\<C-y>\<C-h>" : "\<C-h>"
    と.vimrcで設定した方がよいかもしれません。
    またポップアップメニューを生成する処理は重いため、巨大なリストを返却した場合、Vimの動作が止まってしまうことがあります。その点Vim標準の補完はよく考えられていて、処理が重いときには先頭の候補のみをポップアップメニューに表示します。そしてしばらく待っていると、全ての補完候補が表示されます。自作の補完関数でこのような動作を行うためには、complete_check()でキー入力を監視し、complete_add()で少しずつ要素を追加するようにしてください。
    Shougo

    Home > Tags > lv3

    Search
    Feeds
    Links
  • 公式
  • 勉強会
  • 情報
  • コミュニティ
  • Meta
    Etc
    Creative Commons License
    This blog is licensed under a Creative Commons License.

    Return to page top