今日の関数 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をファイルの最初/最後にセットしています。