Sublime Text 2 でアニメーションGIFを再生する

この記事は Sublime Text 2 Advent Calendar 2012 の 15日目です。

Sublime Text 2 Advent Calendar 2012 - Adventar

VimはXPM形式の画像ファイルを画像として表示することができます。これは、テキストエディタシンタックスハイライトを利用して、1文字を1ピクセルとみなして描画することで実現しています。
そして Sublime Text 2 でも当然、同じ方法で画像を表示することが出来ます。今回はさらにアニメーションGIFを再生してみました。

再生時の動画

以下のGIF動画は実際に再生しているところを録画したものです。nyancat の動画を再生しています。nyancat をご存知の方も多いでしょう。

アニメーションGIFを再生する手順は以下の通りです。 上の動画は手順2から録画しています。

  1. 後述するパッケージ(SublimeMediaPlayer)を導入する
  2. コマンドパレットで "MediaPlayer: Play GIF Select" を選択する
  3. 画面下部にテキストボックスが表示されるので、再生したいGIFファイルのパスを入力してEnterを押す
  4. しばらく待つと再生が始まります。
  5. アニメーションを終了するには再生しているタブを閉じてください。

nyancatのアニメーションGIFはパッケージに同梱していないので、 LOL Comics - ホーム | Facebook から入手してください。

パッケージ 「SublimeMediaPlayer」

GitHub - chikatoike/SublimeMediaPlayer
SublimeMediaPlayer は上記のURLにあるので、 git clone するなり zip で落すなりしてインストールしてください。
急いで作ったのでけっこうひどいコードです。
今のところアニメーションGIFの再生しかできませんが、時間がなかっただけで、jpegpngといった静止画の表示も技術的には可能です。
GIFアニメーションを読み込むために pyglet というライブラリを使っています。
また、 "MediaPlayer: Play GIF Select" 以外に、"MediaPlayer: Play GIF dinosaur.gif" というコマンドも用意していて、これはリポジトリに同梱している dinosaur.gif を再生します。このgifファイルはpygletのリポジトリにあったものです。

対応するプラットフォーム

pyglet がWindows/MacOSX/Linux に対応しているので、全てのプラットフォームで動作すると考えられますが、Windowsでしか動作確認していません。

おわりに

明日のSublime Text 2 Advent Calendar は yudai tachibana さんです。

IMESupportが64bit版に対応しました。

https://github.com/chikatoike/IMESupport
これまでWindows 64bitでは、Sublime Text 2のx86版では動作するものの、x64版では動作しない状態でした。
今回正式にx64版のSublime Text 2に対応しました。
動作確認はWindows 8 64bitのみで行なっているので、Windows XP/Vista/7 では確認していませんが、多分動作するでしょう。
また、以前のバージョンには、プロジェクト移動でクラッシュするという問題があったのですが、これもx64対応と同時に修正しました。

また、現在Package Controlへ登録申請中です。登録されたらまた報告します。

実装方法の変更

以前の記事で、ウィンドウのサブクラス化を使っていると書きましたが、これは SetWindowsHookEx を使ってローカルフックする方法に変更しました。元々最初に実装していたときは、フックを利用しようとしていましたが、フック関数はDLL内に実装する必要があると書いてあったので、pythonのみでは実装できないと思って、サブクラス化を使用していました。その後、あらためて確認したら、DLLにする必要があるのはグローバルフックのみであることに気付きました。IMESupportはSublime Text 2のウィンドウメッセージのみフックできればよいので、pythonのみでローカルフックする処理を実装しました。

WindowsのSublime Text 2 でIMEのインライン変換できるようにしたのでまとめ

現在の Windows用の Sublime Text 2 は IME のインライン変換の入力文字がウィンドウの外とか、おかしな場所に表示されてしまう問題があります。これを、正しくカーソル位置に表示するプラグインを作りました。詳細は github に置いたリポジトリのREADMEを見てください。

GitHub - chikatoike/IMESupport: IMESupport for Sublime Text 2/3

このエントリーでは、実装に使った手段を書いておきます。

インライン変換の実装に必要だったのは以下の3点です。

  • 変換ウィンドウの位置指定
  • 変換開始イベントのハンドリング
  • 変換ウィンドウの位置計算

変換ウィンドウの位置指定

インライン変換の表示位置がおかしいのは、Sublime Text 2が表示位置を指定していないためです。 これは、単に Win32 APIの ImmSetCompositionWindow を呼び出せば対応できます。そして、Sublime Text 2が搭載するPython処理系は、当然ctypesを同梱しているので、Win32 APIを呼び出すことができます。
これに気づいたので、プラグインを作り始めました。

変換開始イベントのハンドリング

ImmSetCompositionWindow を呼び出すだけ、と言っても、IMEの変換開始時に適切なタイミングで呼び出す必要があります。 具体的には、ウィンドウメッセージとして WM_IME_STARTCOMPOSITION が渡ってきた時に呼び出します。
ウィンドウメッセージを処理するには、ウィンドウプロシージャを実装する必要がありますが、普通はすべてのメッセージは Sublime Text 2 が処理してしまいます。これをプラグイン側で処理できるようにするために、ウィンドウのサブクラス化という方法を使いました。
独自のウィンドウプロシージャをPythonの関数として実装して、それをC言語の関数ポインタとして扱えるように変換し、 SetWindowLongW 関数を使ってウィンドウプロシージャの関数ポインタを置き換えます。元のウィンドウプロシージャのポインタは保存しておいて、IMEに無関係のメッセージは元のウィンドウプロシージャで処理させます。

変換ウィンドウの位置計算

これが一番大変でした。ウィンドウの左上からカーソル位置までの距離を ImmSetCompositionWindow に渡す必要があるのですが、Sublime Text 2にそれを直接取得するAPIは存在しないので、いろいろ計算する必要がありました。
(あとで詳しく書く)

補足

Win32 APIの幾つかは、ウィンドウハンドル(hwnd)を引数として取ります。これは Sublime Text 2の Windowクラスのhwndメソッドを呼び出して取得できます。なおhwndは公式ドキュメント(http://www.sublimetext.com/docs/2/api_reference.html#sublime.Window)に記載されていないメソッドです。

今後の予定

  • IMEのON/OFFを制御できるコマンドを追加する予定です。ただ、なぜか ImmSetOpenStatus を呼び出すと "[Error 126] 指定されたモジュールが見つかりません。" というエラーが出てしまって実現できていません。
  • http://d.hatena.ne.jp/topiyama/20070703/p1 に書いてあるような、 IMEの前後参照変換機能 に対応する予定です。

ショートカットキーで外部コマンドを実行して結果を表示する (execコマンド)

Python の subprocess を使って自分で実行することもできますが、標準搭載の exec コマンドを使うのが簡単です。
.sublime-keymap に以下のように書きます。

[
    { "keys": ["alt+r"],
        "command": "exec",
        "args": {
            "cmd": "python --help"
        }
    }
]

この設定で altr+r を押すと、"python --help" を実行した結果が画面下部のパネルに表示されます。

[Decode error - output not utf-8] が出る場合

標準出力のエンコーディングutf-8 以外で日本語等を含む場合、上記エラーが表示されてしまいます。その場合は "args" に "encoding" を指定します。

[
    { "keys": ["alt+r"],
        "command": "exec",
        "args": {
            "cmd": "help.exe",
            "encoding": "cp932"
        }
    },
]

Windowsでシンボリックリンクを使っているときに設定ファイルがリロードされない(続き)

2012/10/21追記: この方法ではプラグインのリロードは反映されませんでした。なのでこの記事に書いてあることは間違いです。
なぜ反映されないかというと、TextCommandはコマンド実行時ではなく、Viewが新しく作られた時にインスタンスが作られるため、単にモジュールをリロードするだけでは反映されませんでした。

id:chikatoike:20121008 の続きです。
Sublime Text 2 がリロードしないよりも Dropbox が同期しないほうがより致命的なので、Dropbox 側に実体を置いて、Sublime Text 2\Packages\User にリンクを置くことにしました。

Sublime Text 2 のリロードに関しては以下のプラグインを作って、保存時に自動的にリロードされるようにしました。

これを適当なファイル名でUserディレクトリの下に置けば良いです。
このプラグインは、.py と .sublime-keymap で動作します。.sublime-keymap の方は、別ファイルの .sublime-keymap のタイムスタンプを更新することによりリロードさせています。 .sublime-settings も同じ方法でいけるかと思いましたが、リロードされませんでした。
.sublime-* というファイルは他にもいろいろありますが、とりあえず上記2つのみ対応しています。

NOTE:

このプラグインシンボリックリンクで作られたディレクトリ下のファイルをリロードするためのものです。普通のディレクトリ下のファイルを保存するときも動作しますがこちらは元々自動リロードされるので意味がありません。

Windowsでシンボリックリンクを使っているときに設定ファイルがリロードされない

Sublime Text 2 の設定ファイル(Preferences.sublime-settingsなど)は、保存したタイミングでリロードされるはずですが、リロードされなくなっていて、原因を調べていたら、シンボリックリンクを使っているのが問題だとわかりました。
私は設定を他のPCと共有するために、Dropboxに作ったUserディレクトリに設定ファイルを入れて、シンボリックリンクでUserディレクトリへのリンクを
C:\Users\(ユーザー名)\AppData\Roaming\Sublime Text 2\Packages\User
に作成していました。
これを行うには、管理者権限でコマンドプロンプトを起動し、以下のmklinkを使用して以下のようにします。

C:\Windows\system32>mklink /d "C:\Users\(ユーザー名)\AppData\Roaming\Sublime Text 2\Packages\User" (DropboxのUserディレクトリ)

これで設定は共有出来るようになりますが、前述のとおりリロードされません。これは、ディレクトリの実体が Sublime Text 2\Packages の方にあれば問題なくリロードできるようです。なのでDropbox側にUserディレクトリを作るのではなく、Packages以下に元々存在するUserディレクトリへのリンクを貼るようにします。つまり、このようにリンクの方向を逆にします。

C:\Windows\system32>mklink /d (DropboxのUserディレクトリ) "C:\Users\(ユーザー名)\AppData\Roaming\Sublime Text 2\Packages\User"

これで問題なくリロードできるようになりました。

2012/10/18 追記

上記2番目の方法でリンクすると、今度はDropboxが同期してくれなくなってしまいました。(参考: http://loveskate.com/wp/?p=648)
Sublime Text 2 と Dropbox はどちらもファイルの監視を行うので、ファイルの実体がある方しか更新が検知されません。あちらを立てればこちらが立たずの状態になってしまいました。どうすればいいのやら。