vim-jp / vimdoc-ja / usr_29

usr_29 - Vimドキュメント

メインヘルプファイルに戻る
usr_29.txt    For Vim バージョン 8.2.  Last change: 2016 Feb 27

                     VIM USER MANUAL - by Bram Moolenaar

                           プログラムの中を移動する


Vim の作者はプログラマです。当然、プログラムを書くための機能が Vim にはたくさ
んあります。この章では、識別子が定義された場所、あるいは使われている場所にジャ
ンプしたり、その定義を別のウィンドウでプレビューしたりする方法を説明します。プ
ログラミング関連の機能は次章でも説明します。

29.1  タグを使う
29.2  プレビューウィンドウ
29.3  プログラムの中を移動する
29.4  グローバル識別子を検索する
29.5  ローカル識別子を検索する

次章: usr_30.txt  プログラムの編集
前章: usr_28.txt  折り畳み
目次: usr_toc.txt

==============================================================================
29.1  タグを使う

タグとは、識別子が定義された場所のことです。例えば C や C++ の関数定義がそうで
す。タグの一覧はタグファイルに保存されます。Vim はタグファイルに対応しており、
タグ、つまり識別子の定義場所に直接ジャンプできます。
カレントディレクトリのすべての C ファイルからタグを生成するには、次のコマンド
を使います:

        ctags *.c

"ctags" は Vim に付属してません。ほとんどの Unix システムには最初からインス
トールされています。持っていない場合は Exuberant ctags を使ってください:

        http://ctags.sf.net 

Vim のコマンドラインから次のコマンドを実行すると関数定義にジャンプできます:

        :tag startlist

"startlist" 関数が検索されます。他のファイルで定義されていても検索可能です。
CTRL-] コマンドを使うとカーソルの下の単語をタグとみなしてジャンプできます。こ
れは複雑な C コードの探索を簡単にしてくれます。例えば、"write_block" 関数の中
で "write_line" の呼び出しを見つけたとき、その関数の動作を知りたかったら、
"write_line" にカーソルを合わせて CTRL-] を押せば、その関数の定義にジャンプで
きます。
"write_line" の中で "write_char" が呼ばれていたら、その関数の動作も調べる必要
があります。"write_char" にカーソルを合わせて CTRL-] を押しましょう。これ
で "write_char" の定義に移動できました。

        +-------------------------------------+
        |void write_block(char **s; int cnt)  |
        |{                                    |
        |   int i;                            |
        |   for (i = 0; i < cnt; ++i)         |
        |      write_line(s[i]);              |
        |}          |                         |
        +-----------|-------------------------+
                    |
             CTRL-] |
                    |    +----------------------------+
                    +--> |void write_line(char *s)    |
                         |{                           |
                         |   while (*s != 0)          |
                         |      write_char(*s++);     |
                         |}       |                   |
                         +--------|-------------------+
                                  |
                           CTRL-] |
                                  |    +------------------------------------+
                                  +--> |void write_char(char c)             |
                                       |{                                   |
                                       |    putchar((int)(unsigned char)c); |
                                       |}                                   |
                                       +------------------------------------+

":tags" コマンドで移動経路を確認できます:

        :tags
          # TO tag         FROM line  in file/text
          1  1 write_line          8  write_block.c
          2  1 write_char          7  write_line.c
        >

では元の場所に戻りましょう。CTRL-T で直前のタグに戻れます。上の例であれば、
"write_line" 関数の中の "write_char" の呼び出しに戻ることになります。
このコマンドはカウント指定を付けてジャンプする回数を指定できます。前方にジャン
プして、そして戻ってくることができましたね。もう一度前方に移動してみましょう。
次のコマンドでタグリストの前方に移動できます:

        :tag

コマンドの前にカウント指定を付けてジャンプする回数を指定できます。例えば
":3tag" のように使います。CTRL-T も同様に回数指定できます。
このように、CTRL-] をで呼び出しをたどり、CTRL-T でさかのぼることができます。
":tags" コマンドで現在地を確認できます。


ウィンドウを分割する
--------------------

":tag" コマンドはカレントウィンドウを使ってジャンプ先のファイルを表示します。
しかし現在の関数とジャンプ先の関数を同時に表示したいこともあると思います。
":split" コマンドでウィンドウを分割してから ":tag" コマンドを使うという方法も
ありますが、専用の短縮コマンドが用意されています:

        :stag tagname

カーソルの下の単語にタグジャンプするときにウィンドウを分割したい場合は次のコマ
ンドを使います:

        CTRL-W ]

カウント指定を付けて新しいウィンドウの高さを指定できます。


複数のタグファイルを使う
------------------------

ファイルが複数のディレクトリに分れている場合、ディレクトリ毎にタグファイルを作
ることもできますが、その方法だとタグファイルと同じディレクトリのファイルにしか
ジャンプできません。
タグファイルが複数ある場合は 'tags' オプションを設定して、関連するすべてのタグ
ファイルが検索されるようにしてください。例:

        :set tags=./tags,./../tags,./*/tags

カレントファイルと同じディレクトリ、その一つ上のディレクトリ、すべてのサブディ
レクトリからタグファイルが検索されます。
これでかなり多くのタグファイルが使えるようになりましたが、まだ十分ではないかも
しれません。例えば "~/proj/src" を編集しているときに "~/proj/sub/tags" を見つ
けることができません。そのような場合はディレクトリツリー全体を検索するように設
定します。例:

        :set tags=~/proj/**/tags


タグファイルを一つだけ使う
--------------------------

たくさんの場所からタグファイルを検索しているときは、ハードディスクがガリガリと
音を立てるのが聞こえると思います。これは効率が良くありません。そんなときは少し
時間を掛けて一つの巨大なタグファイルを生成するのがベストです。寝ている間にでも
やってしまうといいでしょう。
それには上述した Exuberant ctags が必要です。このプログラムにはディレクトリツ
リー全体を検索するためのオプションがあります:

        cd ~/proj
        ctags -R .

Exuberant ctags のいいところは、いろんなファイルタイプを認識してくれるところで
す。C や C++ だけでなく Effiel や Vim script も処理できます。詳しくは ctagsの
ドキュメントを参照してください。
これで、巨大なタグファイルを一つだけ指定するだけでよくなりました:

        :set tags=~/proj/tags


定義の重複
----------

同じ名前の関数が何度も定義されている場合、あるいは複数のクラスで同名のメソッド
が定義されている場合、":tag" コマンドは最初に見つかったタグにジャンプします。
カレントファイル内にタグがある場合はそれが優先されます。
タグが重複している場合は次のコマンドで別のタグにジャンプできます:

        :tnext

もう一度実行するとさらに別のタグにジャンプできます。タグがたくさんある場合は次
のコマンドでタグを選択できます:

        :tselect tagname

このような選択画面が表示されます:

          # pri kind tag               file
          1 F   f    mch_init          os_amiga.c
                       mch_init()
          2 F   f    mch_init          os_mac.c
                       mch_init()
          3 F   f    mch_init          os_msdos.c
                       mch_init(void)
          4 F   f    mch_init          os_riscos.c
                       mch_init()
        Enter nr of choice (<CR> to abort): 

(行頭の) 番号を入力してジャンプしたい場所を選択してください。他の列にはタグの
場所を示すヒントが表示されます。

次のコマンドで他の重複タグに移動できます:

        :tfirst                 最初のタグに移動
        :[count]tprevious       [count]個 前のタグに移動
        :[count]tnext           [count]個 次のタグに移動
        :tlast                  最後のタグに移動

[count] を省略すると 1 が使われます。


タグ名の推測
------------

コマンドライン補完を使うと長いタグ名の入力が簡単になります。最初の数文字を入力
してから <Tab> キーを押してください:

        :tag write_<Tab>

最初にマッチしたタグが補完されます。それが意図したタグでない場合は、目的のタグ
が見つかるまで <Tab> キーを押してください。
関数名の一部しか知らない場合や、同じ文字で始まるタグ (後半だけが違っている) が
たくさんある場合は、パターンを使ってタグを検索できます。
例えば、名前に "block" が含まれているタグにジャンプする場合は、まず次のように
入力します:

        :tag /block

そして、コマンドライン補完を使います。<Tab> キーを押してください。"block" を含
むタグが検索され、最初にマッチしたタグが使われます。
タグ名の前に "/" を付けると、続くタグ名はそのまま使われず、パターンとして解釈
されます。検索パターンと同じ機能がすべて使えます。例えば、"write_" で始まるタ
グを選択したい場合は次のようにします:

        :tselect /^write_

最初の "^" はタグ名が "write_" で始まることを示しています。"^" がない場合はタ
グ名の途中にもマッチしてしまいます。同様に、"$" を最後に付けるとタグ名の末尾に
マッチするようになります。


タグブラウザー
------------

CTRL-] を使うとカーソルの下にある識別子の定義にジャンプできますが、これを利用
すると、識別子の一覧を目次として使うことができます。例を示します。まず識別子の
一覧を作ります (Exuberant ctags が必要です)

        ctags --c-types=f -f functions *.c

そして Vim をファイル指定なしで起動し、作成したファイルを縦分割ウィンドウで開
きます:

        vim
        :vsplit functions

ウィンドウにはすべての関数の一覧が表示されています。関数以外の名前も含まれてい
るかもしれませんが、それは無視してください。":setlocal ts=99" を実行して表示を
見やすくします。
このウィンドウで、次のマップを定義します:

        :nnoremap <buffer> <CR> 0ye<C-W>w:tag <C-R>"<CR>

表示したい関数の行に移動して <Enter> を押すと、カーソルが他のウィンドウに移動
して、選択した関数にジャンプします。


関連項目
--------

タグ名の大文字と小文字を無視する場合には、'tagcase' を "ignore" に設定する
か、'tagcase' を "followic" のまま変更せず 'ignorecase' をオンに設定してくださ
い。

'tagbsearch' オプションにはタグファイルがソートされているかどうかを設定しま
す。初期設定ではソート済みに設定されています。これはタグの検索を高速に実行でき
ますが、ソートされていないタグファイルを扱えなくなります。

'taglength' オプションはタグの識別に使う文字数を指定するのに使います。

cscope はフリーのプログラムです。識別子の定義場所を探すだけでなく、それが使わ
れている場所も検索できます。cscope 参照。

==============================================================================
29.2  プレビューウィンドウ

コードの中で関数を呼び出すときには、その引数を正確に把握する必要があります。引
数の意味は関数の定義を見ればわかります。タグの仕組みを使えば簡単に確認できます
が、できれば別のウィンドウに定義を表示したいところです。それにはプレビューウィ
ンドウを使います。
次のようにすると "write_char" 関数をプレビューウィンドウで表示できます:

        :ptag write_char

ウィンドウが開いて "write_char" タグにジャンプします。カーソルの位置は動かない
ので CTRL-W コマンドを使って戻る必要はありません。
テキストの中に関数名がある場合は、次のコマンドでその定義をプレビューウィンドウ
で表示できます:

        CTRL-W }

カーソルの下にある単語の定義場所を自動的に表示してくれるスクリプトもあります。
CursorHold-example参照。

プレビューウィンドウを閉じるには次のコマンドを使います:

        :pclose

プレビューウィンドウでファイルを開きたい場合は ":pedit" を使います。例えばヘッ
ダーファイルを表示するような場合に便利です:

        :pedit defs.h

最後に ":psearch" コマンドを紹介します。カレントファイルおよびインクルードされ
ているファイルから単語を検索して、ヒットした場所をプレビューウィンドウで表示で
きます。これは例えば、ライブラリ関数を使っていて、それ用のタグファイルを作って
いないときに使います。例:

        :psearch popen

"stdio.h" がプレビューウィンドウで開き、popen() 関数のプロトタイプが表示されま
す:

        FILE    *popen __P((const char *, const char *));

プレビューウィンドウの高さは 'previewheight' オプションで設定できます (最初に
開いたときに使われる)。

==============================================================================
29.3  プログラムの中を移動する

プログラムには構造があるので、構文を認識することが可能です。その情報を利用して
移動するコマンドが用意されています。
例えば C のプログラムには次のような構文がよく現れます:

        #ifdef USE_POPEN
            fd = popen("ls", "r")
        #else
            fd = fopen("tmp", "w")
        #endif

もっと長いかもしれませんし、入れ子になっていることもあります。"#ifdef" に移動
して % を押すと "#else" にジャンプできます。もう一度 % を押すと "#endif" に
ジャンプします。さらに % を押すと "#ifdef" に戻ります。
構文が入れ子になっている場合は、正しく対応しているものが検索されます。これは
"#endif" の書き忘れがないかどうか確認するのに便利です。
"#if" と "#endif" の間にカーソルがあるとき、次のコマンドで開始位置にジャンプで
きます:

        [#

"#if" や "#ifdef" の中にいない場合は警告音が鳴ります。前方の "#else" または
"#endif" に移動するには次のコマンドを使います:

        ]#

これらのコマンドは、途中にある "#if" - "#endif" ブロックをスキップします。
例:

        #if defined(HAS_INC_H)
            a = a + inc();
        # ifdef USE_THEME
            a += 3;
        # endif
            set_width(a);

カーソルが最後の行にあるとき、"[#" で最初の行に移動できます。途中の "#ifdef" -
"#endif" ブロックはスキップされます。


コードブロック内の移動
----------------------

C のコードブロックは {} で囲まれています。ブロックはかなり大きい場合もありま
す。アウターブロック (最も外側のブロック) の開始位置に移動するには "[[" コマン
ドを使います。"][" でブロックの末尾に移動できます。このコマンドは行頭の "{" と
"}" をブロックの区切りとして認識します。
"[{" コマンドで現在のブロックの開始位置に移動できます。同じレベルの {} ペアは
スキップされます。"]}" で末尾に移動できます。
つまりこのような動作です:

                        function(int a)
           +->          {
           |                if (a)
           |       +->      {
        [[ |       |            for (;;)               --+
           |       |      +->   {                        |
           |    [{ |      |         foo(32);             |     --+
           |       |   [{ |         if (bar(a))  --+     | ]}    |
           +--     |      +--           break;     | ]}  |       |
                   |            }                <-+     |       | ][
                   +--          foobar(a)                |       |
                            }                          <-+       |
                        }                                      <-+

C++ や Java では、最も外側の {} ブロックはクラスです。その次のレベルの {} はメ
ソッドです。クラスの中で "[m" を使うと、前のメソッドの開始位置に移動できます。
"]m" で次のメソッドの開始位置に移動できます。

"[]" で前の関数の末尾に移動、"]]" で次の関数の開始位置に移動できます。行頭が
"}" で始まる行が関数の末尾として認識されます。

                                int func1(void)
                                {
                                        return 1;
                  +---------->  }
                  |
              []  |             int func2(void)
                  |        +->  {
                  |    [[  |            if (flag)
        start     +--      +--                  return flag;
                  |    ][  |            return 2;
                  |        +->  }
              ]]  |
                  |             int func3(void)
                  +---------->  {
                                        return 3;
                                }

()、{}、[] などの対括弧に移動する場合は "%" も使えることを忘れないでください。
括弧の間に複数の行がはさまっていても機能します。


カッコ内の移動
--------------

"[(" と "])" は "[{" と "]}" と機能は同じです。ただし、{} のペアではなく () の
ペアに対して動作します。

                                  [(
                    <--------------------------------
                              <-------
                if (a == b && (c == d || (e > f)) && x > y)
                                  -------------->
                          -------------------------------->
                                       ])

コメント内の移動
----------------

コメントの開始位置に戻るには "[/" コマンドを使います。コメントの終了位置に移動
するには "]/" を使います。これは /* - */ 形式のコメントのみ対応しています。

          +->     +-> /*
          |    [/ |    * A comment about      --+
       [/ |       +--  * wonderful life.        | ]/
          |            */                     <-+
          |
          +--          foo = bar * 3;         --+
                                                | ]/
                       /* a short comment */  <-+

==============================================================================
29.4  グローバル識別子を検索する

C プログラムを編集していて、変数の型が "int" なのか "unsigned" なのか分からな
かったら、"[I" コマンドで簡単に確認できます。
例えば、"column" という単語の上でコマンドを実行すると:

        [I

マッチした行が一覧表示されます。カレントファイルとインクルードファイル (さらに
その中でインクルードされているファイル) が検索されます。検索結果は次のように表
示されます:

        structs.h
         1:   29     unsigned     column;    /* column number */

インクルードファイルも検索されるという点が、タグやプレビューウィンドウを使った
検索よりも便利です。たいていは正しい定義場所が見つかります。タグファイルが更新
されていなくても、インクルードファイル用のタグファイルがなくても機能します。
ただし、"[I" が動作するためには少し条件があります。ファイルのインクルードを認
識するために、'include' オプションが正しく設定されていなければなりません。初期
設定は C と C++ 用に設定されているので、他の言語では設定を変更する必要がありま
す。


インクルードファイルの場所
--------------------------

インクルードファイルは 'path' オプションに設定された場所から検索されます。設定
に含まれていないディレクトリがあると、いくつかのインクルードファイルは検出でき
ないかもしれません。次のコマンドで検出できないファイルを確認できます:

        :checkpath

検出できなかったインクルードファイルの一覧が表示されます。インクルードファイル
の中のインクルードも検査されます。次のような結果が表示されます:

        --- Included files not found in path ---
        <io.h>
        vim.h -->
          <functions.h>
          <clib/exec_protos.h>

カレントファイルでインクルードしている "io.h" が見つかっていません。"vim.h" は
見つかったので ":checkpath" はさらにそのファイルのインクルードも検査しました。
そして、"functions.h" と "clib/exec_protos.h" が見つかりませんでした。

        Note:
        Vim はコンパイラではないので、"#ifdef" ステートメントを認識しません。
        つまり、"#if NEVER" で囲まれている "#include" ステートメントもすべて検
        査されます。

この問題を修正するには 'path' オプションにディレクトリを追加します。Makefileを
見れば必要なディレクトリがわかると思います。"-I/usr/local/X11" のように、"-I"
が使われている行を調べてください。次のコマンドでディレクトリを追加できます:

        :set path+=/usr/local/X11

サブディレクトリがたくさんある場合はワイルドカード "*" が使えます。例:

        :set path+=/usr/*/include

これで "/usr/local/include/" や "/usr/X11/include/" などが検索対象になります。

ディレクトリツリーのあちこちにインクルードファイルがあるようなプロジェクトでは
"**" が便利です。すべてのサブディレクトリを検索できます。例:

        :set path+=/projects/invent/**/include

例えば次のようなディレクトリからファイルが検索されます:

        /projects/invent/include
        /projects/invent/main/include
        /projects/invent/main/os/include
        etc.

設定方法は他にもあります。'path' オプションの説明を確認してください。
実際に検出されたインクルードファイルを確認したい場合は次のコマンドを使います:

        :checkpath!

インクルードされているファイルの (長大な) 一覧が表示されます。出力を短くするた
め、同じファイルを見つけた場合は "(Already listed)" とだけ表示し、その中のイン
クルードファイルは表示しません。


定義場所にジャンプする
----------------------

"[I" はマッチした行だけを一覧表示します。その周辺を見たい場合は、次のコマンド
で最初のマッチにジャンプします:

        [<Tab>

<Tab> と CTRL-I は同じなので "[ CTRL-I" でも構いません。

"[I" で表示される一覧には番号が付いています。最初の項目以外の場所にジャンプし
てい場合は番号を指定してください:

        3[<Tab>

三番目のマッチにジャンプします。CTRL-O で元の場所に戻れることをお忘れなく。


関連コマンド
------------

        [i              最初のマッチだけ表示
        ]I              カーソルより後ろのマッチを一覧表示
        ]i              カーソルより後ろの最初のマッチだけ表示


定義済識別子の検索
------------------

"[I" コマンドはすべての識別子を検索します。"#define" で定義されたマクロだけを
検索するには次のコマンドを使います:

        [D

このコマンドもインクルードファイルが検索対象になります。検索される行の書式は
'define' オプションで指定します。C と C++ 以外の言語では設定を変更する必要があ
ります。
次のような "[D" に関連したコマンドがあります:

        [d              最初のマッチだけ表示
        ]D              カーソルより後ろのマッチを一覧表示
        ]d              カーソルより後ろの最初のマッチだけ表示

==============================================================================
29.5  ローカル識別子を検索する

"[I" コマンドはインクルードファイルの中も検索します。カーソルの下の単語が最初
に現れる場所を、カレントファイルの中だけ検索し、その場所にジャンプするには、次
のコマンドを使います:

        gD

ヒント: Goto Definition (定義に移動)。このコマンドはローカル(C 用語で "static")
に定義された変数や関数を検索するのに便利です。例 (カーソルは "counter" の上):

           +->   static int counter = 0;
           |
           |     int get_counter(void)
        gD |     {
           |         ++counter;
           +--       return counter;
                 }

さらに検索範囲を狭めて、現在の関数の中だけ検索したい場合は次のコマンドを使いま
す:

        gd

現在の関数の開始位置から最初に単語が使われている場所が検索されます。実際に、行
頭が "{" で始まる行を後方検索して、その上の空行まで戻り、そこから識別子を前方
検索しています。例 (カーソルは "idx" の上):

                int find_entry(char *name)
                {
           +->      int idx;
           |
        gd |        for (idx = 0; idx < table_len; ++idx)
           |            if (strcmp(table[idx].name, name) == 0)
           +--              return idx;
                }

==============================================================================

次章: usr_30.txt  プログラムの編集

Copyright: see manual-copyright  vim:tw=78:ts=8:noet:ft=help:norl:
関連キーワード:  タグ, usr, コマンド, 検索, 移動, 表示, 関数, ジャンプ, 定義, ウィンドウ