vimの関数ジャンプのかゆいところをMakefileと.vimrcで解決する
最近寒いので毛布を出した @at_grandpa です。
みなさん、関数ジャンプしてますか?
してますよね!
今までエラーで挫けていて導入していなかったのですが、最近本腰いれて解決に臨み、結果、素晴らしいライフチェンジングになりました。
偉大な先人の方々のツールは素晴らしい!巨人の肩の上に乗りまくりましょう。
ctags + vim
ググればたくさん出てきますが、簡単に導入方法と紹介を。
インストール
[debian系] sudo apt-get install exuberant-ctags [CentOS/RedHat系] yum install ctags [mac] brew install ctags
tagsの生成
cd /path/to/target_dir ctags -R
これで、tags
というファイルが生成されます。
vimの設定
vimに読み込ませるtags
ファイルを指定します。
:set tags=./tags;
;
は、「親ディレクトリを探していく」というもので、tags
をプロジェクトのルートディレクトリに置いておけば参照できます。
ジャンプ!
/path/to/target_dir
以下で関数ジャンプが可能になります。
ジャンプしたいキーワードにカーソルを合わせ、<C-]>
でジャンプします。
戻るのは<C-t>
。
これであなたもライフチェンジング。
かゆいところ
ですが、使っていて微妙にかゆいところがあるんですね。
蚊に刺されるほどかゆくはない。
少し布に触れてかゆくなる、そんな程度。
自分の環境は、結構大きなプロジェクトのリポジトリでして、複数言語が混じって構築されているんですね。
ここで ctags -R
をすると、ctags君は一途にデータを集めて、対応言語全てで「たったひとつのtagsファイル」を作ってくれます。
こうなると、全言語が入り混じっているtags
ファイルなので、ジャンプ候補に他言語が出てきてしまうのです。
例えばこんな感じです。(この候補から一番左の番号を選んでジャンプします)
# pri kind tag file 1 F f FunctionName /path/to/file.php public static function FunctionName() { 2 F m FunctionName /path/to/file.rb class:ClassName module FunctionName 3 F s FunctionName /path/to/file.pl sub FunctionName { ...
ちょっとかゆいですよね。
(「機能別に言語が分かれているだろうし、同じ名前を付けるのはナンセンス」というご意見もあるでしょうが、今は触れないこととします。)
Makefileと.vimrcで解決
やりたいことは「ジャンプ候補リストに他言語が混じらないようにする」です。
今回は、Makefile : 40行
+ .vimrc : 15行
で解決しました。
Makefile
Makefile
の役割は「言語別にtagsファイルを生成する」です。
言語別にtagsを生成すれば、ジャンプ候補リストに他言語が混じることはありません。
以下にその40行を示します。
# ---------------------------------------------------- # tagを生成する # ---------------------------------------------------- # 言語 # see also `ctags --list-languages` lang := PHP \ Ruby \ Python \ Perl lower_lang := $(shell echo $(lang) | tr A-Z a-z) # 各言語のtag対象ファイルの拡張子 # see also `ctags --list-maps` ext := default \ .rb.ruby.spec \ default \ default TARGET_PATH = $(PWD) # ここは基本的に書き換える git_toplevel = $(shell cd $(TARGET_PATH);git rev-parse --show-toplevel) seq = $(shell seq 1 $(words $(lang))) ifeq ($(git_toplevel),) # gitリポジトリ管理ではない場合 tags_save_dir = $(realpath $(TARGET_PATH))/tags tags_target_dir = $(realpath $(TARGET_PATH)) else # gitリポジトリ管理である場合 tags_save_dir = $(HOME)/dotfiles/tags_files/$(shell basename $(git_toplevel)) tags_target_dir = $(git_toplevel) endif .PHONY: create_tags $(seq) create_tags: $(seq) $(seq): mkdir -p $(tags_save_dir) ctags -R \ --languages=$(word $@,$(lang)) \ --langmap=$(word $@,$(lang)):$(word $@,$(ext)) \ -f $(tags_save_dir)/$(word $@,$(lower_lang))_tags $(tags_target_dir)
これは、
make -f /path/to/Makefile create_tags TARGET_PATH=./
とすることで、カレントディレクトリ以下について、各言語($(lang)
で指定してある言語)のtagsファイルを生成します。
こんな感じ。
ls ~/dotfiles/tags_files/{リポジトリ名}/ perl_tags php_tags python_tags ruby_tags
簡単に言うと、以下のことをやっています。
$(lang)
に設定された言語についてtagsファイルを生成- tagsファイルの名前は
{言語名}_tags
- git管理のリポジトリでない場合、カレントディレクトリに各tagsを配置
- git管理のリポジトリである場合、
~/dotfiles/tags_files/{リポジトリ名}/*
に各tagsを集約
これだけです。
これで言語別にtagsファイルを生成できました。
.vimrc
.vimrc
の役割は「ファイル毎に適切なtagsファイルを設定する」です。
以下にその15行を示します。
" ファイルタイプ毎 & gitリポジトリ毎にtagsの読み込みpathを変える function! ReadTags(type) try execute "set tags=".$HOME."/dotfiles/tags_files/". \ system("cd " . expand('%:p:h') . "; basename `git rev-parse --show-toplevel` | tr -d '\n'"). \ "/" . a:type . "_tags" catch execute "set tags=./tags/" . a:type . "_tags;" endtry endfunction augroup TagsAutoCmd autocmd! autocmd BufEnter * :call ReadTags(&filetype) augroup END
以下のことをやっています。
BufEnter
でReadTags(&filetype)
が発動- バッファに入ったときに発動。ウィンドウ移動やタブ移動などで発生。
execute "set tags=..."
で読み込むtagsファイルを切り替え- git管理されているなら
~/dotfiles/tags_files/{リポジトリ名}
の言語別のtagsを指定&filetype
を使用することで、ファイルの言語別に読み込みが可能
- git管理されていないなら、ルートディレクトリにある各言語のtagsを指定する
- git管理されているなら
これで、ファイルの種類の応じて適切なtagsファイルを設定できます。
その他の設定
かゆいところを無くすために、キーマッピングも設定しました。
set notagbsearch " [tag jump] カーソルの単語の定義先にジャンプ(複数候補はリスト表示) nnoremap tj :exe("tjump ".expand('<cword>'))<CR> " [tag back] tag stack を戻る -> tp(tag pop)よりもtbの方がしっくりきた nnoremap tb :pop<CR> " [tag next] tag stack を進む nnoremap tn :tag<CR> " [tag vertical] 縦にウィンドウを分割してジャンプ nnoremap tv :vsp<CR> :exe("tjump ".expand('<cword>'))<CR> " [tag horizon] 横にウィンドウを分割してジャンプ nnoremap th :split<CR> :exe("tjump ".expand('<cword>'))<CR> " [tag tab] 新しいタブでジャンプ nnoremap tt :tab sp<CR> :exe("tjump ".expand('<cword>'))<CR> " [tags list] tag list を表示 nnoremap tl :ts<CR>
自分の直感に近い感じで設定できたので、始めからストレス無く活用できました。
良かった点
tags読み込みの自動切り替えやキーマッピングを設定して、最終的に良かった点は以下です。
- ジャンプ候補リストに他言語の候補が出なくなった
- 既存リポジトリ内にtagsファイルを置くのではなく、
dotfiles/tags_files/{リポジトリ名}
に集約することで管理しやすい .gitignore
にtags
と書かなくて良い- 直感的操作でタグジャンプできた
- コマンド一発で設定できる
これでコードを読むストレスが格段に減り、いろんなコードを読みたくなりました。
これは良い傾向。
これらのコードはgithubに上げてあります。
もしかしたら車輪の再発明?
もしかしたら、もっともっと簡単に解決できる方法があるかもしれません。
実は車輪の再発明だったりして...
良い方法、ご存知の方いらっしゃいましたら、コメントやTwitterで是非つぶやきましょう。 (確かneobundleのプラグインでtags系のものがあったと思いますが、vimが重くなったのでやめた記憶)
個人的にはvimscriptと戯れることができたので面白かったです。
時には車輪の再発明をしてみるのも良い経験になりそうですね。
周りのエンジニアに関数ジャンプについて聞いてみると、
「IntelliJ IDEA 使ってるよー」
IDE...
なにそれ美味しいの?( ^ω^)おっおっ
美味しいらしい...
味見してこよう...λ............トボトボ