オプションの追加方法(とおまけ)

@KSuzukiiさんがおもしろいこと始めたようでブログに載ってました。set nuの数字との区切り文字を変更できるようにするという壮大な計画!

自由に設定出来るようにするにはどうすればいいんだろう?

Vim:行数と文字との間を空白文字以外にしたい - LSI設計雑記帳 http://lsifrontend.blog100.fc2.com/blog-entry-231.html

とのことだったので、ちょうど昨日やったやつと共にちょっとした説明など。

何してたのか?

実は随分前に set nu のナンバリングを特定の位置からの相対値にする(http://d.hatena.ne.jp/tkuro/20081219/1229676014)、というのをやったのですが、あのあとしばらくなにもせずに放置していたら、vimのほうでいつの間にか 'relativenumber' というのが追加されていたみたいです。これを使うと、「現在のカーソルの位置」からの相対値表示ができます(カーソルを動かすとグリグリうごくw)。値は abs() してあって上方向も正の値になってます。すなわち、あくまでvim的に「上 14k」とか「うりゃっ 14dd」のように華麗に行移動するためのものであって、僕の目的である「行数えてよ。」とはちっと違います。
とっとと弄ってしまいましょ。と。
#source は hg clone https://vim.googlecode.com/hg/ vim で最新版拾って来ました。

どこいじるのか?

とりあえずまずはオプションをつけるとこまでやります。

src/option.cを見ると最初の方にコメントにて

/*
 * Code to handle user-settable options. This is all pretty much table-
 * driven. Checklist for adding a new option:
(1)- Put it in the options array below (copy an existing entry).
 * - For a global option: Add a variable for it in option.h.
 * - For a buffer or window local option:
(2)  - Add a PV_XX entry to the enum below.
(3)  - Add a variable to the window or buffer struct in structs.h.
(4)  - For a window option, add some code to copy_winopt().
 *   - For a buffer option, add some code to buf_copy_options().
 *   - For a buffer string option, add code to check_buf_options().
(-)- If it's a numeric option, add any necessary bounds checks to do_set().
 * - If it's a list of flags, add some code in do_set(), search for WW_ALL.
 * - When adding an option with expansion (P_EXPAND), but with a different
 *   default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP.
(-)- Add documentation!  One line in doc/help.txt, full description in
 *   options.txt, and any other related places.
(-)- Add an entry in runtime/optwin.vim.
 * When making changes:
(-)- Adjust the help for the option in doc/option.txt.
(-)- When an entry has the P_VIM flag, or is lacking the P_VI_DEF flag, add a
 *   comment at the help for the 'compatible' option.
 */

と全てやり方が書いてあります*1
今回やってみようかなーというものに番号を振って見ました。do_set()での範囲チェックとか必要無いのでカットです。。。
ドキュメントどうしようかなー。

さてさて、まずは(1)。src/option.c に options[] というvimoption 構造体の配列があります。ここに追加するようです。

どんなふうに?

順番に

{
  "オプション名", "短縮名", 
  フラグ, 
 オプション変数へのポインタ, 
  ローカルインデックス(グローバルな場合PV_NONE), 
  { vi 向けの初期値,  vim 向けの初期値 },
  SCRIPT_INIT(ここはvimが面倒みてくれます)
}

今回みたいに window-local なオプションの場合、少し注意が必要ですが、まあまあ直感的(?)。
以下順番に。

名前を決めます
今回は 'numbertop', 'nbt' としました
フラグを決定
P_NUM|P_VIM|P_RWIN (数値、vim専用フラグ、変更されたら即画面更新) で
オプション変数へのポインタ
これはwindow-localの場合 VAR_WIN で(詳しくはoption.cの該当コメントを参照してください)
ローカルインデックス
 ここでは PV_NBT としました。
初期値
両方 0

てな感じ。

(2) ローカルインデックス

ローカルインデックスはwindow-local(またはbuffer-local)な変数を取得するためのインデックスですね。'number'のエントリWV_NU を真似して、WV_NBTエントリをつくり、そこからOPT_WIN(WV_NBT)でPV_NBTエントリを作りました。

(3) ウィンドウ構造体に変数追加

structs.h にwinopt_Tな構造体があります。ここに追加します。今回は負の数も使いたかったのでlongにしました。

(4) copy_winoptにコピーコード追加

他の真似して追加するだけですね。

さてやってみよう(まずはオプション動作確認だけ)

実際に上記の作業をしてみます。diffがわかりやすいのかどうかわかりませんが、とりあえずhg diffにて、

diff -r 54d621a3b561 src/option.c
--- a/src/option.c      Thu Jan 26 20:58:26 2012 +0100
+++ b/src/option.c      Fri Jan 27 18:32:21 2012 +0900
@@ -209,6 +209,7 @@
 #endif
 #define PV_NU          OPT_WIN(WV_NU)
 #define PV_RNU         OPT_WIN(WV_RNU)
+#define PV_NBT         OPT_WIN(WV_NBT)
 #ifdef FEAT_LINEBREAK
 # define PV_NUW                OPT_WIN(WV_NUW)
 #endif
@@ -1868,6 +1869,9 @@
     {"number",     "nu",   P_BOOL|P_VI_DEF|P_RWIN,
                            (char_u *)VAR_WIN, PV_NU,
                            {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
+    {"numbertop",   "nbt",  P_NUM|P_RWIN|P_VIM,
+                           (char_u *)VAR_WIN, PV_NBT,
+                           {(char_u *)0L, (char_u *)0L} SCRIPTID_INIT},
     {"numberwidth", "nuw",  P_NUM|P_RWIN|P_VIM,
 #ifdef FEAT_LINEBREAK
                            (char_u *)VAR_WIN, PV_NUW,
@@ -9610,6 +9614,7 @@
        case PV_FMR:    return (char_u *)&(curwin->w_p_fmr);
 #endif
        case PV_NU:     return (char_u *)&(curwin->w_p_nu);
+        case PV_NBT:    return (char_u *)&(curwin->w_p_nbt);
        case PV_RNU:    return (char_u *)&(curwin->w_p_rnu);
 #ifdef FEAT_LINEBREAK
        case PV_NUW:    return (char_u *)&(curwin->w_p_nuw);
@@ -9804,6 +9809,7 @@
     to->wo_list = from->wo_list;
     to->wo_nu = from->wo_nu;
     to->wo_rnu = from->wo_rnu;
+    to->wo_nbt = from->wo_nbt;
 #ifdef FEAT_LINEBREAK
     to->wo_nuw = from->wo_nuw;
 #endif
diff -r 54d621a3b561 src/option.h
--- a/src/option.h      Thu Jan 26 20:58:26 2012 +0100
+++ b/src/option.h      Fri Jan 27 18:32:21 2012 +0900
@@ -1067,6 +1067,7 @@
 #endif
     , WV_NU
     , WV_RNU
+    , WV_NBT
 #ifdef FEAT_LINEBREAK
     , WV_NUW
 #endif
diff -r 54d621a3b561 src/structs.h
--- a/src/structs.h     Thu Jan 26 20:58:26 2012 +0100
+++ b/src/structs.h     Fri Jan 27 18:32:21 2012 +0900
@@ -169,6 +169,8 @@
 #define w_p_list w_onebuf_opt.wo_list  /* 'list' */
     int                wo_nu;
 #define w_p_nu w_onebuf_opt.wo_nu      /* 'number' */
+    long       wo_nbt;
+#define w_p_nbt w_onebuf_opt.wo_nbt    /* 'numbertop' */
     int                wo_rnu;
 #define w_p_rnu w_onebuf_opt.wo_rnu    /* 'relativenumber' */
 #ifdef FEAT_LINEBREAK

実行してみて各windowで異なる値が設定できることを確認します。おー、できてる。
良さげなので一回commit。

行番号のところを弄る

仕様としては

numbertop = 0 の時
通常動作。カーソル位置が原点。
numbertop > 0 の時
設定した値の行を原点として固定。原点より上もプラス表示(abs)
numbertop < 0 の時
設定した値の行を原点として固定。原点より上はマイナス表示

とします。
ちらと見るとmisc2.c のget_cursor_rel_lnum()をいじれば良いことがわかります。
んで、オプションの値にはMAGICALな変数 curwin からアクセスできます。 curwin->w_p_nbtですね。
つわけで、おもむろに。

diff -r adff37f40674 src/misc2.c
--- a/src/misc2.c       Fri Jan 27 18:36:17 2012 +0900
+++ b/src/misc2.c       Fri Jan 27 18:55:25 2012 +0900
@@ -515,6 +515,15 @@
 #endif
        retval = lnum - cursor;

+    if (curwin->w_p_nbt > 0) {
+        retval -= curwin->w_p_nbt - cursor;
+    } else if (curwin->w_p_nbt < 0) {
+        retval += curwin->w_p_nbt + cursor;
+        return retval;
+    }
+
+    retval = labs(retval);
+
     return retval;
 }

diff -r adff37f40674 src/screen.c
--- a/src/screen.c      Fri Jan 27 18:36:17 2012 +0900
+++ b/src/screen.c      Fri Jan 27 18:55:25 2012 +0900
@@ -2321,7 +2321,7 @@
                num = (long)lnum;
            else
                /* 'relativenumber', don't use negative numbers */
-               num = labs((long)get_cursor_rel_lnum(wp, lnum));
+               num = (long)get_cursor_rel_lnum(wp, lnum);

            sprintf((char *)buf, "%*ld ", w, num);
 #ifdef FEAT_RIGHTLEFT
@@ -3481,7 +3481,7 @@
                            num = (long)lnum;
                        else
                            /* 'relativenumber', don't use negative numbers */
-                           num = labs((long)get_cursor_rel_lnum(wp, lnum));
+                           num = (long)get_cursor_rel_lnum(wp, lnum);

                        sprintf((char *)extra, "%*ld ",
                                                number_width(wp), num);

うむむ。でけた。ヒドイコードだけどもなんとかできた。

あとがき

マイナスの場合widthが足りなくなるので補正しなければ、なのとか、get_cursor_rel_lnum()がfoldの計算を変なふうにやってるので(実際に順に追っかけて見てる)微妙にカーソル位置によっておかしくなったりするんだけど、とりあえずはおっけー。
そのうち治そう。

結論

以上、参考になるでしょうか?
わかりにくかったらツッコミお願いしますー

*1:実は僕は読まずにやってしまった ,,,,,