euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

IntelliJのHaskellプラグインを拡張してみる - Style scannerを入れてみる

公式にあるIntelliJのHaskellプラグインはIntelliJでHaskell開発をする上で無くてはならないのだけど、まだまだ発展途上で応用的な機能(例えばリファクタリングとか)は殆ど無かったりする。今のところghc-modiを使ったEmacsでいうflymake的なエラーチェックと、選択範囲の型表示ぐらいがあるだけだ。

なので、ここにHaskell style scannerを追加することを通して、プラグインの拡張方法をまとめてみる。

このHaskell style scannerの機能を足すとこんな感じでスタイルエラーを黄色くハイライトしてくれる。

f:id:euphonictechnologies:20141005030853p:plain

コンフィグにscanを足して、scanバイナリのパスをセットする

Haskell style scannerは

cabal install scan

でインストールすることができる。パッケージ名に倣ってscanと呼ぶことにしよう。

このscanの使い方は

scan <ファイル名>

Move.hs:28:1: too long line (123 chars)
Move.hs:38:1: too long line (96 chars)
Move.hs:2:7: put blank before (
Move.hs:40:1: missing final newline

という感じに指摘してくれる。オプションを付けることでファイルを修正させることもできる、が、今回は指摘の表示の方を使う。

まずはghc-modiのようにバイナリのパスをコンフィグに設定できるようにしてみる。

コンフィグの画面はplugin/src/org/jetbrains/haskell/config/HaskellConfigurable.ktで作られており、実際のコンフィグを保持しておく場所はplugin/src/org/jetbrains/haskell/config/HaskellSettings.javaになる。

private val scan = TextFieldWithBrowseButton()
// (in createComponent)
...
scan.addBrowseFolderListener(
                "Select scan execurtable",
                null,
                null,
                FileChooserDescriptorFactory.createSingleLocalFileDescriptor())

...
scan.getTextField()!!.getDocument()!!.addDocumentListener(listener)
...
addLabeledControl(5, "scan executable", scan)

// (in apply)
...
state.scanPath = scan.getTextField()!!.getText()

// (in reset)
...
scan.getTextField()!!.setText(state.scanPath ?: "")

といった感じでテキストボックスを足して、state (HaskellSettingsクラスのオブジェクト)にパスをセットする。

scanを使ってスタイルチェック

実際にセットしたパスを使ってスタイルチェックをするルーチンを作る。 これもghc-modiで使われている方法をほぼパクってできる。

Added scanner

これは本当にシンプルな外部コマンド呼び出しのサンプル。scanは引数のファイルをスキャンしたら標準出力に警告を一行ずつ吐いてすぐに終了するのでコマンドの終了を待ち合わせる必要もない。

これを実際に使うところはplugin/src/org/jetbrains/haskell/external/HaskellExternalAnnotator.ktにある。

HaskellExternalAnnotator.kt

といった感じで新たに関数を定義する。 先のScan.runCommandを呼び出して、中身をパーズするだけ。既存のghc-modiのものがそのまま流用できるはずだ。 スタイルエラーをエラーっぽく表示するのはいやなのでWarningレベルで表示するようにした。

それを実際にパーズする場所にプラグインする。

override fun doAnnotate(psiFile: PsiFile?): List<ErrorMessage> {
        val file = psiFile!!.getVirtualFile()
        if (file == null) {
            return listOf()
        }

        val baseDir = getProjectBaseDir(psiFile)

        if (baseDir == null) {
            return listOf()
        }

        val resultGhcModi = getResultFromGhcModi(psiFile, baseDir, file)
        val resultScan = getResultFromScan(psiFile, baseDir, file)

        val result = resultGhcModi + resultScan

        return result
}

ErrorMessageのリストを返す2つの関数をconcatして返すとそれをアノテーションに表示してくれる。かんたんだね!

まとめ

f:id:euphonictechnologies:20141005030853p:plain

これを応用すれば外部コマンドを使ったいろんなツールが簡単に作れそう。 たとえばすぐに思い浮かぶのはHLintをつかうこと、かな。 本当はAlt+Enterのメニューに出てきてワンタッチで修正が入るようになるといいんだけど、それはまた今度。

この機能は

ysnrkdm/haskell-idea-plugin · GitHub

にある。さらにプラグインだけ使いたい人は

https://github.com/ysnrkdm/haskell-idea-plugin/blob/master/plugin/haskell-plugin.zip

をプラグインとしてインストールしてみてください。バグ報告等歓迎です。

追伸

IntelliJのUltimateエディションを買ってしまった。199ドルなり。