ctagsをElixirに対応させて「定義にジャンプ」を実現する【Elixir, Atom Editor】

# [ctags](http://ctags.sourceforge.net/)
南極を含む7大陸で使用されていると評判(?)の[exuberant ctags](http://ctags.sourceforge.net/)は、

> generates an index (or tag) file of language objects found in source files that allows these items to be quickly and easily located by a text editor or other utility.

とあるように、**関数やクラス定義のインデックスを生成し、エディタ機能で容易にその定義位置にジャンプしたり、定義ファイルを開いたりする**ためのユーティリティです。

[41の言語](http://ctags.sourceforge.net/languages.html)にデフォルトで対応しているとのことですが、Elixirは入っていません。

が、インデックスしたい定義の正規表現を登録することで、(大雑把になら)簡単に別言語ファイルに対応できます。

# 使い方

```bash
$ cd project_root
$ ctags -R [paths]
```

`tags`というファイルが生成され、中に関数やクラスが列挙されていればOK.

設定ファイルは`~/.ctags`という名前でホームディレクトリにおきます。

# Elixir

[mmorearty/elixir-ctags](https://github.com/mmorearty/elixir-ctags)で公開されています。

```ctags:~/.ctags
--langdef=Elixir
--langmap=Elixir:.ex.exs
--regex-Elixir=/^[ \t]*def(p?)[ \t]+([a-z_][a-zA-Z0-9_?!]*)/\2/f,functions,functions (def ...)/

...

```

`--regex-<langdef>`の行は、`/<regex>/<capture>/<label>/`という構造になっているようです。[ドキュメント](http://ctags.sourceforge.net/EXTENDING.html)はあまり詳細ではないですが、生成された`tags`ファイルを見るとなんとなくわかります。

上記例の場合、`(p?)`と`([a-z_][a-zA-Z0-9_?!]*)`でキャプチャが2箇所あり、実際にインデックスされて欲しい関数名の方は2番めなので、`<capture>`項に`\2`を指定します。

# Atom Editorで「定義にジャンプ」

ビルトインの[symbols-view](https://github.com/atom/symbols-view)パッケージが担当しています。

単語を選択(OSXなら`cmd-d`、あるいは`ctrl-shift-W`)して、`alt-cmd-down`でジャンプできます。`alt-cmd-up`で元のファイルの元の位置に戻れます。

が、このストロークは複雑なのと、選択した単語がインデックスに存在していても、ジャンプが効かないケースがあります。
(例えば`Nested.Module`で`Nested`部分を選択してジャンプしようとする場合。symbols-view側の仕様?)

どちらかと言うと、ファイル内であれば`cmd-r`、プロジェクト全体であれば`shift-cmd-R`を使って、検索モーダルから探すほうが直観的かもしれません。個人的にはこちらを多用しています。

## [goto](https://atom.io/packages/goto)

ctagsを使わず独自にインデックスを(in-memoryで)自動構築してジャンプを行う[goto](https://atom.io/packages/goto)というパッケージもあり、symbols-viewを代替できるようです。手動でctagsコマンドを叩く手間が省けてお手軽ですが、まだ試してはいません。

ただ、ctagsを介しておけばvimやEmacsでも同じインデックスを使い回せる利点があるので、どちらが良いかは好みと状況によるでしょう。

# Tips
以下おまけ。

## インデックス生成

たまに手動でコマンドを叩くだけですが、面倒ですよね。自動でやらせたい。"[ctagsと連携するように環境を構築する](http://qiita.com/soramugi/items/f918020c2b3f48c93bf3#1-5)"も参考に。

1. git hookで好きなタイミングで実行させる。
    - `post-commit`, `post-merge`, `post-checkout`あたりか。
2. npm scriptsやgulpでwatchしているプロジェクトであれば、適当なスクリプトに`ctags`呼び出しを絡ませておく。
    - ctags自体は単純なシロモノなので高速ですが、走査するファイル量によるので、`[paths]`引数で適宜範囲指定。
    - `deps`とか、`node_modules`とかは最初から含めないのがよさげ。
3. [mix-test.watch](https://github.com/lpil/mix-test.watch)を使っているのなら、`System.cmd/3`とかでctagsを呼び出すMix taskを作っておいて、configで指定する。
    - 試してはいません。でもできるはず。
4. ファイル保存時にコマンドを実行する系のAtomパッケージでフックする。
    - [save-autorun](https://atom.io/packages/save-autorun)や[save-commands](https://github.com/JsonHunt/save-commands)などがある模様。試してみる予定。

エディタ等ローカル開発環境周りの設定事項はできればGitに入ってプロジェクトで共有されるファイルでは設定したくないので、1か4が有望そうです。

## `tags`ファイルはglobal ignore

同様に、頻繁に更新されることになる`tags`ファイルもGitには入れたくありません。が、同名のファイルが複数プロジェクトで生成されるので、いちいち`.gitignore`に書かせてもらうのは忍びない。

[global ignore](http://qiita.com/katsew/items/5cade12fa743a2f31f25)に入れておくのがよさそうです。

## Scala
他の未対応言語ももちろんカバーできます。

Scalaなら[こちらのgist](https://gist.github.com/schmmd/1277336)が参考になります。

```ctags:~/.ctags
--langdef=scala
--langmap=scala:.scala
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private|protected)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\4/c,classes/

...

```

SBTを使っている場合、[sbt-ctags](https://github.com/ceedubs/sbt-ctags)というプラグインを使うとビルド時に生成可能のようです。`~/.ctags`は必要です。

## 筆者設定

[gist](https://gist.github.com/ymtszw/dd538e640227147d35c9d9bf3487c16a)で公開しています。[skirino/croma](https://github.com/skirino/croma)の`defun`シリーズにも対応。