Vim本体のソースコードの読みはじめかた(仮)

github.com

私はVim本体のソースコードを読み出したり触りだしてから2年ちょい経ったのでここで脳内GCをしたく、まとめてみようかと思います(Vim歴は8年くらい)。 とりあえず、これからここに書くことは現時点(2019/3/24)のソースコードの話でかつ私が理解している中でのまとめなので間違っていたり、古かったりするかもしれないので注意してください。

Vim本体のソースコードの在り処

Vim本体のソースコードは以下のGithubリポジトリにて管理されています。 ブランチはmasterのみで、その他のgithubリポジトリとかに依存してないので 単純にgitでこのリポジトリ1つをクローンすれば、Vim本体をソースコードをビルドしたりできます。

git clone https://github.com/vim/vim

ビルドの仕方はここでは説明しませんが、以下のページをみる良いかと思います。

Vim本体のソースコードの構成

Vim本体のソースコードはsrcフォルダ下で完結されてます。なので、初めはsrcファルダ以外のファイルやフォルダは無視して問題ないでしょう。 さて、肝心のsrcフォルダ下の構成ですが、まず簡単なのはxxdとteeファルダです。 Vimはxxdとteeというコマンドが別に同梱されています。以下のファルダ下にはそれぞれのコマンドのソースコードが含まれています。 逆にいうとVim本体のソースコードはこのフォルダには含まれてません。

  • src/xxd
  • src/tee

このため、Vim本体のソースコードを読みたい場合にはこのフォルダ内は無視して構わないでしょう。

次に、poフォルダです。これは各言語(プログラミング言語のほうじゃなくて英語とか韓国語とかのほうの言語)に対応するためのコードが含まれているフォルダになります。 確かにVim本体に関わるフォルダですが、そうそう触るようなものでもないのでこれも無視して構わないでしょう。

  • src/po

あと、以下のフォルダも初めは無視しちゃって構わないです。この辺のフォルダは私もまだちゃんと存在意義がわかっているわけではないですが、経験則的に触ったり見たりすることはめったにないです。

  • src/xpm
  • src/xdiff
  • src/GvimExt
  • src/VisVim
  • src/auto
  • src/if_perl_msvc
  • src/libvterm
  • src/os_mac_rsrc

すると残るフォルダは以下の通りとなります。この3つのフォルダはかなり重要です。とりあえず、Vim本体のソースコードを読みたいやVim魔改造したいと思ったならこのフォルダだけを読んでいけばなんとかなります。

  • src/testdir
  • src/proto
  • src/

以降、これらのフォルダについてちょっと詳しく説明したいと思います。

src/testdir

このフォルダはVimのテストが含まれているフォルダになります。Vim本体のソースコードとは直接的に関係はないですが、Vimにパッチを投げたりする上で避けては通れないのでちょっと説明します。 まず、Vimのテストは旧形式と新形式の2つのテスト方法があります。なぜ2つあるかというとただ単に新形式に移行しきれてないだけですので、いつかは新形式だけになると思います。

旧形式

旧形式はinファイルというVimに入力したい内容が書かれたファイルとokファイルという期待値が書かれたファイルの2つペアが1つのテストして、 「test{数字}.{拡張子}」というファイル名で保存されています。

  • src/testdir/test1.in
  • src/testdir/test1.ok

新形式

新形式はpureなVim script言語で書かれています。このため、テストファイルも「test_{テスト名}.vim」というファイル名のvimファイルです。

  • src/testdir/test_options.vim

もし、新形式のテストを追加したいと思ったらどうすればいいでしょうか。ここでもうちょっと詳しく説明したいと思います。

1) Make_all.makを編集する

Make_all.makにはテストするファイルのリストが格納されています。もし、既存のテストを修正したい場合にはこの工程は不要です。

  • src/testdir/Make_all.mak

このファイルのなかでNEW_TEST_RESという定義があり、この定義が新形式のテストのリストとなりますので、もし、「test_hoge.vim」というファイルを新しく追加したなら、 拡張子をresに変えた「test_hoge.res」という行を新しく追加しましょう。

NEW_TESTS_RES = \
    test_arabic.res \
    test_arglist.res \
        ...

2) vimファイルを作成する

「test_hoge.vim」を作成したいとするなら、

というパスにファイルを作成しましょう。 そのファイルの中身には、以下のような形式のVim script関数を定義します。

func Test_hoge1()
  " テスト処理
endfunc

この関数名が「Test_」で始まる関数が1つテストとして認識しますので、テストしたいだけ関数を定義していくことになります。 「Test_」で始まらない関数が定義できないというわけではなく、ただテストとして認識しないだけですので、別に「Test_」で始まらない関数を定義しても問題ないです。

テストを書く上で注意点としては、以下の点です。

  • 「fu」でも「function!」でもなく「func」で定義すること。
  • 「endfunc」で定義すること。
  • 「abort」や「range」はつけないこと。
  • 変更したオプションなどの変数は使用後に初期化しておくこと。
" テストする
set ttytype=xxx
call assert_report()

" 初期化する
set ttytype&
  • テストしたくない環境は、関数の先頭で即returnしてスキップすること。
func Test_hoge1()
  if !has('keymap')
    return
  endif

  " テスト処理
endfunc

3) テストしてみる

「test_hoge.vim」を作成したなら、早速テストを実施してみましょう。 以下のようなコマンドを実行すると1つのvimファイルだけをテストできます。

cd src/testdir
make test_hoge.res

FAILEDやSEGVという文字が表示されなければテスト成功していると考えて良いでしょう。

src/proto

このフォルダには関数のプロトタイプ宣言が定義しているファイルが拡張子proという形で格納されています。 これらのファイルは以下のファイルからincludeされていますので、もし、あたらしいproファイルを追加したい場合にはこのファイルの更新が必要です。

  • src/proto.h

また、拡張子がproなのでgrepする際に検索対象から外さないように気をつけてくださいw。

src/

さて早速、本題のVim本体のソースコードです。とはいっても私はすべて把握しているわけでもないので、把握している範囲で説明したいと思います。

src/feature.h

このファイルにはVim機能(FEATURE)のdefine変数が定義されています。Makefileやconfigure時の引数でVim機能の有効無効が変更できますが、 このファイルであるVim機能が無効なため、結果的に指定したVim機能は無効になるといったロジックが組まれています。

#if defined(FEAT_NORMAL) \
    && (defined(FEAT_GUI_GTK) \
    || (defined(FEAT_GUI_MOTIF) && defined(HAVE_XM_NOTEBOOK_H)) \
    || defined(FEAT_GUI_MAC) \
    || (defined(FEAT_GUI_MSWIN) \
        && (!defined(_MSC_VER) || _MSC_VER > 1020)))
# define FEAT_GUI_TABLINE
#endif

src/vim.h

このファイルにはVim本体のソースコードで全般的に使用されるdefine変数やenum型の定数がずらーっと定義されています。 このファイル内でfeature.hもincludeされています。 全般的に使用されるだけあってどれも重要だと思いますが、私が知っているなかでいくつか紹介します。

auto_event, event_T

このenum型はVimのイベントそれぞれのIDとなってます。たとえば、BufEnterイベントはEVENT_BUFENTERに対応してます。 BufEnterイベントの影響箇所を調べたいときはEVENT_BUFENTERgrepしたりするとよいでしょう。

enum auto_event
{
    EVENT_BUFADD = 0,       // after adding a buffer to the buffer list
    EVENT_BUFDELETE,        // deleting a buffer from the buffer list
    EVENT_BUFENTER,     // after entering a buffer
    EVENT_BUFFILEPOST,      // after renaming a buffer
    EVENT_BUFFILEPRE,       // before renaming a buffer
    EVENT_BUFHIDDEN,        // just after buffer becomes hidden
    ...
};

typedef enum auto_event event_T;

hlf_T, HL_FLAGS, HL_ATTR

これらはVim組み込みのhighlightのグループについてです。hlf_T型の値がHL_FLAGSの値と関連付いています。 たとえば、highlight EndOfBufferはHLF_EOB'~'に関連付いています。VimソースコードではHL_ATTRというマクロを使ってhighlightのグループのIDを取得したりしてます。

typedef enum
{
    HLF_8 = 0       /* Meta & special keys listed with ":map", text that is
               displayed different from what it is */
    , HLF_EOB       /* after the last line in the buffer */
    , HLF_AT        /* @ characters at end of screen, characters that
               don't really exist in the text */
    , HLF_D     /* directories in CTRL-D listing */
   ...
} hlf_T;

#define HL_FLAGS {'8', '~', '@', 'd', 'e', 'h', 'i', 'l', 'm', 'M', \
          'n', 'N', 'r', 's', 'S', 'c', 't', 'v', 'V', 'w', 'W', \
          'f', 'F', 'A', 'C', 'D', 'T', '-', '>', \
          'B', 'P', 'R', 'L', \
          '+', '=', 'x', 'X', '*', '#', '_', '!', '.', 'o', 'q', \
          'z', 'Z'  }

#define HL_ATTR(n)  highlight_attr[(int)(n)]

さて、HL_FLAGSの値はどこで使われているのかというと、Vim&highlightというオプション変数で使用されています。

'highlight' 'hl' string  (default (as a single string):
                     "8:SpecialKey,~:EndOfBuffer,@:NonText,
                     d:Directory,e:ErrorMsg,i:IncSearch,
                     l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,
                     N:CursorLineNr,r:Question,s:StatusLine,
                     S:StatusLineNC,c:VertSplit,t:Title,
                     v:Visual,V:VisualNOS,w:WarningMsg,
                     W:WildMenu,f:Folded,F:FoldColumn,
                     A:DiffAdd,C:DiffChange,D:DiffDelete,
                     T:DiffText,>:SignColumn,-:Conceal,
                     B:SpellBad,P:SpellCap,R:SpellRare,
                     L:SpellLocal,+:Pmenu,=:PmenuSel,
                     x:PmenuSbar,X:PmenuThumb,*:TabLine,
                     #:TabLineSel,_:TabLineFill,!:CursorColumn,
                     .:CursorLine,o:ColorColumn,q:QuickFixLine,
                     z:StatusLineTerm,Z:StatusLineTermNC")
            global
            {not in Vi}
    This option can be used to set highlighting mode for various
    occasions.  It is a comma separated list of character pairs.  The
    first character in a pair gives the occasion, the second the mode to
    use for that occasion.  The occasions are:

このため、&highlightの初期値をoption.c内で以下のように定義していたりします。新しいhighlightを追加した際にはちょっと注意が必要です。

#define HIGHLIGHT_INIT "8:SpecialKey,~:EndOfBuffer,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,N:CursorLineNr,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm,Z:StatusLineTermNC"

src/struct.h

このファイルにはVimで全般的に使用する構造体がずらーっと定義されています。

vartype_T, typval_T

struct.h内の構造体はどれも高頻度で登場するんですが、ぱっとわかりやすいのでいうとtypval_T型です。 typval_T型はVimの変数の値を保存しておくための構造体です。 もし、タイプがStringの変数を作りたいなら、typval_T.v_typeVAR_STRINGを設定し、typval_T.vvalにその変数の値を格納します。 タイプがStringの変数ならtypval_T.vval.v_stringに設定するといった感じです。

typedef enum
{
    VAR_UNKNOWN = 0,
    VAR_NUMBER,  // "v_number" is used
    VAR_STRING,  // "v_string" is used
    VAR_FUNC,    // "v_string" is function name
    VAR_PARTIAL, // "v_partial" is used
    VAR_LIST,    // "v_list" is used
    VAR_DICT,    // "v_dict" is used
    VAR_FLOAT,   // "v_float" is used
    VAR_SPECIAL, // "v_number" is used
    VAR_JOB,     // "v_job" is used
    VAR_CHANNEL, // "v_channel" is used
    VAR_BLOB,    // "v_blob" is used
} vartype_T;

typedef struct
{
    vartype_T   v_type;
    char    v_lock;     /* see below: VAR_LOCKED, VAR_FIXED */
    union
    {
    varnumber_T v_number;   /* number value */
#ifdef FEAT_FLOAT
    float_T     v_float;    /* floating number value */
#endif
    char_u      *v_string;  /* string value (can be NULL!) */
    list_T      *v_list;    /* list value (can be NULL!) */
    dict_T      *v_dict;    /* dict value (can be NULL!) */
    partial_T   *v_partial; /* closure: function with args */
#ifdef FEAT_JOB_CHANNEL
    job_T       *v_job;     /* job value (can be NULL!) */
    channel_T   *v_channel; /* channel value (can be NULL!) */
#endif
    blob_T      *v_blob;    /* blob value (can be NULL!) */
    }       vval;
} typval_T;

src/evalfunc.c

このファイルにはVim script関数に対応する関数がすべて定義されています。このため、Vim script関数の挙動がおかしいとか謎を感じたのならこのファイルの対応する関数をみるとよいでしょう。 たとえば、Vim scriptのabs関数はf_abs関数に対応しているといった感じです。functionsの定義でこのマッピングが行われています。

static struct fst
{
    char    *f_name;    /* function name */
    char    f_min_argc; /* minimal number of arguments */
    char    f_max_argc; /* maximal number of arguments */
    void    (*f_func)(typval_T *args, typval_T *rvar);
                /* implementation of function */
} functions[] =
{
#ifdef FEAT_FLOAT
    {"abs",     1, 1, f_abs},
    {"acos",        1, 1, f_acos},  /* WJMc */
#endif
    {"add",     2, 2, f_add},
  ...

    static void
f_abs(typval_T *argvars, typval_T *rettv)
{
  ...
}

src/version.c

Vim:intro:versionコマンドの2つの実装が定義されています。とてもわかり易く短いので、試しにコードをいじってビルドしてみるなら、とりあえずこのファイルをいじってみるのがオススメ。

src/option.c, src/option.h

この2つのファイルにはVim内のオプション変数が定義されています。optionsにはオプションがアルファベット順ですべてリストアップされています。 そのオプション変数に対応する変数がポインタで保存されているといった感じです。以下の例では&alephp_alephのポインタを設定していますので、p_alephgrepすると&alephの影響箇所を調べることができます。

#ifdef FEAT_RIGHTLEFT
EXTERN long p_aleph;    /* 'aleph' */
#endif
static struct vimoption options[] =
{
    {"aleph",       "al",   P_NUM|P_VI_DEF|P_CURSWANT,
#ifdef FEAT_RIGHTLEFT
                (char_u *)&p_aleph, PV_NONE,
#else
                (char_u *)NULL, PV_NONE,
#endif
                {
#if defined(MSWIN) && !defined(FEAT_GUI_MSWIN)
...

おわり

screen.cとかも脳内GCしたいのですが、長くなりすぎちゃうので一旦おわりです。 とりあえず、上記のファイルから足がかりにしてVim本体のソースコードを見ていくのがいいんではないでしょうか。