今日の関数 do_search その2 (https://github.com/vim/vim)
search.c
for (;;) {
複数の検索をループで処理していきます。
vimは;で複数の検索をすることができます。
例えば/pat1/;/pat2とすればまずpat1を検索しそのカーソルの 位置からpat2を検索することができます。
知ってました?私は知りませんでした。
searchstr = pat; dircp = NULL; /* use previous pattern */ if (pat == NULL || *pat == NUL || *pat == dirc) { if (spats[RE_SEARCH].pat == NULL) /* no previous pattern */ { searchstr = spats[RE_SUBST].pat; if (searchstr == NULL) { EMSG(_(e_noprevre)); retval = 0; goto end_do_search; } } else { /* make search_regcomp() use spats[RE_SEARCH].pat */ searchstr = (char_u *)""; } }
searchstrを準備します。NULLの場合のエラーチェックもしています。
if (pat != NULL && *pat != NUL) /* look for (new) offset */ { /* * Find end of regular expression. * If there is a matching '/' or '?', toss it. */ ps = strcopy; p = skip_regexp(pat, dirc, (int)p_magic, &strcopy); if (strcopy != ps) { /* made a copy of "pat" to change "\?" to "?" */ searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); pat = strcopy; searchstr = strcopy; } if (*p == dirc) { dircp = p; /* remember where we put the NUL */ *p++ = NUL; } spats[0].off.line = FALSE; spats[0].off.end = FALSE; spats[0].off.off = 0;
searchcmdlenはex_docmd.cで使われるグローバル変数です。
ここに使用したコマンドの長さを入れます。
skip_regexpはまた詳しく見たいと思いますが検索のregexpをstrcopyに入れて そのregexpの最後のポインタを返す関数です。
skip_regexpがstrcopyを変えたらつまりregexが見つかったら(if strcopy != ps) searchcmdlenを足して patにそのregexpを入れます。
pはregexの最後を指しています。
もしそこに'/'か'?'があったら( if(*p == dirc)) dircpにその場所を保存しておきそこにNULを入れてpを 一つ進めます。
これでpは検索パターンの後ろのオプションの部分を指していることになります。
またspatsを初期化します。 spatsはoffset情報をいれて置くバッファです。
if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p)) spats[0].off.line = TRUE; else if ((options & SEARCH_OPT) && (*p == 'e' || *p == 's' || *p == 'b')) { if (*p == 'e') /* end */ spats[0].off.end = SEARCH_END; ++p; }
search patternの後に +,-か数字のオプションがついていたらoff.lineにTRUEをいれます。
/pat/5ならpatを探してその5行下にカーソルを移動します。
e,s,bのオプションがついていたらそれを一つ飛ばします。
eの場合はoff.endにSEARCH_ENDをいれます。
どちらの場合でもpは数字の場所を指しています。
if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') /* got an offset */ { /* 'nr' or '+nr' or '-nr' */ if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1))) spats[0].off.off = atol((char *)p); else if (*p == '-') /* single '-' */ spats[0].off.off = -1; else /* single '+' */ spats[0].off.off = 1; ++p; while (VIM_ISDIGIT(*p)) /* skip number */ ++p; }
数字オプションをoff.offに格納します。+ - は+1, -1になります。
pを一つ進め後の数字の部分は飛ばします。
ここで/pat/e5だった場合off.lineにはFALSEが入ったままoff.offに数字が入ることになります。
つまりpatを探したあと5文字カーソルを進めるということになります。
searchcmdlen += (int)(p - pat); pat = p; /* put pat after search command */
pが数字オプションで進んでいる分をsearchcmdlenに調整しpatに入れます。
if ((options & SEARCH_ECHO) && messaging() ...
ここはmessage関係の処理をするだけなので飛ばしましょう。
/* * If there is a character offset, subtract it from the current * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2". * Skip this if pos.col is near MAXCOL (closed fold). * This is not done for a line offset, because then we would not be vi * compatible. */ if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) { if (spats[0].off.off > 0) { for (c = spats[0].off.off; c; --c) if (decl(&pos) == -1) break; if (c) /* at start of buffer */ { pos.lnum = 0; /* allow lnum == 0 here */ pos.col = MAXCOL; } } else { for (c = spats[0].off.off; c; ++c) if (incl(&pos) == -1) break; if (c) /* at end of buffer */ { pos.lnum = curbuf->b_ml.ml_line_count + 1; pos.col = 0; } } }
ここではoff.lineがFALSEでoff.offが0でない場合の処理です。
つまり検索のあとカーソルを文字単位で動かす場合です。
これを現在のカーソルの位置からその分を引いておくことで実装しています。
declとinclはposのcolを上げ下げするサブルーチンで-1を返すと declならfileの最初inclならfileの最後にカーソルが達したということになります。
このコードの流れはoff.offに入っている数字でループしてdecl/inclが-1なら breakしてループからでます。
その時cがまだゼロになっていないならoff.off分の移動を全部し終わる前に ループからでたことになるので(cがゼロならループは最後まで回っていることになる)
posをファイルの最初/最後にセットしています。