lazy_importとインテリセンス

昨日に引き続き、微妙にVim関連のネタです。

最近は、PythonRubyみたいなLLでもちゃんとインテリセンスで補完できるようなIDEが増えてきました。
僕も実年齢はともかく精神的にはゆとりなので、インテリセンスが無いとまともにコードを書ける気がしません。
しかし、Bazaarのソースでは"lazy_import"という関数が多用されていて、こいつが補完のジャマをします。

lazy_import

lazy_importというのは、こんなやつです。

from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
    import os, re
    from bzrlib.workingtree import WorkingTree
    from bzrlib.plugins.qbzr.lib.util import (
        get_set_tabstop_width,
        QBzrWindow,
    )
""")

lazy_importの中に書かれたimportは、その場では実行されず、本当にそのモジュールが必要となるまでimportが遅延されます。bzrコマンドの起動を速くするための仕組みですね。
これはこれで便利なんですが、当然のことながらIDEはこれがimport文であるとは認識してくれないので、これらのモジュール内のクラスや関数を呼び出そうとしてもちゃんと補完してくれません。

コレを何とかできないかと思って、VimPython用補完プラグインであるpythoncomplete.vimをいじってみました。

pythoncomplete.vim

pythoncomplete.vimの中を見てみると、vimcomplete functionの中で、バッファからソースコードを取得しています。
ここで、取得したソースコードの中のlazy_import呼び出しを普通のimportに書き換えてしまえば、補完が効くようになりそうな気がします。

def vimcomplete(context,match):
    global debugstmts
    debugstmts = []
    try:
        import vim
        def complsort(x,y):
            # ・・・(略)・・・
        cmpl = Completer()
        cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
        all = cmpl.get_completions(context,match)
        all.sort(complsort)
        # ・・・(略)・・・
    except vim.error:
        dbg("VIM Error: %s" % vim.error)

で、やってみました。正規表現を使って、「lazy_import("""」と「""")」を取り除いています。
別に完璧である必要はないので、正規表現はかなりいいかげんです。

--- ./pythoncomplete.vim.orig	Thu Dec 15 06:37:36 2011
+++ ./vimfiles/bundle/pythoncomplete/plugin/pythoncomplete.vim	Thu Dec 15 22:59:31 2011
@@ -96,6 +96,20 @@
 def showdbg():
     for d in debugstmts: print "DBG: %s " % d
 
+import re
+re_lazy_import = re.compile(ur'''
+^lazy_import\s*\(globals\(\)\s*,\s*("""|\'\'\') # lazy_import(globals(), """
+(?P<contents>[^"\']*)                           # inside here document
+\1\s*\)                                         # """)
+''', re.M|re.X)
+re_indent = re.compile(ur'^[ \t]+', re.M)
+
+def strip_lazyimport(source):
+    def unindent(text):
+        return re_indent.sub('', text)
+    
+    return re_lazy_import.sub(lambda m:unindent(m.group('contents')), source)
+
 def vimcomplete(context,match):
     global debugstmts
     debugstmts = []
@@ -121,7 +135,7 @@
             except:
                 return 0
         cmpl = Completer()
-        cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
+        cmpl.evalsource(strip_lazyimport('\n'.join(vim.current.buffer)),vim.eval("line('.')"))
         all = cmpl.get_completions(context,match)
         all.sort(complsort)
         dictstr = '['

おお、補完が効くようになりました。

何かもっといい方法があるような気もしますが、しばらくこれでやってみようと思います。

別のアプローチ

もっと単純な方法としては、lazy_importの呼び出しをこんな風に書き換えちゃうようなVimプラグインなりを作るという手もありますね。pre_commitフックで"##UNCOMITTABLE##"という文字列が含まれるファイルのコミットは拒否するようにして。

##UNCOMITTABLE## lazy_import(globals(), """
import os, re
from bzrlib.workingtree import WorkingTree
from bzrlib.plugins.qbzr.lib.util import (
        get_set_tabstop_width,
        QBzrWindow,
)
##UNCOMITTABLE## """)

ところで、neocomplcacheはすばらしいですね。