Vim本体のソースコードの読みはじめかた(仮)
私は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
あと、以下のフォルダも初めは無視しちゃって構わないです。この辺のフォルダは私もまだちゃんと存在意義がわかっているわけではないですが、経験則的に触ったり見たりすることはめったにないです。
すると残るフォルダは以下の通りとなります。この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ファイルを作成する
というパスにファイルを作成しましょう。 そのファイルの中身には、以下のような形式の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_BUFENTER
でgrepしたりするとよいでしょう。
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_type
にVAR_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
にはオプションがアルファベット順ですべてリストアップされています。
そのオプション変数に対応する変数がポインタで保存されているといった感じです。以下の例では&aleph
はp_aleph
のポインタを設定していますので、p_aleph
でgrepすると&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本体のソースコードを見ていくのがいいんではないでしょうか。