雑文発散

«前の日記(2015-01-19) 最新 次の日記(2015-01-21)» 編集
過去の日記

2015-01-20 [長年日記]

[Emacs][JavaScript] Emacs の JavaScript 開発環境を整備する(Tern編)

Emacs の JavaScript 開発環境についていろいろ調べていたら、「Tern」というキーワードがときどき出てきていた。これはいったい何なんだ?と思いつつインストールしてみたので、記録しておこう。

Tern のウェブサイトに書かれている特徴とかはこんな感じ(翻訳は適当)。

  • スタンドアローンの JavaScript コード解析エンジン
  • 各種エディタと連携
  • 変数やプロパティの補完
  • 関数の引数のヒント
  • 式のタイプを確認
  • 定義している箇所の検索
  • 自動リファクタリング

便利そうに見える機能群じゃないか(笑)

ただ、これを読んだだけだと「スタンドアローンの 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-modetern-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 してみた結果がこれ。

Emacs + Tern

ミニバッファ(画面の一番下の部分)に number と表示されている。これは変数 aaa に入っている値の型を表示している。続いて変数 bbb の部分で C-c C-c した結果がこちら。

Emacs + Tern

こちらも number 型であると表示される。この行だけを見た場合、変数 bbb に変数 aaa を代入しているだけだ。つまり「変数 aaa に代入された値が number 型なので、その aaa を代入した bbbnumber である」ことを把握して結果を表示していることになる。裏でどこまでやっているのか把握できていないけど、なんか変態チックなことをしているのかしら。。。

実現方法はともかく、これができると、他人の書いたソースを読み解くのに「ここに入ってくるデータってなに?」ってのが理解しやすくなるんじゃなかろうか。

次は、関数を実行している hoge() の部分で C-c C-d を実行してみる。

Emacs + Tern

これはカーソル部分のキーワードに対してドキュメントがあれば、それをミニバッファに表示してくれるヤツ。この例の場合は、hoge() を定義している箇所のコメント「hoge is not fuga」が表示されているのが分かる。

ただ、これ、日本語のドキュメントだとミニバッファに表示する段階で文字化けが発生してしまうようだ。Emacs でミニバッファへ日本語の表示はもちろんできる。例えば、

(message "日本語だよー")

とかやっても文字化けはしない。tern-mode の中では、tern-message という関数がミニバッファへの表示を担っているようなので、今度は試しに次のようにやってみた。

(tern-message "日本語だよー")

これでも特に文字化けはしない。カーソル位置のドキュメントを検索する関数は tern-get-docs みたいなんだけど、この中では Tern サーバにクエリを投げて返ってきた結果を tern-message で表示しているだけに見える。ということは、クエリ生成時のエンコード部分か返り値のデコード部分か、はたまた Tern サーバ側内部の実装なのか。。。この辺は常用して不便さを感じたらもう少し追ってみるかな。

気を取り直して、今度は定義している場所へのジャンプ機能を試す。M-. にキーバインドされている。この例でも hoge() のところで M-. を叩いてみる。

Emacs + Tern

すると、function hoge() の部分へジャンプする。

Emacs + Tern

また、この状態で 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 を入力して補完候補を表示させたところ。

Emacs + Tern + auto-complete

このファイル内で自分で定義した関数 getBargetFoo が補完候補として表示されるだけでなく、ネイティブオブジェクトのメソッドも補完候補として表示してくれたってことなのかな。. が正しく動いていなさそうなのも含め、auto-complete 周りはもうちょい調べないとダメそうかなぁ。。。

まぁ、でもこの Tern 関連は入れておいて損はないと思うので、このまま常用する方向で進めよう。

参考にしたところのまとめ