2008/01/31


意外と知られていないんですね。ビジュアル選択って
vimで選択範囲を置換
うわーん。これやりかたかったんだよー!知らなかったよー!
Powered by Vim 同じネタを説明しても面白くないので、今日はビジュアル選択後に行うアクションについて...
「'<'>」の後には、「s(substitute)」だけでなく「g(global)」や「v(vglobal)」を書く事もでき、行単位でのビジュアル選択(正式にはlinewise-visual選択)を行った行に対して絞込みを行い、その上で置換を行う事も出来ます。
例えば 問題
※以下の阿藤について間違っている物に×を入れよ
(  ) 俺は阿藤会だ
(  ) 俺こそ阿藤下位だ
(  ) 僕も阿藤回だ
(  ) リッチに阿藤買いだ
(  ) 実は私の従兄弟が阿藤快だ
(  ) 叔父が阿藤飼いだ
(  ) 海で阿藤貝を拾った
(  ) お前、阿藤甲斐性あるな
こんなテキストならば、「(  )」が付いている行を選択して
:'<,'>v/阿藤快/s/(  )/(×)/g
でおしまい。
※「:」を押した時点で「'<,'>」は補完されます。
意味は、ビジュアル選択している部分から「v」で「阿藤快」の含まれない行を抜き出し、その結果に対して「s」で「(  )」を「(×)」に置換するという物です。

また、例えばテキストファイルに書かれた以下の様なスケジュール一覧があったとします。
予定表
1. 09:00 出社
2. 10:00 会議(午前の部)
3. 12:00 昼休憩
4. 13:00 会議(午後の部)
     ここで仕様を煮詰める
5. 16:00 内部ミーティング
6. 16:30 資料作成
7. 17:30 客先にて打ち合わせ
昼休憩の後に項目番号4として「13:30 来客予定」を入れたくなったらどうしますか?
4から7までを一つずつ足して行きますか?
vimなら4で始まる行から7で始まる行までを選択して
:'<,'>g/^\d/exec "normal 0\<c-a>" とすれば4以降が1個ずつずれるので、5の上から4で書き始めればよいのです。
vimではノーマルモード時、数値の上で<c-a>を押すと数値がインクリメントされる(<c-x>でデクリメント)という機能があるので、これを利用して先頭行の数字に対して<c-a>キーを送信しています。

さらに会議(午後の部)の開始が1時間が遅れるとなった場合、「13:00」を含む行から「17:30」を含む行まで選択して
:'<,'>g/^\d/s/\(\d\d\):/\=printf("%02d:", submatch(1)+1)/ でおしまい。
先頭が数字で始まる行に対して「s」で「数値+数値+":"」を検索し値に1足し、printfでゼロ付き文字にして置換しています。
※printf()はvim7でしか動きません。
少し工夫すれば30分足して60分になった物は1時間繰り上げる...なんて事も出来るでしょうね。

vimってパズルみたいで面白いですよね。方法はこれだけでなく、人によっては私よりも手数の少ない方法を使われる方もいます。
凝ると色んな事が出来ますので、皆さん凄いの見つけたら教えて下さいね。
Posted at by




vimにはexplore.vimというスクリプト(現在はnetrw.vimに統合)が付属しており # vim /usr/include 等と実行すると、vimがファイラとして起動します。同様にコマンドラインから :e /usr/include と実行しても同じ結果になります。コマンド単体としてもExploreとして起動出来ます。
このExplore実は結構よく出来ていて、以前ご紹介した「男は黙ってvimでリモート編集」の応用として :e ftp://ftp.vim.org/pub/vim/
Enter username: anonymous
Enter Password: *********
でフォルダ閲覧出来ます。(*********はanonymous) " ============================================================================
" Netrw Directory Listing                                        (netrw v109)
"   ftp://ftp.vim.org/pub/vim/
"   Sorted by      name
"   Sort sequence: [\/]$,\.h$,\.c$,\.cpp$,\.[a-np-z]$,*,\.info$,\.swp$,\.o$\.obj
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:exec
" ============================================================================
../
./
MIRRORS
README
amiga
atari
be
beanie.gif
doc
extra
faq.html
farsi
green_ball.gif
index.html
ftp://ftp.vim.org/pub/vim/ [RO]                                       1,1     2%

また、Exploreではファイル名にカーソルを合わせて「x」をタイプすると拡張子に合わせてアプリケーションが起動します。
例えばWindowsでファイル名が「勤務表.xls」であればExcelが起動します。

この「x」で外部アプリケーションが起動する機能、現状はWindows、GNOME、KDEをサポートしています。
ちょっとソースを見たところ、netrw.vimには元となったexplore.vimに昔々に私が入れ込んだ「explFileHandler」が別名「netrwFileHandlers」として取り込まれてました。
ただ、「netrwFileHandlers」は私が元々想定していた単一のユーザ関数ではなく「netrwFileHandlers#Invoke」という関数でファイル種別毎に分別され、ファイル種別毎のスクリプト関数が実装されていました。
これをグローバルで宣言すれば自分独自の設定も出来るという仕組みです。 let g:netrw_browsex_viewer='-'
" エディタであるvimから秀丸起動して、何やってんだか...
function! NFH_txt(file)
    " netrwFileHandlers.vimの不具合回避?
    let f = substitute(a:file, '^\([A-Z]\)COLON', '\1:', '')

    exe "silent !start c:/progra~1/hidemaru/hidemaru.exe \"".f."\""
    return 1
endfunction

こんな感じのユーザ関数を作れば例えば.plや.shでperlやbashを起動したりする事も出来ます。
コード内にある「netrw_browsex_viewer」ですが、"-"に設定すると上記のようなユーザ/スクリプト関数を呼び出す機能として動作しますが、実行可能なコマンドを設定するとそのまま起動してくれるようにもなっています。
これを使用すれば、現状Windows、GNOME、KDEしかサポートしていないnetrw.vimでも、Mac OS Xに対応する事が出来ます。
私はMac OS Xを持っていないので確認出来ませんが、MacWiki - OSXの固有コマンドを見ると、Mac OS Xではコマンドラインからファイルを開く「open」コマンドがあるらしいので let g:netrw_browsex_viewer = 'open'
とvimrcに設定しておけば、Exploreから「x」をタイプする事でファイル種別に応じたアプリケーションが起動出来るかと思います。
※どなたか動作報告頂ければ、オフィシャルにマージして貰えるかもしれません。

その他、netrw.vimが使用するftp/sshのコマンドライン等の設定は :NetrwSettings
とすれば、設定画面が表示されますので、色々カスタマイズして見ると面白いかもしれませんね。

mattn the vim explorer
Posted at by




昨日の記事「Publish::Jaikuをでっちあげた」でご紹介したソースコードは、最終的にはCodeReposにcommitする事にしました。
で、その際CodeReposのトップページで読んだコミットルール
Commit messege rule
svn ci -m "lang/LanguageName/BigProjectName: Commit messege."
svn ci -m "lang/LanguageName/misc/ScriptName: Commit messege."
svn ci -m "dotfiles/SoftwareName/Username-Filename: Commit messege."
を守ってcommit時にファイル名を一覧しました。
で、悩んだのが複数のファイルをcommitする場合。
色々な人のcommit logを見てたら、皆さん lang/LanguageName/BigProjectName,
lang/LanguageName/misc/ScriptName,
dotfiles/SoftwareName/Username-Filename:
  Added.
といった書き方をされていました。
でもこれってU*NIXなら"pwd"して、コピれば簡単ですが、Windowsの場合は"¥"になったり、部分的にチェックアウトしている場合には"pwd"で取得出来るものはなかったりと不便だったりします。
で、なんか楽出来ないかなと思いまして今回はこのcommit対象ファイル一覧を"svn commit"時のコメントとして出力してくれるvimscriptをご紹介します。
※というか、さっき作りました。

仕組みとしては、"svn info"を実行すると出力される
URL : http://host/root/path/to/dir
Repository Root : http://host/root
という部分の2行の差、「path/to/dir」を取得し、svn commit実行時にエディタに表示されている
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
のファイル名部分の先頭に上記フォルダ「path/to/dir」を付与し path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
というコメントを生成します。
あとはこれをFileTypeがsvnの場合に動作するようにautocmdを作ればsvn commit時に
path/to/dir/docs,
path/to/dir/docs/file1.txt,
path/to/dir/docs/file2.txt,
path/to/dir/docs/file3.txt:
 
--This line, and those below, will be ignored--

A    docs
A    docs/file1.txt
A    docs/file2.txt
A    docs/file3.txt
という画面が現れます。カーソルも":"の次の行に移動しますので、そこからコメントを書くと事が出来ます。
ツールは横着から生まれる物ですね!

vimscriptのコードは以下の通り
"=============================================================================
" File: svn_file_comment.vim
" Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
" Last Change: Fri, 12 Oct 2007
" Version: 0.1
"-----------------------------------------------------------------------------
" when editing comment for 'svn commit',
"  it append svn comment like following
"
"   root/path/to/dir/docs,
"   root/path/to/dir/docs/file1.txt,
"   root/path/to/dir/docs/file2.txt,
"   root/path/to/dir/docs/file3.txt:
"   <= cursor
"   --This line, and those below, will be ignored--
"   A    docs
"   A    docs/file1.txt    
"   A    docs/file2.txt    
"   A    docs/file3.txt    
"-----------------------------------------------------------------------------

function! AppendCommitFiles()
  let lstart = search("^--", "n")
  let lend = line("$")
  if line(".") > 1 || lstart != 2
    return
  endif
  let oldlang=$LANG
  let $LANG="C"
  let lines=system("svn info")
  let $LANG=oldlang
  let url=substitute(lines, '.*\nURL: \([^\x0A]*\).*', '\1', '')
  let root=substitute(lines, '.*\nRepository Root: \([^\x0A]*\).*', '\1', '')
  if match(url, root) != 0
    return
  endif
  let basedir=substitute(strpart(url, strlen(root)), '^\/*', '', '')
  let lcur = lstart
  let lines = ""
  let mx = '^\s*[A-Z]\s\+\([^$]\+\)$'
  while lcur <= lend
    let line = getline(lcur)
    if line =~ mx
      let lines .= basedir."/".substitute(line, mx, '\1', '')."\<NL>"
    endif
    let lcur = lcur + 1
  endwhile
  let lines = substitute(lines, '\n.', ',&', 'g')
  let lines = substitute(lines, '\n$', ':&', '')
  call cursor(0)
  let value = getreg("a")
  let type = getregtype("a")
  call setreg("a", lines, "c")
  execute 'normal! "ap'
  call setreg("a", value, type)
  silent! /^$
endfunction
autocmd FileType svn call AppendCommitFiles()
例によって、このソースもCodeReposのコノ辺に置く予定です。

追記
ちょこっと修正
Posted at by