Sublime Text 2 の自動補完プラグインの作成方法

Sublime Text 2 はデフォルトで自動補完する機能を持っていますが、その補完候補に独自の候補を追加する方法を簡単に書きます。
実装する方法は本当に簡単で、 EventListener を継承して on_query_completions で補完候補をリストで返すだけです。以下にサンプルコードを示します。

import sublime, sublime_plugin

class CompleteTest(sublime_plugin.EventListener):
    def on_query_completions(self, view, prefix, locations):
        return [
            ('neocomplecache', 'neocomplecache'),
            ('unite.vim', 'unite.vim'),
            ('vimshell', 'vimshell')
            ]

このコードを (データディレクトリ)/Packages/User/complete_example.py みたいなファイル名で保存し、適当なファイルで neo と入力すると、 "neocomplcache" が補完候補として表示されます。
on_query_completions で返すリストは、リストの中にタプルが入っている入れ子構造で、タプルは「ポップアップに表示する文字列」、「補完結果として挿入される文字列」の2つの要素からなります。次のような構造です。

[(候補1の表示文字列, 候補1の挿入文字列), (候補2の表示文字列, 候補2の挿入文字列), ...]

単純な単語補完では、表示文字列は挿入文字列と同じ文字列で問題ないでしょう。つまり、タプルの要素は2つとも同じ文字列となります。
ポップアップ表示の補完候補に説明を追加したい場合は、表示文字列に説明を追加すればよいです。

Sublime Text 2 のプラグインの作成方法

Sublime Text 2 は Python により拡張することが可能です。この記事ではプラグインの作成方法について自分が調べたことをメモとして書いておきます。間違っていたらコメントなどでツッコミをお願いします。

APIリファレンス

Sublime Text 2 にアクセスするAPIAPI Reference - Sublime Text 2 Documentation にリファレンスがあるのでそれを参考にしてください。 個人的には提供されているAPIの数は少ない印象です。

プラグインの種類

プラグインは、Sublime Text 2 が提供するベースクラスを継承することで実装します。単に継承するだけでプラグインとして認識されます。
ベースクラスは以下の4種類があります。

  • ApplicationCommand
  • WindowCommand
  • TextCommand
  • EventListener

上の3つはコマンドプラグインです。キーマップで設定して呼び出します。 EventListener はイベント発生時にメソッドがコールバックとして呼び出されます。

コマンドプラグイン

Sublime Text 2 のメニューから Tools→New Plugin を選択すると、以下のコードが未保存のファイルとして開かれます。このコードは、ファイルの先頭に "Hello, World!" という文字列を挿入するプラグインになります。

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

ポイントは、以下の4点です。

  • sublime_plugin モジュールに上記のベースクラスが定義されている
  • ベースクラスを継承したクラスがプラグインとなる
  • クラス名から末尾の"Command"を取ってスネークケースにした文字列がコマンド名となる(後述)
  • run メソッドがコマンド実行関数なので、この関数にコマンドの処理を実装する

sublime_plugin.TextCommand を継承した場合は、 self.view を使って現在編集中のファイルにアクセスできます。同様に、sublime_plugin.WindowCommand を継承した場合は self.window を使ってウィンドウにアクセスできます。 ApplicationCommand と EventListener はフィールドには何も持ちません。
また、 sublime モジュールに定義されている関数も利用できます。利用できるAPIの情報はAPIリファレンスを参照してください。
view や window はコンストラクタの引数として渡されるので、コンストラクタを書く場合は自分で保持するか、ベースクラスのコンストラクタを呼び出します。

自分で保持する場合:

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
    def __init__(self, view):
        self.view = view

    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

ベースクラスのコンストラクタを呼び出す場合:

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
    def __init__(self, view):
        super(ExampleCommand, self).__init__(view)

    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

配置

自作したプラグインは、 Windows の場合は
C:\Users\(ユーザー名)\AppData\Roaming\Sublime Text 2\Packages\User
に .py ファイルを配置することで認識されます。他のOSに関しても、 (データディレクトリ)/Packages/User の下に配置すればよいみたいです。データディレクトリの位置は Basic Concepts — Sublime Text Unofficial Documentation を参照。
User ディレクトリはユーザー用なので、プラグインを配布する場合は別ディレクトリにする必要があると思いますが、その方法は調べていません。

コマンド名

定義したコマンドを呼び出す場合は、コマンド名を指定します。クラス名から末尾の"Command"を取ってスネークケースにした文字列がコマンド名となります。例えば、"CamelCaseCommand" クラスのコマンド名は、"camel_case" となります。
詳細は Plugins — Sublime Text Unofficial Documentation を参照してください。

コマンド実行

定義したコマンドをPythonコンソールから実行してみるには、 まず ctrl+@(Windowsの場合)を押してPythonコンソールを表示して、以下のコードを実行します。

# TextCommandを継承したコマンドを実行
view.run_command('example')

# WindowCommandを継承したコマンドを実行
window.run_command('example')

# ApplicationCommandを継承したコマンドを実行
sublime.run_command('example')
キーマップ

キーボードショートカットに割り当ててコマンドを実行する場合は、自分の設定ファイルに追加します。メニューの Preferences→KeyBinding - User を選択し、開かれたファイルを以下のように編集します。

[
    {
        "command": "example", // コマンド名
        "keys": ["ctrl+m"] // Ctrl+m に割り当て
    },

    // 以下自分用キーマップ設定
    { "keys": ["ctrl+h"], "command": "left_delete" }
]

設定ファイルには、C言語Javascript 形式のコメントを書けます。また、配列の最後の要素の後ろにカンマを付けるとエラーになるので注意してください。

イベントリスナープラグイン

ファイルを開いた時など、特定のタイミングで処理を実行したい場合は EventListener クラスを継承してイベントリスナーを作成します。 継承するだけでイベントリスナーとして登録され、イベント発生時に対応するメソッドが呼び出されます。
以下のコードでは、すべてのイベントを空関数として実装していますが、普通は必要なものだけ実装すれば良いです。ちなみに、自動補完を実装するのに必要な on_query_completions はなぜかAPIリファレンスに載っていませんでした。

class ExampleEventListener(EventListener):
    def on_new(self, view):
        pass
    def on_clone(self, view):
        pass
    def on_load(self, view):
        pass
    def on_close(self, view):
        pass
    def on_pre_save(self, view):
        pass
    def on_post_save(self, view):
        pass
    def on_modified(self, view):
        pass
    def on_selection_modified(self, view):
        pass
    def on_activated(self, view):
        pass
    def on_deactivated(self, view):
        pass
    def on_query_context(self, view, key, operator, operand, match_all):
        return None
    def on_query_completions(self, view, prefix, locations):
        return []

最後に

とりあえずプラグインを実装する最初のステップを書いてみました。実際にプラグインを作るには Sublime Text 2 の API を知らなければなりませんが、それはやる気が出れば続きを書こうと思います。
実は、現在の私の仕事のコードがShift-JISなので、私自身は Subilme Text 2 を実戦投入できていません。 この記事は仕事で使えない悲しみを糧にして書きました。誰かの参考になれば幸いです。
(補足: 現在の Sublime Text 2 は Shift-JIS のファイルを扱えません。)

[Vim] if_python のおかしな挙動

if_python を使っていたらおかしな挙動ではまったのでメモ。
if_python では vim.eval 関数を使えます。 vim.eval は引数に Vim script の式を取って、それを評価した結果を python の値として取得できます。
以下のコードでは、 Vim script の matchstr('', 'a') を評価した結果を表示します。
これを実行した結果として、空文字列('')が表示されることを期待しましたが、 None と表示されました。Vim のバグ?

python import vim
python print repr(vim.eval("matchstr('', 'a')"))
" Noneと表示される

普通に Vim script として評価した場合は期待通りの結果となります。 if_pythonvim.eval したときだけなぜか None となってしまいます。

echo matchstr('', 'a')
" 結果は空文字列となる
echo type(matchstr('', 'a')) == type('')
" 型は文字列型となっている

ちなみに、 matchstr が空文字列以外を返す場合は期待通りの動作となります。

python import vim
python print repr(vim.eval("matchstr('a', 'a')"))
" 'a'と表示される

let g:str = ''
python print repr(vim.eval("g:str"))
" ''と表示される

let g:str = matchstr('a', 'a')
python print repr(vim.eval("g:str"))
" Noneと表示される。一時変数に入れてもやっぱりだめ

回避策

とりあえず、 matchstr の結果に空文字列を結合することで期待通りの動作になりました。

python import vim
python print repr(vim.eval("matchstr('', 'a') . ''"))
" ''と表示される。期待通り

[Vim]エスケープシーケンスを含む vimshell バッファで表示通りにヤンクする

vimshell は、エスケープシーケンスを解釈し、 vimshell バッファ上で色付けして表示することができます。
以下は vimshell で git log --color を実行した図です。コミットのハッシュ番号が黄色になっています。

問題

これをそのままヤンクするとエスケープシーケンス文字もそのままヤンクされ、このようになってしまいます。

^[[33m4391e8e^[[m Implement new cache method.
^[[33m947fb43^[[m - Changed default cache lines in nofile buffer.
^[[33m3232f40^[[m Merge pull request #250 from ujihisa/master
^[[33mec342f3^[[m better english
^[[33m407608b^[[m - Improved FAQ.

vimshell は vim が conceal に対応している場合は conceal 機能でエスケープシーケンス文字を隠していますが、conceal はバッファの実際の文字を変えるわけではないため、ヤンクしたときにエスケープシーケンス文字も一緒に付いてきてしまいます。
これは不便ですね。表示されているものがそのままヤンクされるようになってほしいです。

解決方法

conceal された文字を取得する方法は vim に用意されています。 synconcealed 関数です。この関数は現在のバッファの行番号、桁位置を引数に取り、Conceal 可能リージョンの中にいるかどうかの情報を返します。
この関数を使えば、表示されている文字列を取得できます。これを使ってプラグインを作りました。

設定

プラグインGitHub - chikatoike/concealedyank.vim で公開しています。これを neobundle 等でインストールしてください。
このプラグイン(operator-concealedyank) をマッピングとして公開しています。例として、例えばこのように .vimrc に書きます。

xmap Y (operator-concealedyank)

以上の設定をすることで、 vimshell バッファでビジュアルモードで範囲選択し、Yを押すとエスケープシーケンスを除いた、表示されているままの文字列をヤンクできます。

補足

このプラグイン自体は、 vimshell のみを対象にしたものではなく、 conceal が使われているバッファすべてで機能します。conceal は、例えば :help のバッファで使われています。
また、 conceal は文字を消すだけではなく置き換えることもできますが、置き換えた後の文字列をヤンクできるはずです。(確認していません)
また、手抜きしたため、現在のところオペレーターとして動作しません。ビジュアルモードのみで機能します。例えば、 ノーマルモードで Yiw としても普通に yiw と同じようにヤンクします。これはいずれ直すかもしれません。

Source Mapを使ってJSXで生成したJSファイルからJSXファイルの対応箇所にジャンプする

SourceMapについては以下の記事を参照してください。
http://maruta.be/intfloat_staff/144

基本的にブラウザに搭載されているデバッガで使用することを想定しているようですが、当然他のツールから使用することもできます。というわけで、タイトルの通りのプラグインを作ってみました。

準備

Source Mapの情報を.mappingファイルに出力する必要があります。JSXファイルをコンパイルするときに、 --enable-source-map オプションを使用してください。
ちなみに、.mappingファイルに含まれるファイルパスが相対パスだと、うまく動作しないかもしれません(JSXコマンドが sourceRoot を出力してくれないため。ただ、spec 的には sourceRoot は optional)。そのため、絶対パスで出力したほうがいいかもしれません。絶対パスで出力するには、JSXコマンドに渡すファイル名を絶対パスにすればよいみたいです。

動作

JSXをコンパイルして出力されたJSファイルをVimで開いている状態で、 :SourceMapSwitch コマンドを実行すると、JSXファイルの対応箇所にジャンプします。ただし、JSXが自動的に挿入したコードなど、対応関係がない位置で実行してもジャンプしません。また、逆のJSXファイルからJSファイルへのジャンプはまだ出来るようになっていません(可能なのかもわかっていませんが)。
さらに、Source Mapとしては行内の桁位置を情報として持っていますが、手を抜いたため、行番号しか見ていません。なので、ジャンプする位置は行頭となっています。

実装

このプラグインGitHub - mozilla/source-map: Consume and generate source maps. のコードを流用して作りました。javascriptのコードをVim scriptへベタ移植しています。vital.vimも使用しています。
また、この記事 source map generatorの使い方 - Islands in the byte stream も参考にしました。

JSX以外の言語について

Source Mapは言語非依存の機能だし、このプラグインにもJSX特有の処理はないので、他のSource Map対応の言語でも使用できるはずです。ただし、JSXでしか確認していません。
また、コンパイル後のソースコードの末尾のsourceMappingURLを見てmappingファイルを特定しているので、それも必要です。

非同期で動作するVim用のシンタックスチェックプラグインを開発しています

プラグイン名は activefix.vim です。まだ開発途中ですが、それなりに動作するようになったので公開します。

説明

シンタックスチェックを行うVimプラグインGitHub - vim-syntastic/syntastic: Syntax checking hacks for vim が有名ですが、これはシンタックスチェックを実行している間ユーザーの操作をブロックしてしまいます。
そこで、操作をブロックせずに、syntasticのように多数のファイルタイプに対応したシンタックスチェッカーを開発することにしました。

必要なもの

GitHub - Shougo/vimproc.vim: Interactive command execution in Vim.が必要です。ただし、vimprocがインストールされていなくても動作しないわけではなく、syntasticと同様にファイルを保存するときにブロックして実行するようになります。
プラグイン本体はGitHub - chikatoike/activefix.vim: Syntax checker for Vimにあります。これをpathgenやneobundle等、任意の方法でインストールしてください。

対応する形式

syntasticのソースコードを流用しているので、基本的にsyntasticと同じ形式に対応しています。また、形式毎のオプション変数も同じです。形式毎、というのはたとえばpythonなら g:syntastic_python_checker_args などです。一方g:syntastic_check_on_openはsyntastic本体のオプション変数で、activefix.vimでは適用されません。
また、そのまま流用できなかった形式があり、それらは現状未対応となっています。
使用できない形式は、ada,c,cpp,d,haskell,haxe です。
注意点として、上ではsynasticと同じ形式に対応していると書きましたが、今のところ動作を確認したのは python/pyflakes と ruby/mri だけです。その他の形式は動作しないかもしれません。そのうち確認していきます。

使い方

シンタックスチェックに使う外部プログラムがインストールされている状態であれば、以下のタイミングでシンタックスチェックを開始します。

  • ファイルを開いた時
  • ファイルを書き込んだ時
  • バッファを編集して、一定時間経過した時(updatetimeに設定してある期間)

インサートモードに入っている時でも動作します。ブロックしないので開始した後も操作可能です。
チェックが終了したら、自動的にlocation-listを開いてエラー一覧を表示します。該当行のハイライトもしますが、不完全かもしれません。

注意点

上に書いてあるupdatetimeはデフォルトで4000ミリ秒となっているので、バッファを変更してから開始するまでちょっと待つ必要があります。もっと早く開始したい場合は以下のような感じで短い時間を設定してください。

:set updatetime=1000

その他

上に書いた通り、syntasticのソースコードを流用しています。また、 id:thinca さんのquickrunのソースコードを流用しています。このプラグインの実装について大雑把にいうと、 quickrunの上にsyntasticを乗せた感じです。quickrunのrunnerやhookのようなモジュール構造も参考にした、というよりほとんどそのまま使っています。ただし、quickrunと互換性があるけでもないし、現状は拡張性もないです。

最後に

いまはまだドキュメントを用意できていません。そのうち書きます。

Ubntuの表示がおかしくなる問題

環境:Ubuntu 11.10

現象

最近Ubuntuでログインすると、 「保存したモニターの設定を適用できませんでした」と表示されるようになり、表示がおかしくなる問題が発生するようになりました。
表示がおかしい、というのは具体的にはファイルマネージャ(nautilus)の外観が、フォントサイズが異様に小さくボタンの大きさと釣り合っていない、例えて言うならWindowsのクラシック表示のような状態になりました。(と言ってもわかるかな・・・問題解決前にスクリーンショットを撮るべきでした)

調査

上記の「保存したモニターの設定を適用できませんでした」というメッセージで検索しても、 参考になる情報はヒットしなかったため、英語の情報をあたるしかありませんでした。

[gnome-settings-daemon] Updated Japanese translationに日本語メッセージと対になる英語メッセージがあったので、 それを検索することにしました。

"Could not apply the stored configuration for monitors"で検索すると一番最初にヒットしたページ(Could not apply the stored configuration for the monitor - Ask Ubuntu)に解決策がバッチリ載っていました。次のとおりです。

Remove ~/.config/monitors.xml.

解決

これの通りに ~/.config/monitors.xmlを削除した上でログインしなおすとメッセージは表示されず、nautilusの外観も元通りになりました。