gettavbar()/settabvar()の追加

  • getbufvar()/setbufvar()等はあるのにgettabvar()/settabvar()はないので、その実装をする。
  • 2008-11-16 - 完成。

実装

/*
 * "gettabvar()" function
 */
    static void
f_gettabvar(argvars, rettv)
    typval_T    *argvars;
    typval_T    *rettv;
{
    tabpage_T   *tp;
    char_u      *varname;
    dictitem_T  *v;
    tabpage_T   *save_curtab;
    char_u      *save_p_ei;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL));
    varname = get_tv_string_chk(&argvars[1]);

    ++emsg_off;

    if (tp != NULL && varname != NULL)
    {
        /* set 'eventignore' for all events to avoid side effects. */
        save_p_ei = vim_strsave(p_ei);
        set_string_option_direct((char_u *)"ei", -1,
                                 (char_u *)"all", OPT_FREE, SID_NONE);

        save_curtab = curtab;
        goto_tabpage_tp(tp);

        if (*varname == NUL)
            /* let gettabvar({tabnr}, "") return the "t:" dictionary.  The
             * scope prefix before the NUL byte is required by
             * find_var_in_ht(). */
            varname = (char_u *)"t:" + 2;
        /* look up the variable */
        v = find_var_in_ht(&tp->tp_vars.dv_hashtab, varname, FALSE);
        if (v != NULL)
            copy_tv(&v->di_tv, rettv);

        goto_tabpage_tp(save_curtab);
        if (curtab == save_curtab) {
            /* fake the flags to avoid flickering. */
            redraw_all_later(VALID);
            must_redraw = VALID;
        }

        /* restore 'eventignore' with the previous value. */
        if (save_p_ei != NULL) {
            set_string_option_direct((char_u *)"ei", -1,
                                     save_p_ei, OPT_FREE, SID_NONE);
            free_string_option(save_p_ei);
        }
    }

    --emsg_off;
}


/*
 * "settabvar()" function
 */
    static void
f_settabvar(argvars, rettv)
    typval_T    *argvars;
    typval_T    *rettv;
{
    tabpage_T   *tp;
    char_u      *varname;
    char_u      *tabvarname;
    typval_T    *varp;
    tabpage_T   *save_curtab;
    char_u      *save_p_ei;

    rettv->vval.v_number = 0;

    if (check_restricted() || check_secure())
        return;

    tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL));
    varname = get_tv_string_chk(&argvars[1]);
    varp = &argvars[2];

    if (tp != NULL && varname != NULL && varp != NULL)
    {
        /* set 'eventignore' for all events to avoid side effects. */
        save_p_ei = vim_strsave(p_ei);
        set_string_option_direct((char_u *)"ei", -1,
                                 (char_u *)"all", OPT_FREE, SID_NONE);

        save_curtab = curtab;
        goto_tabpage_tp(tp);

        tabvarname = alloc((unsigned)STRLEN(varname) + 3);
        if (tabvarname != NULL)
        {
            STRCPY(tabvarname, "t:");
            STRCPY(tabvarname + 2, varname);
            set_var(tabvarname, varp, TRUE);
            vim_free(tabvarname);
        }

        goto_tabpage_tp(save_curtab);
        if (curtab == save_curtab) {
            /* fake the flags to avoid flickering. */
            redraw_all_later(VALID);
            must_redraw = VALID;
        }

        /* restore 'eventignore' with the previous value. */
        if (save_p_ei != NULL) {
            set_string_option_direct((char_u *)"ei", -1,
                                     save_p_ei, OPT_FREE, SID_NONE);
            free_string_option(save_p_ei);
        }
    }
}

以下、作業時の雑多なメモ

Vimスクリプトの組み込み関数について

  • Vimスクリプトの組み込み関数の実装はsrc/eval.cにある。
  • Vimスクリプトのgetbufvar()に対応するCの関数はf_getbufvar()
  • 組み込み関数に対応するCの関数のプロトタイプ: static void f_getbufvar __ARGS((typval_T *argvars, typval_T *rettv));
    • __ARGS()はK&R C対策のマクロだろう。昔からあるソフトでよく見かけるアレ。
    • 関数への引数はargvars、関数の戻り値はrettvなのだろう。
    • typval_TはVimスクリプトで扱える値(数値だの文字列だの)を表す構造体。定義はsrc/structs.hにある。
    • 多分argvarsは引数列への参照、rettvは戻り値を格納するための場所への参照だろう。
  • 関数名、引数の最小個数・最大個数、実装へのポインタをまとめたテーブルがsrc/eval.cのfunctions[]にある。組み込み関数を追加する場合は適切な実装を書いた上でこのテーブルにエントリーを追加する必要がある。
  • 組み込み関数は呼び出される際に引数の個数のチェックは行われているため、個々の実装でチェックする必要はない(call_func() in src/eval.c)。
    • 引数の個数が可変の場合、argvars[n].v_type == VAR_UNKNOWNであるので、これでチェックできる(nは与えられた引数の個数)。
  • 何らかの値を参照するだけの関数なら簡単そう。
  • 何らかの値を変更する関数の場合、restricted及びsecureのチェックをする必要がある。これはif (check_restricted() || check_secure()) return;で簡単にできる。

以上のことがgetbufvar()/setbufvar()を基点に分かった。あとはtab pagesに関する情報をどう参照するか・どう変更するかさえ分かれば書けるかな。

tab pagesについて

  • 対応する構造体はtabpage_T
    • tp_winvartp_varsがtab page localな変数を保持する辞書らしい。違いが今一分からないな。

辞書/dict_T

  • dict_T *tp_vars - 辞書そのものを表す構造体(参照カウントなどの各種関連情報を含む)。普通はこちらを使うようだ。
  • dictitem_T *tp_winvars - 辞書が持つ要素の1つを表す構造体。スコープ内の変数の一覧を辞書として返すために用いられている。Vimスクリプトからはt:等とスコープの接頭辞のみで変数名を省略した式で参照できる(こんな式があること、全然知らなかった)。
  • dict_T.dv_hashtabが辞書の各要素を参照しているハッシュテーブル(へのポインタ)。ハッシュテーブル自体は色々なところで用いられるため、辞書専用ではなく、汎用化した形で提供しているのだろう。

ハッシュテーブル/hashtab_T

  • hashtab_T.ht_arrayhashitem_Tの配列へのポインタ。
  • hashitem_Tはハッシュテーブルの各要素を表す型。
    • 面白いことにhashitem_Tのメンバーはhi_key(ハッシュのキーとなる値=文字列へのポインタ)とhi_hash(hi_keyのハッシュ値のキャッシュ)の2つしか存在しない。つまり、キーに対応する値へのポインタが存在しない
    • 省略すれば値へのポインタの分だけメモリの節約になる。頻繁に用いられる箇所なのでこのような最適化が施されているのだろう。
    • なぜ省略できるのかというと、キーへのポインタから値へのポインタを算出するようにしているからだ。これは次の点を利用して実現可能にしている: ハッシュテーブルに格納したい値は何らかの構造体で、その構造体の内部にはキーとなりうる値へのポインタがある。キーの値へのポインタとして構造体の内部の適切な位置を使えば、キーに対応する値すなわち構造体そのものへのポインタはキーから逆算できるという訳だ。
    • ただ、これを行うためには適切な構造体が分かっていなければならないという問題がある。もっとも、1つのハッシュテーブルに格納される値の型は大抵統一されているだろうから、別に問題にはならないのか。
    • すげぇ。こんな最適化初めて見たよ。これを考えた人、誰なんだろう。

Take 1 - gettabvar()のみ

  • 問題点: curtab(現在のタブページ)以外の変数を参照する場合、画面がちらつく。
  • 原因: goto_tabpage_tp()によるタブページの切り替え。
    • でもこれをしないとt:でのスコープそのものへの参照ができない。これを提供しないなら不可能ではないが、他のインターフェースとの一貫性に欠けてしまう。
    • また、settabvar()ではタブページの切り替えが必須であるようなので、goto_tabpage_tp()を避けることは難しい。
    • 一応、バイパスできなくはないが、上側のインターフェースを使っておかないと色々と不味いだろうし。
  • 対策: 単に変数の参照・変更を行うだけであり、画面内用に変化は起きないと考えられる。よって、goto_tabpage_tp()によって起こる画面の更新を何らかの方法で抑制すればよい。

    • 別の問題点: goto_tabpage_tp()によりautocmdが実行される。これにより画面の更新が必要になった場合に矛盾が生じる。
    • これに対してはautocmdを実行しないという処置が取れれば対応できるのだが。
  • 対策1: avoid flickering

    • 2回目のgoto_tabpage_tp()、つまり変数参照前のcurtabに戻した後、本当に以前のタブページに戻っているかを確認してからredraw_all_later(VALID)must_redraw = VALIDをすればflickeringはなくなった。
/*
 * "gettabvar()" function
 */
    static void
f_gettabvar(argvars, rettv)
    typval_T    *argvars;
    typval_T    *rettv;
{
    tabpage_T   *tp, *save_curtab;
    char_u      *varname;
    dictitem_T  *v;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL));
    varname = get_tv_string_chk(&argvars[1]);

    ++emsg_off;

    if (tp != NULL && varname != NULL)
    {
        save_curtab = curtab;
        goto_tabpage_tp(tp);

        if (*varname == NUL)
            /* let gettabvar({tabnr}, "") return the "t:" dictionary.  The
             * scope prefix before the NUL byte is required by
             * find_var_in_ht(). */
            varname = (char_u *)"t:" + 2;
        /* look up the variable */
        v = find_var_in_ht(&tp->tp_vars.dv_hashtab, varname, FALSE);
        if (v != NULL)
            copy_tv(&v->di_tv, rettv);

        /* Restore current tabpage and window, if still valid (autocomands can
         * make them invalid). */
        if (valid_tabpage(save_curtab))
            goto_tabpage_tp(save_curtab);
    }

    --emsg_off;
}

Take 2 - gettabvar()完成版

  • autocmdの件は'eventignore'を使えば解決した。以下が完成版。
  • 本当はFEAT_WINDOWS等で激しく#ifdef/#else/#endifしなければならないが、今時そんな低機能なものを使う人は珍しいことと、そういう方面であればまずFEAT_EVALを切るだろうということから、括ってません。適切な括り方が分からないとも言う。
  • (ページ上部に移動)
changed January 15, 2008