最近の更新

関連


その他いろいろ

MODxでつくる! 最強のCMSサイト カバー
MODxでつくる! 最強のCMSサイト

はやくMODx 2.0でないかなあ

長らく更新をさぼっていました。忙しい…
久しぶりに.vimrcに手を加えたので、その話です。

vimでは頻繁に先頭に移動するggコマンドを使います。しかし、ちょっとまともな(?)ソースコードなどは先頭に大量のコメントがついていて、ggだけではコードの一片も見えず、さらに<C-F>を連打…ということをしがちです。

連打… これはvimmerとして敏感になるワードです。もっと効率よく、本来の目的である「最初の非コメント行」にたどり着けるはず。

そこで、早速ちょっとした関数を書いてみました。

function! GotoFirstEffectiveLine()
  normal gg
  while line(".")<line("$") && (
        \ getline(".") =~ '^\s*$'
        \ || synIDattr(synID(line("."), col("."), 0), "name") =~ ".*Comment$"
        \ )
    normal j0
  endwhile
  exe "normal z\<CR>"
endfunction
nnoremap <silent> gG :<C-U>call GotoFirstEffectiveLine()<CR>

このGotoFirstEffectiveLine()をコールすると、最初の「非空白かつ非コメント行」に移動することができます。vimではgGが空いているので、これに割り当てると覚えやすいでしょう。

~/.vim/template にあるテンプレートを(複数あればリストアップし)ロードするか否か選択できるプラグイン、qtmplsel.vimに式展開機能を追加し、v1.1.0として公開しました。

式展開

テンプレートファイルに含まれる@{@@}@で囲まれた部分をvimの式として評価し、置換します。

例えば、

// \file @{@expand('%:t')@}@
// \date @{@strftime('%Y-%m-%d')@}@
class @{@expand('%:t:r')@}@ {
};

のようなcpp用テンプレートをtestclass.cppの作成時に適用すると、以下のように展開されます。

// \file testclass.cpp
// \date 2009-09-07
class testclass {
};

入手方法

http://www.vim.org/scripts/script.php?script_id=2761から最新版をダウンロードし、~/.vim/pluginか$VIMRUNTIME/pluginに置いてください。

なお、1.0.0の説明はこちらにあります: vimでファイル作成時にテンプレートを選択 qtmplsel.vim

vimでファイルを新規作成する際、ファイルタイプに応じてテンプレート(orスケルトン)を自動的にロードするためには、.vimrcに以下のような設定を加えることが多いと思います。

autocmd BufNewFile *.py 0r ~/.vim/template/template.py

しかし、決まり切った内容を拡張子ごとに用意するのも芸がなく、
またテンプレートを使いたくない場合や、複数のテンプレートから選びたい場合に対応できません。

そこで、~/.vim/template にあるテンプレートを(複数あればリストアップし)
ロードするか否か選択できるプラグイン、qtmplsel.vimを書いてみました。

インストール

  1. qtmplsel.vim - Quick Template Selector : vim online から最新版をダウンロード
  2. ダウンロードしたスクリプトを $VIMRUNTIME/plugin か ~/.vim/plugin に入れます。

使い方

:e コマンドや vim の起動時に存在しないファイル名を入力して新規作成すると、

ファイルタイプ
ft=pythonなら サーチパス/python_*
拡張子
*.pyなら サーチパス/*.py
ファイル名
ファイル名=Makefile なら サーチパス/Makefile_*

に該当するテンプレートを ~/.vim/template から列挙し、一つ以上該当するものがあればリストを表示します。

リスト上では k(上) / j(下) で項目を選択し、 Enter で決定、 q でキャンセル(ロードしない)となります。

また、グローバル変数g:qts_templatedirを設定することで、任意の場所をテンプレートファイルのサーチパスに設定することができます。

使用例

qtmplsel.vim screenshot

リスト表示部分などQuickBufを参考にしました。

vimで{モーション}で指定された範囲を、レジスタ(デフォルト”、指定も可能)で置き換えるオペレータ<C-K>を提供するプラグイン、
regreplop.vimの説明を書きました。
ビジュアル選択中で<C-K>とすると選択範囲が置換されます。

前に直接vim scriptとしてポストしたのとだいたい同じ内容ですが、少し綺麗にしてvim.orgに載せました。

使用例

単語の上で<C-K>iw
単語全体をレジスタ(")で置換
"ayyした後、他の行で"a<C-K><C-K>
行全体を、レジスタaにヤンクした行で置換
vWの後にhlで微調整して<C-K>
選択範囲をレジスタ(")で置換

vimのfoldopenにhorが含まれる場合、閉じた折りたたみの上で右移動しようとすると自動的にzo(折りたたみを開く)してくれます。
しかし逆に、開いた折りたたみの上で左移動してもzc(閉じる)してくれません(多分)。

行頭でhすると折りたたみを閉じるようにする設定を.vimrcに加えてみました。

nnoremap <expr> h foldlevel(getpos('.')[1])>0 &&
      \(getpos('.')[2]==1 \|\|
      \getline('.')[: getpos('.')[2]-2] =~ "^[\<TAB> ]*$" )?"zch":"h"

ちょっと便利。

行単位コメントをトグル/挿入/削除するためのコマンド/オペレータを導入するvimスクリプト、commentop.vimv1.1.1をリリースしました。

1.1.0からの変更内容は、

  • range function (:h function-range-example) を用いてスクリプトを簡素化
  • virtcolを利用し、タブ/スペースが混在していても見た目の幅を考慮してコメント挿入されるように

の2点です。

ソースコードを編集しているときにありがちな状況として、一行の先頭や末尾にはそれほど興味がないが、中央付近を編集したいというものがあります。

例えば、今次に示す行の先頭にカーソルがあるとします。

  cord.set_slot(0, boost::shared_ptr<PipelineCallback<Image> >(new PipelineCallback<Image>()));

ここでshared_ptrの中身を書き換えたい場合、ちょっと思いつくだけでも

  1. 検索する /Pi<CR>
  2. カギ括弧へ移動して右 f<l
  3. Pへ移動する fP
  4. 11番目の単語に移動する 11w
  5. カラム指定 37| (エルでもいいけどパイプ)
  6. lllllllllllllllllllllllllllllllllllll

など様々な方法がありますが、2,3,4,5は確実性・直観性に劣り、1はタイプ数に劣り、6はvim使いとして許されざる入力です。この中なら一番まっとうなのは検索でしょうか。
fやtは明らかに次に何処に行くか分かっていれば使い出がありますが、ひとたび変な場所に飛ばされると混乱するのが常です。(vim歴が浅いだけかも知れませんが)

そこで、ここは自分をはじめとする脳の緩いvimユーザのために、最後の6. lllllllllllllllllllllllllllllllllllllに活路を見出そうと思いました。

37回もlをキーリピートするかわりに、最初に行の中央に移動してからlなりhなりで目的の場所へ行こうという方法です。

幸いvimにはvirtualedit/virtcolという、見た目の文字幅を考慮する仕組みが備わっているのでこれをそのまま利用します。

noremap <expr> gm (virtcol('$')/2).'\|'

元々のgmは画面幅に対して中央になるように、カーソルを水平移動するキーですが、あまり使いそうも無いので上書きしてしまいました。

行単位コメントをトグル/挿入/削除するための[count]コマンドとオペレータ{motion}を導入するvimスクリプト、commentop.vimの説明を書きました。

co

で今いる一行のコメントをトグルしたり、

3cO

で3行コメントアウトしたり、

gOa{

で{}ブロックを丸々コメントアウトしたりできます。

ノーマル ビジュアル オペレータ
トグル(ON⇔OFF) co co go{motion}
挿入 cO cO gO{motion}
削除 c<C-O> c<C-O> g<C-O>{motion}

上記の3×3=9種のキーマップが新たに定義されます。

デフォルトでftpluginの設定するcommentstringを使うので、何も設定しなくても多くの言語(vim, python, perl, ruby, haskell, sh, bash, zsh, java, javascript, Makefile, tex, ..etc.) では適したコメントが挿入されます。一部の自動的に決定できないファイルタイプ(c, cpp, csharp, php, matlab)については、スクリプト中で個別に定義していますし、ユーザが.vimrcでオーバーライドすることも可能です。

詳しくはcommentop.vimを参照してください。

先日はじめてのvimスクリプト(vimでモーション(or選択範囲)をレジスタの内容で置き換えるオペレータ)を書いてみましたが、

Re: vimでモーション(or選択範囲)をレジスタの内容で置き換えるオペレータ @ 7bit - while ("im the true Vim master"); - vimグループ
でアドバイスを頂いたので&自分で他のスクリプト書いて少し慣れたので修正しました。マークやレジスタの退避やらなんやらは不要のような気がしたので除外。

機能的に前回と違うのは、レジスタ指定出来る(しない場合はデフォルトレジスタ)になったこと。v:register退避のためだけに呼ぶ関数がダーティな気はします。

" replace selection with register
function! s:ReplaceMotion(type, ...)
  let reg = empty(s:lastreg) ? '"' : s:lastreg
  let op_mode = 'v'      " default: character
  let marks   = '<>'     " default: visual mode

  if !a:0 " normal mode
    let marks = '[]'
    if a:type == 'line'
      let op_mode = 'V'
    endif
  endif

  exe 'normal! `'.marks[1].'$'
  let paste_cmd = getpos("'".marks[1]) == getpos('.') ? 'p' : 'P'
  exe 'normal! `'.marks[0].'"_d'.op_mode.'`'.marks[1].'"'.reg.paste_cmd
endfunction

function! s:SaveReg()
  let s:lastreg = v:register
endfunction

" default mapping
if !hasmapto('<Plug>ReplaceMotion', 'n')
  nmap <silent> <C-K> <Plug>ReplaceMotion
endif
if !hasmapto('<Plug>ReplaceVisual', 'v')
  vmap <silent> <C-K> <Plug>ReplaceVisual
endif

" export the plugin mapping
nnoremap <silent> <Plug>ReplaceMotion :<C-U>call <SID>SaveReg()<CR><ESC>:set opfunc=<SID>ReplaceMotion<CR>g@
vnoremap <silent> <Plug>ReplaceVisual :<C-U>call <SID>SaveReg()<CR><ESC>:call <SID>ReplaceMotion('', 1)<CR>

~/.vim/plugin/ 以下に放り込んでプラグインとして使います。

a:type==の所が’list’というわけのわからん物になっていたのでlineに直しました

vimユーザなら超絶便利なciw (1単語を削除して挿入)等のオペレータを多用していることと思います。

一応説明すると、vimにおけるオペレータ( c, d など)とは、ノーマルモードで押下したあと「モーション」入力待ちになり、与えられたモーションに対して所定の操作 (削除して挿入, 削除) を行うものです。モーションとしてはhjklやwなどの移動コマンド、もしくはiwやawといったテキストオブジェクトが使えます。

しかしヤンクした内容を貼り付けようとすると、P(貼り付け)してからDb(前方単語削除)などやや面倒な操作が必要になります。 (多分)

そこで、「レジスタの内容で置き換える」オペレータ <C-K> を定義します。オペレータと殆ど同様に使えるv (キャラクタ単位ビジュアル)でも <C-K> により「現在選択中の領域」をレジスタで置き換えられるように、vnoremapも併せて定義します。

nnoremap <silent> <C-K> :set opfunc=ReplaceMotion<CR>g@
vnoremap <silent> <C-K> :<C-U>call ReplaceMotion('', 1)<CR>
function! ReplaceMotion(type, ...)
  let sel_save = &selection
  let &selection = "inclusive"
  let reg_save = @@
  let mark_save = getpos("'a")

  if a:0 " visual mode
    silent exe "normal! '>$"
    if getpos("'>") == getpos('.')
      silent exe 'normal! `<"_d`>"_d$"0p`<'
    else
      silent exe 'normal! `>lma`<"_d`a"0P`<'
    endif
  elseif a:type == 'char' " char motion
    silent exe "normal! ']$"
    if getpos("']") == getpos('.')
      silent exe 'normal! `["_d`]"_d$"0p`['
    else
      silent exe 'normal! `]lma`["_d`a"0P`['
    endif
  endif

  let &selection = sel_save
  let @@ = reg_save
  call setpos("'a", mark_save)
endfunction

これを.vimrcにコピペすれば、

<C-K>3w  → 現在位置から3単語をレジスタの内容で置換
<C-K>i(  → 現在のカーソル位置を含む括弧内部をレジスタの内容で置換
(ビジュアル選択)<C-K> → 選択部分をレジスタの内容で置換

などの操作が可能になります。

デモの動画を撮ってみました。

  1. 「test, yank, words」をヤンクして<C-K>i(によりfunctionの引数内を置換
  2. vimをヤンクして<C-K>iwによりemacsを置換
  3. ビジュアル選択中に<C-K>を押下して選択部分をレジスタで置換

スクリプトの解説をすると、

nnoremapの方では、ノーマルモードでの<C-K>をオペレータとして定義しています。これはオペレータを実行するコマンドg@を利用していて、g@{motion}とするとモーションの開始/終了範囲がマーク[、]として設定された上でopfuncに指定したコールバックが呼ばれます。
<C-K>ではopfuncを設定し、{motion}の所はユーザに任せることで独自のオペレータ定義を可能にしています。

vnoremapではオペレータではなく、単にマーク<, >を利用して同様の動作を実装しています。if文の中でさらに分岐があるのは、削除オペレータdがexclusive=終端を含まない開区間操作であるためです。似たようなことを4つも書いているのであまり綺麗ではありませんが…

追記: 20090706
Re: vimでモーション(or選択範囲)をレジスタの内容で置き換えるオペレータ @ 7bit - while ("im the true Vim master"); - vimグループ

で突っ込まれた内容を踏まえるなどしてちょっと綺麗 & レジスタ指定を可能に しました。

→ vim レジスタで置換するオペレータ 2 @ 7bit

また、削除オペレータがexclusiveという表現は正しくないようで、オペレータのペンディングに対してexclusive/inclusiveがある模様。dwとdvwで比較すると、dvwの方は終端も含んでいる。たぶんそういうことかな…

次のページ »