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




Googleが提供している開発用tagツールGoogle GTags(gtags)をWindowsで動かす手順です。
以下、パッチを上げてますが、接続タイムアウトのalarmを無効化しています。必要だと思われる人は、SIGALRMを別の方法で実装してください。
まず、サイトからsvnで最新ソースを取得します。
C:\TEMP> svn checkout http://google-gtags.googlecode.com/svn/trunk/ google-gtags 次に、以下のパッチを当てます。
Index: regexp.h
===================================================================
--- regexp.h    (revision 57)
+++ regexp.h    (working copy)
@@ -4,7 +4,9 @@
 #ifndef TOOLS_TAGS_REGEXP_H__
 #define TOOLS_TAGS_REGEXP_H__
 
+extern "C" {
 #include <regex.h>
+}
 #include "tagsutil.h"
 
 class RegExp {
Index: configure
===================================================================
--- configure   (revision 57)
+++ configure   (working copy)
@@ -10,7 +10,7 @@
 if [ ! -e "scons/scons.py" ]; then
     pushd scons > /dev/null
     echo "Unpacking scons..."
-    tar xzvf scons-local.tar.gz > /dev/null
+    gzip -dc scons-local.tar.gz | tar xv > /dev/null
     if [[ "$?" == 0 ]]; then
    echo "Done"
     else
Index: gtags.cc
===================================================================
--- gtags.cc    (revision 57)
+++ gtags.cc    (working copy)
@@ -49,6 +49,10 @@
 #include "tagsoptionparser.h"
 #include "tagsrequesthandler.h"
 
+#ifdef WIN32
+# include <winsock2.h>
+#endif
+
 DEFINE_STRING(tags_file, "", "The file containing the tags information.");
 
 
@@ -78,6 +82,11 @@
     return -1;
   }
 
+#ifdef _WIN32
+  WSAData wsadata;
+  WSAStartup(MAKEWORD(2,0), &wsadata);
+#endif
+
   logger = new StdErrLogger();
 
   tags_request_handler = new TagsRequestHandler(GET_FLAG(tags_file),
@@ -90,4 +99,9 @@
 
   delete tags_request_handler;
   delete logger;
+
+
+#ifdef _WIN32
+  WSACleanup();
+#endif
 }
Index: gtags.py
===================================================================
--- gtags.py    (revision 57)
+++ gtags.py    (working copy)
@@ -175,11 +175,11 @@
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      address = socket.getaddrinfo(host, port, socket.AF_INET,
                                socket.SOCK_STREAM)
-     signal.signal(signal.SIGALRM, alarm_handler)
-     signal.alarm(CONNECT_TIMEOUT)
+     #signal.signal(signal.SIGALRM, alarm_handler)
+     #signal.alarm(CONNECT_TIMEOUT)
      s.connect(address[0][4])
-     signal.alarm(0)
-     signal.alarm(DATA_TIMEOUT)
+     #signal.alarm(0)
+     #signal.alarm(DATA_TIMEOUT)
 
      # need \r\n to match telnet protocol
      s.sendall(command + '\r\n')
@@ -191,7 +191,7 @@
      while data:
        buf.write(data)
        data = s.recv(1024)
-     signal.alarm(0)
+     #signal.alarm(0)
      return buf.getvalue()
 
 # Instance of connection_manager that forwards client requests to gtags server
Index: tags_logger.h
===================================================================
--- tags_logger.h   (revision 57)
+++ tags_logger.h   (working copy)
@@ -67,6 +67,7 @@
   }
 };
 
+#undef ERROR
 const int INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3, NUM_SEVERITIES = 4;
 
 // uncomment out the standard google logger
Index: socket_server.cc
===================================================================
--- socket_server.cc    (revision 57)
+++ socket_server.cc    (working copy)
@@ -18,10 +18,14 @@
 
 #include <assert.h>
 #include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <unistd.h>
+#ifdef WIN32
+# include <winsock2.h>
+#else
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <unistd.h>
+#endif
 #include <string>
 
 #include "tagsprofiler.h"
@@ -29,6 +33,12 @@
 #include "tagsrequesthandler.h"
 #include "socket_server.h"
 
+#ifdef WIN32
+typedef int socklen_t;
+#define write(x, y, z) send(x, y, z, 0)
+#define close(x) closesocket(x)
+#endif
+
 extern GtagsLogger* logger;
 
 DEFINE_INT32(tags_port, 2222, "port to tags server");
次にgoogle-gtagsのソースルートにregex for win32を解凍します。これで、gnu_regex_distというフォルダが出来ます。
zsh for win32等をお持ちの方ならば、そのまま sh configure
お持ちでない方でもconfigureの中身を見ると大体検討が付きます。
Makefile.w32は以下の通り。mingw32-makeでビルドします。
all: gtags.exe

gtags.exe : filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc gnu_regex_dist/regex.c
    gcc -DHAVE_STRING_H -c -I. gnu_regex_dist/regex.c
    gcc -I. -Ignu_regex_dist -o gtags.exe filename.cc gtags.cc sexpression.cc strutil.cc symboltable.cc tagsoptionparser.cc tagsprofiler.cc tagsrequesthandler.cc tagstable.cc socket_server.cc regex.o -lstdc++ -lws2_32
あとはspiritlooseのはてなダイアリー - [Vim]Google Tags(GTags)を試してみた(with Vim)を参考に lang_call_to_server = {
  "c++" : { "definition" : [("localhost", 2222)],
            "callgraph" : [] },
  "java" : { "definition" : [],
             "callgraph" : [] },
  "python" : { "definition" : [],
               "callgraph" : [] } }
等と設定して、vimrcに exec "set runtimepath+=".escape(globpath(&runtimepath, 'gtags'), ' ')
nmap <C-]> :call Gtag(expand('<cword>'))<CR>
を追加します。
これで設定はOKです。次に以下の手順でソースツリーでtagsファイルを生成します。
python c:/temp/google-gtags/gentags.py --etags=c:/emacs/bin/etags.exe --rtags=c:/temp/google-gtags/rtags.py --etags_to_tags=c:/temp/google-gtags/etags_to_tags.py ここではetagsとしてemacsに含まれるバイナリを使用しましたが、ctags.exeをetags.exeにリネームしても同様に使えます。
これで「cpp.tags.gz」というファイルが生成されますので、あとはサーバを起動します。
c:/temp/gtags.exe --tags_file ./cpp.tags.gz --tags_port 2222 --gunzip 起動したらvim(gvim)を起動して、タグジャンプしたい部分でC-]します。
spiritlooseさんの言うように確かに速いですね。共同開発等では便利かもしれませんね。

オフトピですが、このライブラリに含まれるSconsというビルドツールについて今度調べてみようかと思います。
Posted at by