SublimeLinterにD言語(他の任意の言語)を追加してハイライト出来るようにする


SublimeLinterはSublimeText2のPackageの一つで、コンパイルエラーやシンタックスエラーが起きた箇所をハイライトする機能です。
下の画像みたいな感じ。

![SublimeLinter例](http://desmond.yfrog.com/Himg610/scaled.php?tn=0&server=610&filename=cyxm.png&xsize=640&ysize=640 "SulimeLinter例")

SublimeLinterについて公式は以下のページ。
https://github.com/SublimeLinter/SublimeLinter

SublimeLinterのインストールは以下のページ参照。
http://perl.no-tubo.net/2012/05/03/mac%E3%81%AE%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF-sublime-text2%E3%81%A7javascript%E3%81%AE%E3%82%B7%E3%83%B3%E3%82%BF%E3%83%83%E3%82%AF%E3%82%B9%E3%82%A8%E3%83%A9/
http://watson1978.github.com/blog/2012/08/23/make-a-sublime-text-environment/

で、これでどうやってD言語のハイライトするかを以下に。

###D言語用ファイル追加
SublimeLinterインストール後、[Sublime Text 2]->[Preference]->[Browse Packages…]でパッケージ入ってるフォルダ開く。(MacOSの場合。他のOSは知らない)
![BrowsePackage…](http://a.yfrog.com/img863/6499/hnlb.png)

んで、パッケージ入ってるフォルダ開いたら、[〜〜/Packages/SublimeLinter/sublimelinter/modules/]フォルダを開く。中にpython.pyとかjava.pyとか入ってると思うので、d.pyを作成する。

###d.pyのガワを作成
d.pyに、objective-j.pyからソースをコピペして、必要ない所削除して以下みたいにする。

```python:d.py
import re
import os
import subprocess

from base_linter import BaseLinter

CONFIG = {
    'language': 'D'
}


class Linter(BaseLinter):
    def built_in_check(self, view, code, filename):
        return ''

    def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
		return ''

```
`built_in_check`がコードをコンパイルしてコンパイルエラーを返す関数、`parse_errors`が`built_in_check`から来たエラーを元にハイライトする場所を決める関数に当たる。

###`built_in_check`の中身
`built_in_check`の引数は`self`,`view`,`code`,`filename`。selfは自信で(C#で言うthis)、viewはまぁタブのクラス、codeは**編集中の保存してないファイルの中身**で、filenameはそのまま表示中のファイルの名前。
んで、'built_in_check'にコードコンパイルする中身処理を書くんだけど、注意点とかは以下の通り。

1. コンパイル対象はcodeにする。filenameのファイルをコンパイルすると編集前のファイルをコンパイルするのでおかしな事になる。codeをd.pyのある適当なフォルダに保存してそれをコンパイルする
2. dmdのコンパイルオプションは`-c -o- -w -unittest -debug`で
3. 分からなくなったら`base_linter.py`を読もう

ということでその結果が以下。

```python:d.py
…
#書いてる途中のファイルを書き出すtempフォルダ
TMPPATH_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '.dtmpfiles'))
if not os.path.exists(TMPPATH_DIR):
    os.mkdir(TMPPATH_DIR)
…
class Linter(BaseLinter):
    def built_in_check(self, view, code, filename):
      def built_in_check(self, view, code, filename):
        #パス取得
        work_path = os.path.dirname(filename)
        file_name = os.path.basename(filename)

		 #コンパイル場所の設定取得
        settings = view.settings().get('SublimeLinter', {}).get(self.language, {})
        if(settings):
            dwd = settings.get('working_directory', [])
            if(dwd):
                #プロジェクトに設定あったらそれを取得
                work_path = dwd

        #書いてる途中のファイルをtempファイルとしてd.py直下に書き出し
        tempfilePath = os.path.join(TMPPATH_DIR, file_name)

        with open(tempfilePath, 'w') as f:
            f.write(code)

        args = ["dmd", "-c", "-o-", "-w", "-unittest", "-debug", tempfilePath]

        try:
			#コンパイル
            process = subprocess.Popen(args,
                                        cwd=work_path,
                                        stdin=subprocess.PIPE,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.STDOUT,
                                        startupinfo=self.get_startupinfo())
            result = process.communicate(None)[0]
        finally:
            if tempfilePath:
                #保存したファイルの削除
                os.remove(tempfilePath)

        return result.strip()
…
```

SublimeText2にはプロジェクト管理の機能があるのだけど、そのプロジェクトファイルに設定を書くと、dmdする場所を設定出来る。自分で作ったmoduleとかをimportしてる時はそれをしないとimport部分でコンパイルエラー起こる(と思う)。
設定は以下の感じで。

```json:xxx.sublime-project
{
	"folders":
	[
		{
			//プロジェクトのフォルダ
			"path": "~~~~"
		}
	],

	"settings":
    {
        "SublimeLinter":
        {
            "d":
            {
				//dmd動かしたいフォルダ。上のフォルダと同じになったりするかも。
                "working_directory": "~~~~~"
            }
        }
    }
}

```


###`parse_errors`の中身
`built_in_check`のreturnで渡されたコンパイルエラーのエラーメッセージは`parse_errors`引数の`lines`に渡される。
後は`lines`の各行にあるエラーメッセージから、エラーメッセージとそのエラー行を切り出して`self.add_message`関数に渡せばいい。
ここでの注意点は以下の通り。

1. dmdのv2.060では、一部書いたソース意外の場所でのコンパイルエラーが吐き出される。これは修正される予定。この段階ではそんなの吐き出されたらimportしてるだろって前提でimportしてる行にエラー全部入れ込んでる。
2. `errorMessages`渡してるがここはあまり意味が無い。というか何が来るか調べてない。多分何も入ってない。
3. 分からなくなったら`base_linter.py`を読もう(大事なことなので二回目)

ということで(ry

```python:d.py
…
    def parse_errors(self, view, errors, lines, errorUnderlines, violationUnderlines, warningUnderlines, errorMessages, violationMessages, warningMessages):
        for line in errors.splitlines():
            match = re.match(r'^(?P<filename>.+\.d)\((?P<line>\d+)\): Error: (?P<error>.+)', line)

            if match:
                tab_filename = os.path.basename(view.file_name())
                error_filename = os.path.basename(match.group('filename'))
                error, line = match.group('error'), match.group('line')
                if(tab_filename == error_filename):
                    self.add_message(int(line), lines, error, errorMessages)
                else:
                    line = 1
                    module_name = error_filename.replace('.d', '')
                    regions = view.find_all(r'import.*\.' + module_name)
                    if(regions):
                        line = view.rowcol(regions[0].a)[0] + 1
                    self.add_message(line, lines, error, errorMessages)
```

これでおそらく上手く行くはずです。上手く行かなかったら修正してください。