Emacs の JavaScript 開発環境についていろいろ調べていたら、「Tern」というキーワードがときどき出てきていた。これはいったい何なんだ?と思いつつインストールしてみたので、記録しておこう。
Tern のウェブサイトに書かれている特徴とかはこんな感じ(翻訳は適当)。
便利そうに見える機能群じゃないか(笑)
ただ、これを読んだだけだと「スタンドアローンの JavaScript コード解析エンジン」ってなんのこと?って疑問が残る。Demo のページを見に行くと、ブラウザでの編集でコード補完ができたりして、すげー!とは思うものの、これがどう Emacs とか関係あるんだ?と余計な疑問も追加されてしまう。
MELPA には tern の名前で登録されいるので、M-x package-install
でインストールできるのだけど、、、実はこれだけだと使えない。なぜなら、Tern の本質は node.js で動く Tern サーバであり、Emacs 用の tern-mode
は、そのサーバと通信するクライアントだからだ。
では、その Tern サーバはどうやってインストールするのか?というと、Tern の Git リポジトリを clone して、npm install を実行せよ、という話のようだ。
具体的にはこんな感じ。git
とか npm
とかは既に入っているものとする。
% cd YOUR_PATH
% git clone https://github.com/marijnh/tern.git
% cd tern
% npm install
リポジトリの bin/
ディレクトリ内に tern
コマンドがあるので、こいつを PATH の中に入れてやる。オレは、$HOME/bin/ の下にシンボリックリンクを置いた。
% cd $HOME/bin
% ln -s YOUR_PATH/tern/bin/tern .
また、同じリポジトリ内に elisp も入っているので、それを使うか、先ほど説明した MELPA のパッケージを使って elisp をインストールする。オレは、autoload 設定とかが面倒くさいので、package.el で(正確には Cask で)インストールした。
Cask についての詳細はここでは書かない。必要があれば「Emacs 24.4 のパッケージ管理を Cask にしてみた」とかを参照して欲しい。
いちおう Cask ファイルの一部を晒しておくと、こんな感じで書いている。tern
と共に tern-auto-complete
も入れた。後者は名前からも分かる通り、auto-complete-mode
用の拡張。tern で解析した内容を auto-complete の補完候補に使っちゃうようなヤツだ。
(source gnu)
(source melpa)
[snip]
(depends-on "tern")
(depends-on "tern-auto-complete")
[snip]
あとは、JavaScript 用に使っている mode の設定に追記してしまおう。オレは js3-mode
を使っているので、こんな感じになっている。js3-mode
になったときに自動的に tern-mode
と tern-auto-complete
を有効にするような設定。
(defun js3-mode-hooks()
(tern-mode t)
(eval-after-load 'tern
'(progn
(require 'auto-complete)
(auto-complete-mode t)
(require 'tern-auto-complete)
(tern-ac-setup))))
(add-hook 'js3-mode-hook 'js3-mode-hooks)
(add-hook 'js3-mode-hook 'helm-gtags-mode) ; これは昨日の日記の gtags 用の設定
これで準備が完了したはず。Tern サーバは PATH さえ通っていれば、elisp 側で勝手に立ち上げてくれるので、手動で起動する必要はない。
じゃあ、サンプルの JavaScript コードを使って、何ができるのかを見ていこう。まずは、カーソルポイントにある変数などの表示してくれるヤツ。標準のキーバインドは C-c C-c
になっている。
例えば、下記のソースの変数 aaa
の部分で C-c C-c
してみた結果がこれ。
ミニバッファ(画面の一番下の部分)に number
と表示されている。これは変数 aaa
に入っている値の型を表示している。続いて変数 bbb
の部分で C-c C-c
した結果がこちら。
こちらも number
型であると表示される。この行だけを見た場合、変数 bbb
に変数 aaa
を代入しているだけだ。つまり「変数 aaa
に代入された値が number
型なので、その aaa
を代入した bbb
も number
である」ことを把握して結果を表示していることになる。裏でどこまでやっているのか把握できていないけど、なんか変態チックなことをしているのかしら。。。
実現方法はともかく、これができると、他人の書いたソースを読み解くのに「ここに入ってくるデータってなに?」ってのが理解しやすくなるんじゃなかろうか。
次は、関数を実行している hoge()
の部分で C-c C-d
を実行してみる。
これはカーソル部分のキーワードに対してドキュメントがあれば、それをミニバッファに表示してくれるヤツ。この例の場合は、hoge()
を定義している箇所のコメント「hoge is not fuga」が表示されているのが分かる。
ただ、これ、日本語のドキュメントだとミニバッファに表示する段階で文字化けが発生してしまうようだ。Emacs でミニバッファへ日本語の表示はもちろんできる。例えば、
(message "日本語だよー")
とかやっても文字化けはしない。tern-mode
の中では、tern-message
という関数がミニバッファへの表示を担っているようなので、今度は試しに次のようにやってみた。
(tern-message "日本語だよー")
これでも特に文字化けはしない。カーソル位置のドキュメントを検索する関数は tern-get-docs
みたいなんだけど、この中では Tern サーバにクエリを投げて返ってきた結果を tern-message
で表示しているだけに見える。ということは、クエリ生成時のエンコード部分か返り値のデコード部分か、はたまた Tern サーバ側内部の実装なのか。。。この辺は常用して不便さを感じたらもう少し追ってみるかな。
気を取り直して、今度は定義している場所へのジャンプ機能を試す。M-.
にキーバインドされている。この例でも hoge()
のところで M-.
を叩いてみる。
すると、function hoge()
の部分へジャンプする。
また、この状態で M-,
を入力すると、元の場所へ戻ることもできる。これがあれば、昨日の GNU GLOBAL (gtags) は不要なんじゃね?と一瞬思ったんだけど、この機能は同一ファイル内で定義されているところへのジャンプのみのようだ。別ファイルで定義されていた場合は追えないので、gtags も併用した方が効率が良いと思う。
他にも C-c C-r
でカーソル位置の変数名を変更する機能があったりする。これはバッファ内の該当変数名を全て指定の名前に入れ替えてくれるようだ。細かく試してないけど、構文解析をしているので replace-string
などでの置換よりも精度が高いんじゃないかな。
ここまでの操作をまとめておこう。
キーバインド | 内容 |
---|---|
C-c C-c |
カーソル位置のワードの型(type)を表示する |
C-c C-d |
カーソル位置のワードのドキュメントを表示する |
M-. |
カーソル位置のワードを定義している場所へジャンプする |
M-, |
ジャンプした場所から元の場所へ戻る |
C-c C-r |
カーソル位置の変数名を変更する |
最後は、tern-auto-complete
によるキーワード補完について。tern-ac-complete
コマンドが .
(ドット)にキーバインドされている。つまり、.
を入力するタイミングで、補完候補を表示してくれると思うのだけど、、、なんかオレの環境だとうまくいかない。他の設定に干渉しているような気がしているんだけど、細かいところまで追えていない。
なので、ちょっと無理やり使ってみた。下図は Bar.get
まで入力した段階で M-x tern-ac-complete
を入力して補完候補を表示させたところ。
このファイル内で自分で定義した関数 getBar
と getFoo
が補完候補として表示されるだけでなく、ネイティブオブジェクトのメソッドも補完候補として表示してくれたってことなのかな。.
が正しく動いていなさそうなのも含め、auto-complete
周りはもうちょい調べないとダメそうかなぁ。。。
まぁ、でもこの Tern
関連は入れておいて損はないと思うので、このまま常用する方向で進めよう。