SublimeText2のプラグインを作る時のまとめ
概要
プラグインを作るときに、自分が調べて学んだこととか
調べ方とかを纏めておく。
参考にしたもの
なにはともあれまずAPIと、
http://www.sublimetext.com/docs/2/api_reference.html
作り方
http://net.tutsplus.com/tutorials/python-tutorials/how-to-create-a-sublime-text-2-plugin/
あと今まで使ってたプラグインを、参考までに開けて見てた。全部Package Controlに入ってるはず。
感謝。
InsertDate
どっちかっていうとPythonの勉強になった
Package Control
UIの勉強に超なった
Git
Gitの勉強になった
Scalariform
コマンドライン実行の勉強になった
commandについて
SublimeText2でよく見る、実行プロトコルみたいなもの。
値の名前がついてるPythonプログラムを実行する。
JSON内で定義されている事が多いが、.py内から他のcommandも呼べる。
例えば、
"command": "myCommand"
とか書いてあると、
myCommand って名前をつけられてる特定条件を満たした.pyプログラムが実行される。
特定条件
command : コマンド名 に対して、
・このプラグインフォルダ中に、コマンド名を含んだclass が定義されている.pyファイルがある
.pyファイルならファイル名は何でも良くて、class名がコマンド名を含んでいるのが条件。
command : something なら、 SomethingHappen とかになる。
注意点として、cammelケースとかがあると、混乱のもと。
command : somethingelse とあったら、
class SomethingElse だと ×。
class Somethingelse で ○。 一度ハマった。
・その class が、次のどれかのSublimeのクラスを継承している。
sublime_plugin.ApplicationCommand
sublime_plugin.WindowCommand
sublime_plugin.TextCommand
それぞれの違いはAPIみてくだしあ。
http://www.sublimetext.com/docs/api-reference#sublimeplugin.Plugin
当然 import も必要。
・run メソッド(特定引数あり)を持っている
引数は上記クラスによって異なる。
各条件を満たした.pyの表記はこんな感じ。
.pyファイル
import sublime
import sublime_plugin
class コマンド名を含んだクラス名 (sublime_plugin.TextCommand):
def run (self, edit) :
プラグイン基礎構造
プラグイン名のフォルダ
+-syntax
+-Default.sublime-commands
+-Default.sublime-keymap
+-Main.sublime-menu
+-プラグイン名.sublime-settings
+-その他 .pyとか、README.mdとか
何を変えるとどこが変わるのか
プラグインの中のファイル一覧と、その内容についての記述が無かったのでメモ。
Default.sublime-commands
commandの基礎リスト。
⌘ + shift + pから出てくる、プラグインに含まれてるcommand一覧が書いてある。
commandの条件を満たせば表示される。
Default.sublime-keymap
キーバインド、ショートカットとcommand(ST2での標準的な実行形式)の連携を書くファイル。
こんな感じ。
[
{ "keys": ["super+alt+b"], "command": "gbuild" }
,{ "keys": ["super+alt+t"], "command": "gtest" }
]
内容がすでに用意されている物とかぶると、上書きしちゃったりされちゃったりする。
ところで現在インストールされてるキーマップ一覧をチートシートみたいに表示するプラグインが欲しい。。
Main.sublime-menu
Toolsなど、Sublime上部のバーで表示する要素に追加する項目を記述する所。
jsonでの階層構造、とても奇麗。//でコメントが使える。
[
{
"id": "tools",←①
"children":
[
{
"caption": "Gradle",←②
"children":
[
{ "caption": "Log", "command": "test" }
]
}
]
}
,{
"caption": "Preferences",←③
"mnemonic": "n",
"id": "preferences",
"children":
[
{
"caption": "Package Settings",
"mnemonic": "P",
"id": "package-settings",
"children":
[
{
"caption": "Gradle",
"children":
[
{
"command": "open_file",←④
"args": {"file": "${packages}/Gradle/Gradle.sublime-settings"},
"caption": "Settings – Default"
},
{
"command": "open_file",←⑤
"args": {"file": "${packages}/User/Gradle.sublime-settings"},
"caption": "Settings – User"
},
{ "caption": "-" }
]
}
]
}
]
}
]
とか書くと、①Tools の項目にGradle、さらにその要素として Log が表示される。
③Preferences > package Settings に表示される際の名称。アプリの設定(Preferences)から選択できる。
だいたいのプラグインは、ここから設定ファイル ④、⑤プラグイン名.sublime-settings を開けるようにしているみたい。
メニューの項目はネスト可能で、ネストした場合、プルダウン表示される。
簡単に作れて素敵。
Side Bar.sublime-menu
サイドバーで、メニュー表示時に表示される内容が書き足せる。
jsonでの階層構造、とても奇麗。コメントが使える。
プラグイン名.sublime-settings
設定ファイル。
プラグインのデフォルトがプラグインフォルダにあり、
上書き用の同名のファイルがSublimeのUser フォルダ下にあるのがコモンセンスらしい。
プラグイン製作中のSublimeの挙動について
control + ` でSublime内のPythonコマンドライン窓が出るんだけど、
プラグインのファイルを編集→保存、とかやると、
[Reloading plugin /Users/sassembla/なにやら] とか表示されるので、プラグインは都度更新、ビルド、再読み込みがされている。
.pyファイルについて
プラグインのフォルダ内に存在してれば読み込まれる。
Toolsからのプラグインのcommandの実行について
[
{
"id": "tools",←①
"children":
[
{
"caption": "Gradle",←②
"children":
[
{ "caption": "Build", "command": "build" }←③
]
}
]
}
①、②で、ToolにGradleメニューが現れるようになるが、
③のtest commandがちゃんとToolsからハイライト表示されるかどうかは、
commandの条件を満たす必要がある。
条件を満たさない場合、commandは灰色で表示され、実行できない。
実際の挙動を作る
具体例はちょっと思いつかないので、仮の例を挙げる。
・とあるプロセスをSublimeText2から実行したい
たとえばTypeScriptのテスト(って何まだ無いけど)を実行したいとか、
JenkinsのトリガーをSublimeText2から引きたいとか
そんな感じの物事があったとして、
「無事にPlugin作りました、簡単です、実行できます!」
→いざ実行→UIがロックされる
と殺意を覚える。コンマ一秒でもUIをロックしちゃだめだ。。。
→できるだけ非同期に行って、でも結果はUIに返そうぜ!
そんなときは次の機能のペアが役に立つ。
threading.Thread
と
sublime.set_timeout(callback, delay)
Androidやってる人ならピンと来るかもしれない。
iOSやってる人には全くピンと来ないと思う。
JSerな自分はJSな理解で誤解した。
threading.Thread は
スレッドです。
PythonのAPIに入ってるので調べてくだしあ。
このへん
http://docs.python.org/2/library/threading.html
http://www.doughellmann.com/PyMOTW-ja/threading/
sublime.set_timeout は
特定動作を指定時間後に実行する。
SublimeTextのAPIに含まれている。
http://www.sublimetext.com/docs/2/api_reference.html
このAPIは、
特定の関数(callback)を、delayミリ秒後にMainThreadで実行する
っていう感じのものになる。
"It is safe to call setTimeout from multiple threads."
この一文から解れってのが、ちょっとキツい。
これらを使って何をすると捗るか:
スレッド立ち上げてなんらかの処理を実行、
UIをロックせず、
終わったらUIに通知
というのが、この2つの組み合わせで出来る。
とても捗るのでお薦め。
以下おまけ話
よくあるクライアントサイドアプリの話として、
UIをもつアプリケーションは、須くUIを扱うThreadを持つわけで、
このUIThreadへのアクセスができないと、GUIになんにも反映できない。
良いプラットフォームはこのへんの隠蔽が上手。
SublimeText2だとどうなっているのか。
例えばSublimeTextのAPIに含まれている次の関数
status_message(string)
なんかは、stringに入れた内容をSublimeText2の最下部のバーに表示できるんだけど、
UIの要素なので、
UIThread(っていうかMainThread)からしか呼べない。
適当にThreadを継承したクラスを作って、インスタンスつくって、
インスタンス内でstatus_messageメソッドを呼ぼうとすると、Assertionで殺される。
たとえばこんなソースを書いたとする
#どっかでスレッド継承クラスをinstantiateして、start
thread = SomeThread("hahaha!")
thread.start()
#スレッド継承クラス
class SomeThread(threading.Thread):
def __init__(self, message):
self. message = message
threading.Thread.__init__(self)
def run(self):
print "run start, message is ", self.message
sublime.status_message(self.message)
こいつは、次のエラーを呼ぶ。
Exception in thread Thread-397:
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 532, in __bootstrap_inner
File "./testing.py", line 40, in run
sublime.status_message(self.command)
RuntimeError: Must call on main thread, consider using sublime.set_timeout(function, timeout)
(MainThread外から呼んでんじゃねーよksg!! set_timeout使え!!)
で、言われた通り、sublime.set_timeout を使うと、問題なく該当メソッドが呼べる。
delayミリ秒後、timeout先で呼ばれるのがmainThreadになっている、という絡繰り。
関数をぶん投げて、MainThread で実行される。
Androidだと、実行したい処理を含んだThreadを作ってrunOnUiThreadメソッドにThread放り込んで云々、って感じになるところ。
iOSだと、そもそもそんな残念な苦労はしなくてよくて、UIViewのクラスメソッドや、セレクタ渡しや、NSNotificationが超強力になんとかしてくれる。
JSだとそもThreadって概念無いので、はい。
Sublimeは、っていうと、だいたい上記のメソッドで、優雅では無いけど使いやすい感じに仕上がっていると思う。
せめてrunOnMainThread と runOnMainThreadWithDelay とかに出来なかったのか、、とか思うが、
関数そのまま渡せるのですっごく助かる。
メモ
OS判定
デフォルトでOS判別をしてくれてる。
詳しくは @ikeike443 さんのScalariformとか読むと良いと思う。
ikeike443 / Sublime-Scalariform
https://github.com/ikeike443/Sublime-Scalariform
Terminalみたいに何かを実行したいんだけど。
コマンドラインを実行するには。
self.view.window().run_command('exec', {'cmd': ['sh', 'script.sh'], 'quiet': False})
→viewが存在するメインスレッドからのみ実行できる。
実行中の内容がPromptっぽいWindowで表示できるんだけど、Mainをがっちり掴んでしまう。
スレッド違いには出来ない。UIが絡んでるからだね。
subprocess.call(self.CMD+self.view.file_name(), shell=True)
→別スレッドでも実行可能。
Python API
http://www.python.jp/doc/release/library/subprocess.html
なんだかんだで Popen が無双。
SublimeText2の現在のデフォルトがPython2.6系なので、
check_outputが使えないのが残念。
viewの再描画
self._force_refresh()
強制読み直し
Toolでのプラグインの実行表記に、ショートカットが勝手についた \(^o^)/わーい
プラグイン作ってる途中になんか、勝手についちゃった。
本来は、 Default.sublime-keymapに書かなければつかない、、はず、、
勝手にショートカットついちゃった//// 流石俺/////
→なぜなんだぜ
→build というcommand名を用意していたんだが、それが不味かった。
デフォルトで存在するcommnd名 build とモロかぶりした。
デフォルトで存在するcommnd名を自作プラグインが定義してしまうと、プラグインのもので上書きされる。
で、デフォルトのbuildには、super + b というキーが Default.sublime-keymapで セットしてあったので、
基礎定義部分だけプラグインがオーバーライドして、表記などは Default.sublime-keymap のものが採用されてた、という。
部分オーバーライド(?)こええ。
ショートカットについてのハマりどころ
super + alt + t を とあるcommandのショートカットに指定してたんだけど、
commandPaletteからは表示されるのに、
Toolsからは表示されなくて
「、、、何でなんだぜ、、? 一方では出て、もう一方で出ない、、強制上書きが発動しているはず、、では、、?」
って思ってたんですけど、これ、上書き不可とかも有るみたいですねー。
super + alt + tは、こいつだった。
試しにcommand + alt + n に変更したら、commandPaletteの方も、Toolsのほうも、
ちゃんと表示されました。
泣いてません。