2008/01/31


Powered by Vim 会社でちらりと覗いたデスクトップにvimを見つけました。少しずつですが何故か最近vimユーザが増えている気がしています。
vimは開発の場で見かけるとカッコよく映りますね。vimユーザでない方から見ると「何だか分かんない内にソースコードが出来上がって行く」と神懸り的な物に見えるようです。
今日はこれまでもvimを使って来たけどなかなか上達しない方、またはこれからvimを覚えようとされているvimビギナーの方に送る、vimの使い方を上達させる5つのオキテ。

キーボードは見るな

まずは基本。要はブラインドタッチしろという意味ですが、なぜvimのオキテなのかというと...
vimは頭で考えるテキストエディタです。ブラインドタッチが出来ていないと、vimに命令を与える際に思考を止めてしまい学習出来なくなります。以下のオキテをブラインドタッチよりも先に学習してしまう事は、ある意味無駄な学習ともなり得ます。

マウスを触るな

vimに限らずU*IX系のテキストエディタでは、マウスを使わず全ての編集が出来る様になっています。
マウスを探す事で思考を止めてしまい、せっかく思いついたかもしれない素晴らしいアイデアを自ら消し去ってしまうかも知れません。
海外の方がvimを触っている動画を幾らか見たことがありますが、彼らは常に喋るようにタイプしコタツの上にあるテレビのチャンネルを探す様な感覚でテキストを検索しています。
vimを上達させる為には見たかったテレビ番組の内容を喋りながらもチャンネルを変えられる...くらいの当たり前さをテキスト編集スキルとして身に着けなければなりません。

脳内で英文を作れ

vimは基本的にカウント、モーション、オブジェクトという3つの要素を用いてvimに命令することで編集を行います。
例えば行を3行消すのに、SHIFTキーを押しながら3回矢印キーを押してDELキーを押す...という通常のテキストエディタとは違い、「消す 3 下」と言った少し英語に似た脳内文章を自分で表現しvimに命令しなくてはなりません。この脳内文章を如何に効率良く作れるかがvim上達への近道となるのです。
現在のカーソル位置続く単語3つを消して別の物に置き換えたい場合、vim使いならば脳内で「変える 3 単語」という文章が出来上がり「c3w」とタイプされるのです。

短いショートカットキーを知れ

良く出来たテキストエディタでは、良く登場する入力方法を予め決められたキーボードショートカットとして割り当てられています。
例えばプログラムのソースコードを編集中に、カーソルがある次の行から新たな行として入力を開始したい場合、通常のテキストエディタならば<End>を押して行末まで移動し<Enter>を押して入力を開始しなければなりませんが、vimだとノーマルモードから「o」だけ。
この辺りの気配りさが如何にもコード書き専用エディタと言われる由縁なのかもしれません。

人のテクニックを盗め

これはテキストエディタに限らず、何にでも言える話ですがオープンソース界隈では自らの設定ファイルまでも公開したがる人がワンサカいます。
まぁ私もその一人ですが...
vimは一人で「:help」から勉強するには多すぎる程のノウハウが詰まったテキストエディタです。自分で思い付かなかった素晴らしいテクニックは、ありがたくら盗んでしまいましょう。

vimはどれ程使い込んでも満足する事が出来ないテキストエディタです。
どんなに上達しても時に人の設定ファイルや編集方法に驚かされたりします。
頭で考えるテキストエディタだからこそ、人それぞれの編集方法が生まれるのでしょうね。
vimを使い始めてみようと思われている方、このどっぷりと深い世界にのめり込んで見ませんか?
Posted at by




最近Plaggerにハマってるからって、やる事メチャメチャやな...

Plaggerのフィードをvimの「--remote-send」を使って転送するPlaggerプラグインを作りました。
vimには、リモートサーバと言う機能があり以下のオプション/コマンドで別に起動しているvimへコマンド/式/ファイルを送信する事が出来ます。

--remote [+{cmd}] {file} ...
別のサーバへファイルを送信します。
+{cmd}により開く際にコマンドを併用出来ます。
--remote-silent [+{cmd}] {file} ...
--remote と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-wait [+{cmd}] {file} ...
--remote と同等ですが、送信したvimが終了するまで待機します。
vimで編集した結果をどこかに送るシェルスクリプト等では使えるかもしれません。
--remote-wait-silent [+{cmd}] {file} ...
--remote-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab [+{cmd}] {file} ...
--remote と似ていますが、指定されたファイルを全て別タブで開きます。
--remote-tab-silent [+{cmd}] {file} ...
--remote-tab と同等ですが、エラー等が発生してもメッセージ出力されません。
--remote-tab-wait [+{cmd}] {file} ...
--remote-tab と同等ですが、送信したvimが終了するまで待機します。
--remote-tab-wait-silent [+{cmd}] {file} ...
--remote-tab-wait と同等ですが、エラー等が発生してもメッセージ出力されません。
--servername {name}
リモートサーバを名称で指定します。リモートサーバ一覧を取得するには --serverlist を使用します。
--remote-send {keys}
リモートサーバにキーを送ります。基本的にnormalコマンドで与える引数と同等です。
--remote-expr {expr}
リモートサーバに式を送り、その結果を出力します。
--serverlist
リモートサーバ一覧を出力します。

その他、スクリプトから使える
remote_expr({server}, {string} [, {idvar}])
{server}に対して{string}という式を評価して貰います。
:echo remote_expr("gvim", "2+2") と書くと4が表示されます。
remote_foreground({server})
{server}をフォアグラウンドにします。 remote_expr({server}, "foreground()") と同等の機能ですね。
等がありますので、vimを起動しなくても色々な事が出来ます。

今日は、はてなブックマークから「vim」タグが付いているエントリをvimに転送するPlaggerプラグインを作りました。
確認はWindowsでしかしてませんが、X Windowが動いている環境や、Mac OS X等でも動くかと思います。
※あと、フィードタイトルにエスケープ文字や「<ESC>」等といったvimのキー識別ぽい物があると動かない可能性があります。 package Plagger::Plugin::Publish::Vim;
use strict;
use base qw( Plagger::Plugin );

our $VERSION = '0.01';

use Encode;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'plugin.init' => \&initialize,
        'publish.entry' => \&add_entry,
    );
}

sub initialize {
    my($self,$context) = @_;

    $self->{vim} = $self->conf->{vim};
    $self->{vim} = "vim" if (!$self->{vim});
    $self->{server} = $self->conf->{server};
    if (!$self->{server}) {
        open(IN, "vim --serverlist|");
        $self->{server} = <IN>;
        close(IN);
        chomp $self->{server};
    }
    open(IN, sprintf("%s --servername %s --remote-expr \"&encoding\"|",
        $self->{vim}, $self->{server}));
    $self->{encoding} = <IN>;
    print $self->{encoding}."\n";
    close(IN);
    my $command = sprintf("%s --servername %s --remote-send \"<C-\\\><C-N>:new<CR>i\"",
        $self->{vim}, $self->{server});
    system($command);
}

sub add_entry {
    my($self, $context, $args) = @_;

    $context->log(info => $self->{server});
    my $command = sprintf("%s --servername %s --remote-send \"%s\n\t%s\n<C-W>\"",
        $self->{vim}, $self->{server},
        encode($self->{encoding} || 'utf8', $args->{entry}->{title}),
        encode($self->{encoding} || 'utf8', $args->{entry}->{link}),
    );
    system($command);
}

1;
で、YAMLはこんな感じ
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/t/vim?mode=rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Vim
    config:
      #vim: vim7
      #server: GVIM1
実行結果は
vim_plagger

またくだらんもん作ってしもた...
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