今日の関数 searchit その5 (https://github.com/vim/vim)
/* With the SEARCH_END option move to the last character * of the match. Don't do it for an empty match, end * should be same as start then. */ if ((options & SEARCH_END) && !(options & SEARCH_NOOF) && !(matchpos.lnum == endpos.lnum && matchpos.col == endpos.col)) { /* For a match in the first column, set the position * on the NUL in the previous line. */ pos->lnum = lnum + endpos.lnum; pos->col = endpos.col; if (endpos.col == 0) { if (pos->lnum > 1) /* just in case */ { --pos->lnum; pos->col = (colnr_T)STRLEN(ml_get_buf(buf, pos->lnum, FALSE)); } } else { --pos->col; #ifdef FEAT_MBYTE if (has_mbyte && pos->lnum <= buf->b_ml.ml_line_count) { ptr = ml_get_buf(buf, pos->lnum, FALSE); pos->col -= (*mb_head_off)(ptr, ptr + pos->col); } #endif } } else { pos->lnum = lnum + matchpos.lnum; pos->col = matchpos.col; }
matchした後でSEARCH_ENDの設定をしているところです。
if ((options & SEARCH_END) && !(options & SEARCH_NOOF) && !(matchpos.lnum == endpos.lnum && matchpos.col == endpos.col)) {
SEARCH_ENDのオプションがついていてmatchした文字が空でないかをチェックします。
pos->lnum = lnum + endpos.lnum; pos->col = endpos.col; if (endpos.col == 0) { if (pos->lnum > 1) /* just in case */ { --pos->lnum; pos->col = (colnr_T)STRLEN(ml_get_buf(buf, pos->lnum, FALSE)); } } else { --pos->col; #ifdef FEAT_MBYTE if (has_mbyte && pos->lnum <= buf->b_ml.ml_line_count) { ptr = ml_get_buf(buf, pos->lnum, FALSE); pos->col -= (*mb_head_off)(ptr, ptr + pos->col); } #endif }
SEARCH_ENDオプションがついているのでカーソルの位置をマッチした最後に持っていきます。
その時マッチの最後の部分が0ならば前の行の最後にカーソルを移動します。そうでなければ
マッチした最後の部分から一つ戻った位置にカーソルをおきます。
else { pos->lnum = lnum + matchpos.lnum; pos->col = matchpos.col; }
SEARCH_ENDオプションがない場合です。
カーソルの位置をマッチした位置に持っていきます。
#ifdef FEAT_VIRTUALEDIT pos->coladd = 0; #endif found = 1; first_match = FALSE; /* Set variables used for 'incsearch' highlighting. */ search_match_lines = endpos.lnum - matchpos.lnum; search_match_endcol = endpos.col; break; }
found, first_matchなどのフラグを調整して行ごとのループ(697行目のfor loop)から抜け出します。
line_breakcheck(); /* stop if ctrl-C typed */ if (got_int) break;
ここに来る場合はマッチしなかった場合です。
毎行ループするたびこの部分を通ります。
まずはctrl-cで中断されないかをチェックします。
#ifdef FEAT_SEARCH_EXTRA /* Cancel searching if a character was typed. Used for * 'incsearch'. Don't check too often, that would slowdown * searching too much. */ if ((options & SEARCH_PEEK) && ((lnum - pos->lnum) & 0x3f) == 0 && char_avail()) { break_loop = TRUE; break; } #endif
ここではサーチしている間にまた文字入力がされていないかをチェックします。
このifの二番目の条件は
あまりに頻繁にbreakしないように間隔を空けるための条件です。
lnumはループしている行数です。それからカーソルのある行数を引いた数が
0x3f(0011 1111,10進数では63)との論理的ANDが0の時breakします。
つまり64で割り切れる数の時この条件が満たされます(0x0100 0000(64) & 0011 1111(0x3f) == 0)。
for (loop = 0; loop <= 1; ++loop) { ... if (loop && lnum == start_pos.lnum) break; /* if second loop, stop where started */ } at_first_line = FALSE;
loopが0でないということは二回目のloopだということなので
その上で検索のスタート位置の行とlnumが一致しているということは
検索の結果が一周回って元に戻ってきたということです。
その時もbreakします。
/* * Stop the search if wrapscan isn't set, "stop_lnum" is * specified, after an interrupt, after a match and after looping * twice. */ if (!p_ws || stop_lnum != 0 || got_int || called_emsg #ifdef FEAT_SEARCH_EXTRA || break_loop #endif || found || loop) break;
ここに来るのは全部の行を周り終わった時です。
wrapscanでない場合、中断された時、ループを二回回った時にbreakします。
/* * If 'wrapscan' is set we continue at the other end of the file. * If 'shortmess' does not contain 's', we give a message. * This message is also remembered in keep_msg for when the screen * is redrawn. The keep_msg is cleared whenever another message is * written. */ if (dir == BACKWARD) /* start second loop at the other end */ lnum = buf->b_ml.ml_line_count; else lnum = 1; if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) give_warning((char_u *)_(dir == BACKWARD ? top_bot_msg : bot_top_msg), TRUE); }
行を全部ループし終わったのでwrapscanするために後方検索ならlnumにbufferの一番最後の行の数を入れて
前方検索ならlnumを1にして次のloopに入ります。
その際検索が最後または最初の行に戻ることをワーニングします。
今日の関数 searchit その4 (https://github.com/vim/vim)
if (dir == BACKWARD) { /* * Now, if there are multiple matches on this line, * we have to get the last one. Or the last one before * the cursor, if we're on that line. * When putting the new cursor at the end, compare * relative to the end of the match. */ match_ok = FALSE; for (;;) { /* Remember a position that is before the start * position, we use it if it's the last match in * the line. Always accept a position after * wrapping around. */ if (loop || ((options & SEARCH_END) ? (lnum + regmatch.endpos[0].lnum < start_pos.lnum || (lnum + regmatch.endpos[0].lnum == start_pos.lnum && (int)regmatch.endpos[0].col - 1 < (int)start_pos.col + extra_col)) : (lnum + regmatch.startpos[0].lnum < start_pos.lnum || (lnum + regmatch.startpos[0].lnum == start_pos.lnum && (int)regmatch.startpos[0].col < (int)start_pos.col + extra_col)))) { match_ok = TRUE; matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; # ifdef FEAT_EVAL submatch = first_submatch(®match); # endif } else break; /* * We found a valid match, now check if there is * another one after it. * If vi-compatible searching, continue at the end * of the match, otherwise continue one position * forward. */ if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { if (nmatched > 1) break; matchcol = endpos.col; /* for empty match: advance one char */ if (matchcol == matchpos.col && ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } } else { /* Stop when the match is in a next line. */ if (matchpos.lnum > 0) break; matchcol = matchpos.col; if (ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } } if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, #ifdef FEAT_RELTIME tm #else NULL #endif )) == 0) break; /* Need to get the line pointer again, a * multi-line search may have made it invalid. */ ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); } /* * If there is only a match after the cursor, skip * this match. */ if (!match_ok) continue; }
後方検索をするところのコードです。前方検索の時とは逆にmatch_okがまずFALSEで与えられます。
このmatch_okがFALSEの間は次の行へと検索が続くところは前方検索の時と同じです。
if ( loop || ((options & SEARCH_END) ? (lnum + regmatch.endpos[0].lnum < start_pos.lnum || (lnum + regmatch.endpos[0].lnum == start_pos.lnum && (int)regmatch.endpos[0].col - 1 < (int)start_pos.col + extra_col)) : (lnum + regmatch.startpos[0].lnum < start_pos.lnum || (lnum + regmatch.startpos[0].lnum == start_pos.lnum && (int)regmatch.startpos[0].col < (int)start_pos.col + extra_col)))) { ...
このloopは行をループするループのもう一つ外側にあるループのカウンターです。
for (loop = 0; loop <= 1; ++loop) /* loop twice if 'wrapscan' set */ { for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count; lnum += dir, at_first_line = FALSE) {
このifの条件はまずloopが1かまたはSEARCH_ENDのオプションがついているかによって分かれています。
その条件が真なら次にマッチした最後の場所の行がカーソルがあった行の前にあるか
同じ行にあってその位置 - 1 がカーソルの位置の前にあるかを調べます。
最初の条件が偽の場合真の場合と同じことをチェックしますが同じ行にある時のマッチの最後の位置から1 を引いていません。
つまりloopが1 かまたはSEARCH_ENDのオプションがついているなら、マッチした最後の位置がカーソルの位置と同じかその前にある、そうでないならマッチした最後の位置がカーソルよりも前にあるということをチェックしています。
{ match_ok = TRUE; matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; # ifdef FEAT_EVAL submatch = first_submatch(®match); # endif } else break;
この条件が揃う、つまりマッチの最後がカーソルより前なら
マッチの位置を変数に入れループを出るためにmatch_okをTRUEにします。
そうでないならbreakして次の行のループに進みます。
/* * We found a valid match, now check if there is * another one after it. * If vi-compatible searching, continue at the end * of the match, otherwise continue one position * forward. */ if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { if (nmatched > 1) break; matchcol = endpos.col; /* for empty match: advance one char */ if (matchcol == matchpos.col && ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } } else { /* Stop when the match is in a next line. */ if (matchpos.lnum > 0) break; matchcol = matchpos.col; if (ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } }
この部分は検索で見つかったmatchposからカーソルの間にさらにまだ見つからないかを調べていきます。
前方検索の時と同じで nmatchedが1以上の場合はmatchの最後が次の行なのでwhileループから抜けmatch_okはTRUEなのでループから抜けます。
nmatchedが1ならmatchcolの場所をそのマッチの最後の位置にします。これはCPOオプションの'c'フラグによる仕様です。
cフラグがあると検索が終わった時にそのマッチした文字の最後から次に検索するようになります。
"abababababab" で "/abab" と検索した場合フラグがある場合3つない場合5つマッチします。
そのあとmatchcolとmatchpos.colが同じ場合(empty match)の場合matchcolを一つ進めています。
これは"/^"のような検索の時がこのケースになります。
このあとmatchcolからもう一回検索します。
cフラグがない場合matchcolをマッチした場所にしてもしそこが行末出ないなら一つ進めて次の検索を行います。
if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, #ifdef FEAT_RELTIME tm #else NULL #endif )) == 0) break; /* Need to get the line pointer again, a * multi-line search may have made it invalid. */ ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); }
ptrのmatchcolの場所が行末(NUL)になるかもう一度検索をした結果が何も見つからなかったら(0なら)
breakしてmatch_okはTRUEなのでループから抜けます。
もし検索した結果が見つかったならもう一度ptrにその行のバッファをセットして無限ループを回して
さらにそのmatchcolからカーソルの間に検索を続けます。
/* * If there is only a match after the cursor, skip * this match. */ if (!match_ok) continue; }
後方検索の最後の部分です。match_okにならずにbreakしてきた場合次の行にループを進めています。
今日の関数 searchit その3 (https://github.com/vim/vim)
while (matchpos.lnum == 0 && ((options & SEARCH_END) && first_match ? (nmatched == 1 && (int)endpos.col - 1 < (int)start_pos.col + extra_col) : ((int)matchpos.col - (ptr[matchpos.col] == NUL) < (int)start_pos.col + extra_col))) {
前回どのような時にこのwhileに入るかを見ました。ここでは前方検索の時
matchした文字がカーソルよりも前に来るのを避けるための調節をします。
if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { if (nmatched > 1) { /* end is in next line, thus no match in * this line */ match_ok = FALSE; break; } matchcol = endpos.col; /* for empty match: advance one char */ if (matchcol == matchpos.col && ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } } else { matchcol = matchpos.col; if (ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } }
ここではまずCPOオプションの'c'フラグが立っているかをみています。
cフラグがあると検索が終わった時にそのマッチした文字の最後から次に検索するようになります。
"abababababab" で "/abab" と検索した場合フラグがある場合3つ,ない場合5つマッチします。
if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { if (nmatched > 1) { /* end is in next line, thus no match in * this line */ match_ok = FALSE; break; } matchcol = endpos.col; /* for empty match: advance one char */ if (matchcol == matchpos.col && ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } }
ではまずフラグありの時です。
nmatchedが1以上の場合はmatchの最後が次の行なのでwhileループから抜け次の行のループに入ります。
nmatchedが1ならmatchの場所をそのマッチの最後の位置にします。
その際matchcolとmatchpos.colが同じ場合(empty match)の場合matchcolを一つ進めています。
これは"/^"のような検索の時がこのケースになります。
matchcolはもう一回検索する際のスタート位置としてこの後使われます。
... } else { matchcol = matchpos.col; if (ptr[matchcol] != NUL) { #ifdef FEAT_MBYTE if (has_mbyte) matchcol += (*mb_ptr2len)(ptr + matchcol); else #endif ++matchcol; } }
cオプション無しの場合です。ここは単にmatchcolにマッチした場所を入れるだけです。
ここでも前の場合と同じようにmatchcolを一つ進めて次の検索に備えます。
if (matchcol == 0 && (options & SEARCH_START)) break;
matchcolが0でここに来るのは空の行で"/^"の検索した時で
ptr[matchcol]がNULになります。
この時SEARCH_STARTのフラグが立っていればここに入ります。
これはカーソルの位置を検索に入れることになり。今カーソルがいる行の先頭にmatchがセットされます。
もしこのSEARCH_STARTのフラグがなければもう一回ループして次の行の先頭にカーソルが動きます。
if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, #ifdef FEAT_RELTIME tm #else NULL #endif )) == 0) { match_ok = FALSE; break; }
matchcolのところの文字がNULかもう一回regex検索をmatchcolの位置からして
見つからなかったなら次の行にループを進めます。
matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; # ifdef FEAT_EVAL submatch = first_submatch(®match); # endif /* Need to get the line pointer again, a * multi-line search may have made it invalid. */ ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); }
regex検索がマッチしたのでその情報をmatchpos, endposにセットしptrにそのマッチした
行を入れてループから出ます。
if (nmatched > 0) { matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; #ifdef FEAT_EVAL submatch = first_submatch(®match); #endif if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) ptr = (char_u *)""; else ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); if (dir == FORWARD && at_first_line) { match_ok = TRUE; ... if (!match_ok) continue; }
このmatch_okがループの最初にTRUEが入ってループを出る場合はここに
FALSEが入ります。
今日の関数 searchit その2 (https://github.com/vim/vim)
nmatched = vim_regexec_multi(®match, win, buf, lnum, col, #ifdef FEAT_RELTIME tm #else NULL #endif ); /* Abort searching on an error (e.g., out of stack). */ if (called_emsg) break; if (nmatched > 0) { /* match may actually be in another line when using \zs */ matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; #ifdef FEAT_EVAL submatch = first_submatch(®match); #endif /* "lnum" may be past end of buffer for "\n\zs". */ if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) ptr = (char_u *)""; else ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE); /* * Forward search in the first line: match should be after * the start position. If not, continue at the end of the * match (this is vi compatible) or on the next char. */ if (dir == FORWARD && at_first_line) { match_ok = TRUE; /* * When the match starts in a next line it's certainly * past the start position. * When match lands on a NUL the cursor will be put * one back afterwards, compare with that position, * otherwise "/$" will get stuck on end of line. */ while (matchpos.lnum == 0 && ((options & SEARCH_END) && first_match ? (nmatched == 1 && (int)endpos.col - 1 < (int)start_pos.col + extra_col) : ((int)matchpos.col - (ptr[matchpos.col] == NUL) < (int)start_pos.col + extra_col))) { ... }
colに検索を開始する位置を入れた後の部分を読んでいきましょう。
まずは正規表現で検索します。
nmatched = vim_regexec_multi(®match, win, buf, lnum, col, #ifdef FEAT_RELTIME tm #else NULL #endif );
vim_regexec_multiはlnumの行のcol番目の文字から検索してマッチした行数を返します。
if (nmatched > 0) { /* match may actually be in another line when using \zs */ matchpos = regmatch.startpos[0]; endpos = regmatch.endpos[0]; #ifdef FEAT_EVAL submatch = first_submatch(®match); #endif /* "lnum" may be past end of buffer for "\n\zs". */ if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) ptr = (char_u *)""; else ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
nmatchedが0以上なのでここは検索がヒットしたということです。
matchposにマッチした最初の位置、endposにマッチした最後の位置を入れます。
ここでマッチした行のテキストをptrに格納するのですがその時そのmatchの位置が バッファから飛び出していないかをチェックします。
例えば最後の行にabcというテキストがあったとします。
そこに/c\n\zsという( \zは前の検索がマッチした次にマッチさせるオプション )検索をすると
matchはcの後の改行の後になるので最後の行の次の行を指してしまうことになります。
その時は上のコードのように空文字をptrにセットします。
普通にマッチしているならそのマッチした行をptrに入れます。
if (dir == FORWARD && at_first_line) { match_ok = TRUE; /*
次に前方検索で一行目の時の条件がきます。
while (matchpos.lnum == 0 && ((options & SEARCH_END) && first_match ? (nmatched == 1 && (int)endpos.col - 1 < (int)start_pos.col + extra_col) : ((int)matchpos.col - (ptr[matchpos.col] == NUL) < (int)start_pos.col + extra_col))) {
このwhileがとても読みづらいのですがちょっと整理してみましょう。
while ( ① matchpos.lnum == 0 && ((options & SEARCH_END) && first_match ? ② (nmatched == 1 && (int)endpos.col - 1 < (int)start_pos.col + extra_col) : ③ ((int)matchpos.col - (ptr[matchpos.col] == NUL) < (int)start_pos.col + extra_col))) {
① マッチがカーソルのある行にあり
SEARCH_ENDのオプションがあり
一番最初のマッチである
② マッチしている行は一行だけであり
マッチしている最後の部分がカーソルの位置より左にある。
③ マッチしている最初の部分(もしその位置がNULならその位置から-1したところ)がカーソルの位置より左にある。
このように整理してみるとこれはSEARCH_ENDのあるなしの場合わけだとわかります。
前方検索なので、もしマッチした文字がカーソル位置の左にあってはならないのでその調節を行います。
SEARCH_ENDがついているならそのマッチの最後の位置、ついていないならマッチの最初の位置が カーソルの左にある間はwhileが回ることになります。
5分でmrubyをswiftから呼ぶ方法
まずはmrubyをios用にビルドします。 こちら を参考にbuild_config.rbを編集します。
build_config.rb
MRuby::CrossBuild.new('ios_sim') do |conf| toolchain :clang SDK_PATH_SIM = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator9.3.sdk" ARCH_SIM = "-arch x86_64" conf.cc.flags << "#{ARCH_SIM} -mios-simulator-version-min=9.0" conf.cc.include_paths << "#{SDK_PATH_SIM}/usr/include" conf.linker.flags << "#{ARCH_SIM}" conf.linker.library_paths << "#{SDK_PATH_SIM}/usr/lib" conf.bins = [] conf.gem :core => "mruby-print" conf.gem :core => "mruby-math" conf.gem :core => "mruby-enum-ext" end
これをbuild_config.rbの最後に追加してrakeします。
次にxcodeでFile → New して"Single View Application"を作ります。
Build Setteings → Search PathsのHeader Search Paths とLibrary Search Pathsに mrubyのincludeフォルダとbuild/ios_sim/libのフォルダをセットします。
Build Phases → Link Binary With Librariesに追加で(+ボタン)libmruby.aを追加します。
mrubyのC関数をswiftから呼べるようにするためにbridging-headerを作ります。
File → Newから.h ファイルを選んで"ProjectName-Bridging-Header.h"
という名前(ProjectNameはそれぞれのプロジェクト名が入ります)で作りましょう。
このヘッダーファイルをbridgeとして使うための設定をしなければなりません。
Build Settings → Swift Compiler -Code Generation の中のObjective-C Bridging Headerにこのファイルのパスを
入れましょう。おそらく"ProjectName/ProjectName-Bridging-Header.h"となります。
いよいよmrubyを呼び出すswiftコードを書きます。
ViewController.swift
override func viewDidLoad() { super.viewDidLoad() let rubycode = "puts 'mruby Hello, world!!'"; var mrb:UnsafeMutablePointer= nil mrb = mrb_open() mrb_load_string(mrb, rubycode) }
ViewController.swiftのviewDidLoadをこのように書いてみましょう。
Run すればシミュレーターが起動してoutputに"mruby Hello, world!!"が出力されるはずです。
今日の関数 searchit その1 (https://github.com/vim/vim)
search.c
int searchit( win_T *win, /* window to search in; can be NULL for a buffer without a window! */ buf_T *buf, pos_T *pos, int dir, char_u *pat, long count, int options, int pat_use, /* which pattern to use when "pat" is empty */ linenr_T stop_lnum, /* stop after this line number when != 0 */ proftime_T *tm UNUSED) /* timeout limit or NULL */ {
0以外が返ればなら検索成功となります。
search.c
... if (search_regcomp(pat, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { if ((options & SEARCH_MSG) && !rc_did_emsg) EMSG2(_("E383: Invalid search string: %s"), mr_pattern); return FAIL; }
検索パターンのエラーチェックをします。
search.c
do /* loop for count */ { ...
無限ループに入ります。
search.c
if (pos->col == MAXCOL) start_char_len = 0; #ifdef FEAT_MBYTE /* Watch out for the "col" being MAXCOL - 2, used in a closed fold. */ //------------------------------ // pos ga max - 2 ika nara //------------------------------ else if (has_mbyte && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count && pos->col < MAXCOL - 2) { //------------------------------ // ptr = buf + col //------------------------------ ptr = ml_get_buf(buf, pos->lnum, FALSE) + pos->col; if (*ptr == NUL) start_char_len = 1; else start_char_len = (*mb_ptr2len)(ptr); } #endif else start_char_len = 1;
今のカーソル位置でstart_char_lenを設定していきます。
カーソル位置がMAXCOLにないかmultibyteの時カーソル位置の文字がNULでないならstart_char_lenは1となります。
このstart_char_lenはextra_colに入れられSEARCH_START(カーソルの位置を検索に含める)オプションのために使われます。
search.c
if (dir == FORWARD) { if (options & SEARCH_START) extra_col = 0; else extra_col = start_char_len; } else { if (options & SEARCH_START) extra_col = start_char_len; else extra_col = 0; }
SEARCH_START(カーソルの位置を検索に含める)オプションがついているなら
検索の方向によってextra_colが0か1に設定されます。
前方検索ならSEARCH_STARTがあるならextra_colに1が入りカーソル位置の文字を飛ばします。
SEARCH_STARTがないならextra_colは0で検索はカーソル位置の文字から始まります。
search.c
start_pos = *pos; /* remember start pos for detecting no match */ found = 0; /* default: not found */ at_first_line = TRUE; /* default: start in first line */ if (pos->lnum == 0) /* correct lnum for when starting in line 0 */ { pos->lnum = 1; pos->col = 0; at_first_line = FALSE; /* not in first line now */ }
検索ループに入る前の変数の初期化です。
search.c
//------------------------------ // imano gyou no search // moshi back de col ga 0 de nainara //------------------------------ if (dir == BACKWARD && start_pos.col == 0 && (options & SEARCH_START) == 0) { lnum = pos->lnum - 1; at_first_line = FALSE; } else lnum = pos->lnum;
lnum(行数)は今のカーソル位置のlnumをとります。
ただ後方検索の時で今のカーソル位置が0でしかもカーソル位置の文字を含めるオプションがついていない時は
lnumをカーソル位置の行の一つ前の行を入れます。そしてat_first_lineにFALSEを入れています。
search.c
for (loop = 0; loop <= 1; ++loop) /* loop twice if 'wrapscan' set */ { for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count; lnum += dir, at_first_line = FALSE) // dir ha 1 ka -1 { ...
wrapscanは検索が最後まで行ったらまた最初からもう一度
検索するオプションです。そのために2回ループするようになっています。
その中にもう一つ行を舐めていくループがあります。
lnumが0以上でbufferの行数以下でループが回ります。
dirは前方で1後方検索で-1なのでどちらの方向でもこのループは機能します。
search.c
if (stop_lnum != 0 && (dir == FORWARD ? lnum > stop_lnum : lnum < stop_lnum)) break; #ifdef FEAT_RELTIME /* Stop after passing the "tm" time limit. */ if (tm != NULL && profile_passed_limit(tm)) break; #endif
stop_lnumとタイムリミットがセットされているならチェックします。
search.c
col = at_first_line && (options & SEARCH_COL) ? pos->col : (colnr_T)0;
colの設定をします。at_first_lineがTRUEでカーソル位置の文字を含めるオプションがあるなら
そのままカーソル位置のcolを入れます。
そうでなければ0です。一番最初の行にいる時だけカーソル位置を指定しています。
今日の関数 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をファイルの最初/最後にセットしています。