CtagsでVimにタグジャンプ機能を追加する

![ad_banner4.gif](https://qiita-image-store.s3.amazonaws.com/0/8292/ef5f6cbc-0702-1c98-580f-786429f835e5.gif)
このエントリーは、[aratana Adventカレンダー](https://qiita.com/advent-calendar/2017/aratana)13日目のエントリーです。

はじめまして！！
新卒一年目、Vim一年生の田村です。

前日は、@mt-kageさんの「[はじめてのServerless ✕ Webpack ✕ TypeScript](https://qiita.com/mt-kage/items/b3348d95be37d282a7f9)」というエントリーでした。
サーバーレスと聞いて、「サーバーはいらないけど、筋肉は必要」という迷言を思い出したのは私だけでしょうか。

私日々のコーディングは、Vimで行っており、操作や動作などで不満が出れば、解決しvimrcを育成しております。（vimrc Breederってやつです。~~聞いたこと無い~~）

コードの処理を追っている中で、必ずと言ってもよいほど利用する__定義元へのジャンプ__。
主要言語はだいたいプラグインを導入すればジャンプできますが、
__マイナー言語__のほとんどに、プラグインが作られておらず、定義元ジャンプができません。
grep使えばいいじゃんって声も聞こえて来そうですが、色々工程省ける定義元ジャンプのほうが（ry...
そこで、定義元ジャンプを使えるようにする`Ctags`を利用していきます。

# やること
* 標準で対応していない定義元ジャンプの設定
* Vimで定義元ジャンプを実感!!
* 楽するためのVim設定（関数とか書いてみる）

# 事前準備
* Vim（まー標準で入っているでしょう）
* Ctags（パッケージマネージャーなどで入れましょう）
* ソースコード[NaspSample](http://na-s.jp/Nasp/)をダウンロード（[DL直リンク](http://na-s.jp/download.php?dl=/Nasp/NaspSample_1.0.0.0.zip)）
* 解凍したNaspSampleディレクトリにいる状態 In 黒い画面
* あ、対象は、Linux, Macとなっております（Windows翻訳できる方を除く）
* ちょっとVimを触れる

ちなみに、Ctagsは

* [exuberant-ctags](http://ctags.sourceforge.net/)
* [universal-ctags](https://ctags.io/)

の２種類あります。

`exuberant-ctags`の方は、更新が途絶えており、
`universal-ctags`は、現在もメンテナンスされ続けています。
違いとしては、__設定ファイルの場所__とか、__対応言語__などです。
やっぱりオススメは、メンテナンスされている`universal-ctags`ですね。

Macなら、

```vim:exuberant-ctags
$ brew install ctags
```

```vim:universal-ctags
$ brew install --HEAD universal-ctags/universal-ctags/universal-ctags
```
`universal-ctags`の主張が激しい。

## 事前vimrc
`$HOME/.vimrc`ファイルに以下を追加設定しておいてください。
（文字コードを正しく認識して、動作不良を起こさないため）
※ 既に設定済みって方は不要です。

```vim:.vimrc
set fileformats=unix,dos,mac
set fileencodings=utf-8,sjis
```

# Ctagsというすごいやつ
Ctagsは、ソースファイルを解析して__インデックスファイル__を作成するプログラムです。
簡単に言い換えれば、__関数や変数などの定義元へジャンプ__するためのファイルを作成するプログラムです。
標準でいくつかのプログラム言語に対応しています。

## Ctagsを使ってみよう
実際に__標準のCtags__を使ってみましょう！！
NaspSampleディレクトリで以下のコマンドを実行してから、
インデックスファイル（以後タグファイル）を作成できます。

```bash:bash
$ ctags -R -f .tags
```

（`.tags`ファイルをエディタで開いて中身確認しても良いかも）

### コマンド解説
* `ctags`で`ctags`実行。（そのまんま）
* `-R`は、現在ディレクトリから__再帰的__にファイルを解析していくオプション指定。
つまり、__全ファイル見ますモード__。
* `-f .tags`が__出力ファイル名__を設定するオプション。（デフォルトは、`tags`）

たった、これだけで便利な機能が使えるようになっちゃいます。

## Vimでジャンプしてみる
では、先程作成したタグファイルを元にVimで定義元ジャンプを実感しましょう！！

### やり方
1. `vim script/@ImgList.asp`で該当ファイルをVimで開く
1. `:set tags=.tags`でタグファイルのパスを指定して
1. `50gg` OR `50G`で、５０行へ飛ぶ
1. 文字列`rst`にカーソルが乗った状態で、`Ctrl-]`でジャンプします。

定義元の`dim rst:`へ移動していると思います。
（そんなん`gd`でもいけるやんは、聞こえなかったことにします:thinking::ear:）
正直このレベルでは、大したことないですね〜。

## 標準の限界
今回、ジャンプさせたい定義元を確認してみます。
`script/@ImgList.asp`ファイルの28行（つまり、`28gg` OR `28G`）

```aspx-vb:script/@ImgList.asp
function OnDraw_Images(oNasp, vParam)
```

呼び出し元も確認してみます。
`wwwroot/ImgList.asp`の23行

```aspx-vb:wwwroot/ImgList.asp
	(#%Images|
```
です。ここで、`OnDraw_Images`を呼び出しています。
Vimを一回落としている場合は、
`:set tags=.tags`をしましょう。

`Images`にカーソル合わせて、`Ctrl-]`でジャンプ!?

ん？なんかエラー出ていますね。
`E426: tag not found: Images` __Imagesというタグ無いですよ__って言われています。
Ctagsの標準では__対応していない__ようですね。
では、Ctagsの設定をカスタマイズして飛べるようにしましょう!!

# Ctagsカスタマイズ
Ctagsの設定ファイルを変更して、カスタマイズできます。
Ctagsの設定ファイルは、

* exuberant-ctagsの場合は、`$HOME/.ctags`
* universal-ctagsの場合は、`$HOME/.ctags.d/configure.ctags`

※`configure`部分はなんでも良いっぽいです。

となっています。インストした方に合わせましょう。

考えられる解決方法は、

1. `OnDraw_Images`をタグファイルへ登録
1. `Images`と`OnDraw_Images`を関連付ける

## Ctags設定追加
では、設定ファイルに以下の一行を追加しましょう。

```
--regex-asp=/^[ \t]*(function)[ \t]+OnDraw_([a-zA-Z0-9_]+)/\2/r,ondraw/
```

この処理の意味をまとめると、

* `asp`という言語に対してパターン追加
* `function OnDraw_hogehoge`に一致する行を`hogehoge`としてタグファイルに追加
* `ondraw`というタグ種類で登録（識別子`r`）

`\2`は、([a-zA-Z0-9_]+)を指しており、つまり`hogehoge`です。

これで、`OnDraw_Images`の箇所をタグファイルへ登録でき、`Images`で関連付け完了しました。

## カスタマイズ後の確認
では、先程と同じことしてジャンプできるか確認してみましょう。

1. `ctags -R -f .tags`でタグファイル更新
1. `vim wwwroot/ImgList.asp`
1. `:set tags=.tags`
1. `23gg`で23行へ飛ぶ
1. `Images`上で`Ctrl-]`でジャンプ！！

パっと変わったと思いますパっと！！
ちなみに、`Ctrl-t`で元に戻れます。
Vimのタグジャンプは、スタックとして履歴残る感じなので、
`Ctrl-]`でpushして、`Ctrl-t`でpopする的な感じです。

無事ジャンプさせることができましたね！！
これを応用していけば、飛ばしたい場所に自由に飛べます。
IDEにも劣らぬ__ジャンプ機能__を手に入れることができました！！

### ちなみに
インサートモードで、`<Ctrl-x><Ctrl-]>`を入力すると、__タグリストの補完__できます。
例えば、`OnDraw`まで入力した後に、`<Ctrl-x><Ctrl-]>`で、補完一覧がでてきます。
設定次第では、WordPressのアクションフック名の補完もできたりします。

# 問題点
ただ、いくつか面倒なところがありますね。。。

* Vimを立ち上げる度に`:set tags=.tags`
* ソースコードを変更する度に、タグファイル生成コマンド実行

さらに、極めつけには、ターミナル上で`wwwroot`へ移動した後、Vimで`ImgList.asp`を立ち上げ、同じようにすると、ジャンプ出来ません。
`set tags=.ctags`と記入しているので、カレントディレクトリ直下の`.tags`を探しており、`wwwroot`直下には、見つからないのでこのような現象になっています。~~こんなの使い物にならないじゃん！！~~

# 課題
とりあえず、現状の課題をまとめて見ました。

* Vim立ち上げるたび`set tags=.tags`
* タグファイルがあるディレクトリで、Vim立ち上げないとタグファイル読み込めない
* ソース変更するたび、手動でインデックスファイル作り直し

では、これらすべて解決しましょう！

# 課題の解決
課題に対して一つずつ解決していきます！

## vimrc設定
`$HOME/.vimrc`に記述すれば、Vim立ち上げ時に読み込まれるので、`.vimrc`に、`set tags=.tags`を追加しましょう。

これで、立ち上げと一緒に実行されます。

が、しかし、カレントディレクトリにタグファイルがないといけません問題。

解決できそうな動作としては、
__親ディレクトリを遡り__、`.tags`があるか探してから、あればそれを読み込む的な動作ができれば完璧ですね。
この動作は、Vimの標準で`Upward search`あるので、それ使いましょう。
詳しくは、`:h file-searching`の項目に書いてます。

以下の記述で、カレントディレクトリから、ホームディレクトリまで`.tags`を探します。便利〜。

```vim:.vimrc
set tags=.tags;$HOME
``` 

OR

```vim:.vimrc
set tags=.tags;~
```

に変更しましょう。お好きな方で。


## 自動コマンド設定
次は、毎回タグファイル作るの面倒問題。
Vimには、指定イベントが行われたら__自動的に実行されるコマンド__を指定できる、
`autocmd`なるコマンドがあります。
これ使いましょう。

```vim:.vimrc
augroup ctags
  autocmd!
  autocmd BufWritePost * silent !ctags -R -f .tags
augroup END
```

* `augroup`と`autocmd!`は、簡単に説明すると、複数回`autocmd`が登録されるのを防ぐやつです。
詳しくは、[おさらい autocmd/augroup](https://qiita.com/s_of_p/items/b61e4c3a0c7ee279848a)
* ファイル書き込み後に`silent !ctags -R -f .tags`実行しますという意味
* `silent`は、メッセージ出力させないよ。出力いらない。:spy:
* `!ctags -R -f .tags`先頭にビックリマークで、外部プログラム実行するという意味

## さらなる問題とその解決
これで、保存するたびインデックスファイル更新されるーっと思ったけど、
カレントディレクトリが変わったら、その直下に`.tags`できちゃう問題発生。
これじゃ、ディレクトリ移動して保存するたびに`.tags`生まれちゃう！！

安心して作業できませんね。
読み込んでる`.tags`ファイルパスと同じ場所で生成できるようにしたいですね。

てことで関数作りました。

```vim:.vimrc
function! s:execute_ctags() abort
  " 探すタグファイル名
  let tag_name = '.tags'
  " ディレクトリを遡り、タグファイルを探し、パス取得
  let tags_path = findfile(tag_name, '.;')
  " タグファイルパスが見つからなかった場合
  if tags_path ==# ''
    return
  endif

  " タグファイルのディレクトリパスを取得
  " `:p:h`の部分は、:h filename-modifiersで確認
  let tags_dirpath = fnamemodify(tags_path, ':p:h')
  " 見つかったタグファイルのディレクトリに移動して、ctagsをバックグラウンド実行（エラー出力破棄）
  execute 'silent !cd' tags_dirpath '&& ctags -R -f' tag_name '2> /dev/null &'
endfunction
```

これを、保存毎に実行するようにすれば良いので、
`autocmd BufWritePost * call s:execute_ctags()`
に変更します。
これで、快適にタグジャンプ生活を送れます。

現状のままだと、`execute_ctags`関数は、`.ctags`無かったら何もしないようになっています。
解決方法として、`.ctags`が見つからなかった場合、

* プロジェクトのルートに自動生成するようにする（`.git`と同じ階層とか）
* カレントディレクトリに生成するようにする（以降、生成したタグファイルを更新）

など、自分好みに改良していただければと思います。


# .vimrc

```vim:.vimrc
set fileformats=unix,dos,mac
set fileencodings=utf-8,sjis

set tags=.tags;$HOME

function! s:execute_ctags() abort
  " 探すタグファイル名
  let tag_name = '.tags'
  " ディレクトリを遡り、タグファイルを探し、パス取得
  let tags_path = findfile(tag_name, '.;')
  " タグファイルパスが見つからなかった場合
  if tags_path ==# ''
    return
  endif

  " タグファイルのディレクトリパスを取得
  " `:p:h`の部分は、:h filename-modifiersで確認
  let tags_dirpath = fnamemodify(tags_path, ':p:h')
  " 見つかったタグファイルのディレクトリに移動して、ctagsをバックグラウンド実行（エラー出力破棄）
  execute 'silent !cd' tags_dirpath '&& ctags -R -f' tag_name '2> /dev/null &'
endfunction

augroup ctags
  autocmd!
  autocmd BufWritePost * call s:execute_ctags()
augroup END
```

# ちなみに...
[Tagbar](https://github.com/majutsushi/tagbar)というプラグインを入れると、右とかにタグ登録されたリストが表示され、選択することでその場所へ飛べます。

<img width="1266" alt="tagbar.png" src="https://qiita-image-store.s3.amazonaws.com/0/218282/2179bc08-acf1-4dd2-564e-6595e41ecb7a.png">

つまり、CtagsとTagbarの設定をカスタマイズすることで、表示させたいリストを自分好みに変更することができるようになります。
CSSなら、セレクタ一覧とか。

さらに画像の左下に、現在カーソルがどこの関数に含まれるか表示させることもできます。
便利ですね。

# 最後に
思っていたよりもたくさん書いてしまいました。
CtagsとVimの素晴らしさを少しでも感じて頂けましたでしょうか。
Ctagsをカスタマイズして快適なVimジャンプライフを送りましょう。
これからも、さらにvimrcを育成していきます。

明日（14日目）の[aratana Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aratana)のエントリーは、期待の新人@sakochiさんのエントリーです！お楽しみに！