本稿から検索コマンドを実装する。まずは vi 上級者御用達の f F t T ; , コマンド。
f に続けて1文字を打鍵すると、行内でその1文字を検索する。f、t は行末方向に、F、T は行頭方向に検索。 f、F はマッチした文字位置にカーソルを移動する。t T はマッチする文字の(検索方向に向かって)ひとつ手前にカーソルを移動する。
; は再検索を行う。, は逆方向に再検索を行う。行き過ぎてしまった場合に便利だ。
f F t T ; , いずれも繰り返し回数を前置することができる。
おいら的には、サービスラインあたりのロブをミスすることなく8割以上決めれるのが上級テニスプレイヤー、 行内カーソル移動で無意識に f F t T ; , コマンドを使えるのが上級vi使い、と考えている。 ちなみに筆者はどちらにも該当しない。
まずは必要なメンバ変数を追加。
1: class ViEngine : public QObject
2: {
3: .....
4: private:
5: bool m_noRepeatCount; // 繰り返し回数指定不可(for fFtT等)
6: ushort m_lastFTCmd; // 'f' or 'F' or't' or 'T'
7: QChar m_lastFTChar; // fFtT 検索文字
8: .....
9: };
fFtT の後に数字を入力すると、それは繰り返し回数ではなく、その数字の文字を検索しなくてはいけない。
ViEngine::doViCommand(const QChar &qch) の初めの方で数字の場合は繰り返し回数として処理しているので、
m_noRepeatCount というフラグを用意し、これが true の場合は繰り返し回数処理を行わないようにする(4〜10行目)。
; , は同方向・逆方向へ再検索なので、19、20行目で再検索のための情報を覚えておき、21行目で実際に検索処理を行う。 moveCursorFindInLine() はそのための関数。実装は後で述べる。
f F t T が押された場合は、繰り返し回数処理を行わないよう m_noRepeatCount フラグを立て、 m_cmdPrefix をセットするだけ(26〜36行目)。
; , が押され、過去に f F t T 検索が行われた場合(m_lastFTChar が空でない)は、 moveCursorFindInLine() をコールして行内検索を行う。 ,(カンマ)の場合は逆方向検索なので、'f'←→'F'、't'←→'T' 反転を行う(37〜46行目)。
1: bool ViEngine::doViCommand(const QChar &qch)
2: {
3: .....
4: if( !m_noRepeatCount &&
5: (ch == '0' && m_repeatCount != 0 || ch >= '1' && ch <= '9') )
6: {
7: m_repeatCount = m_repeatCount * 10 + (ch - '0');
8: showMessage(m_cmdString);
9: return true;
10: }
11: .....
12: if( m_cmdPrefix != 0 ) {
13: switch( m_cmdPrefix ) {
14: .....
15: case 'f':
16: case 'F':
17: case 't':
18: case 'T':
19: m_lastFTCmd = m_cmdPrefix; // マッチするかどうかに関係なく記録
20: m_lastFTChar = qch;
21: cursorMoved = moveCursorFindInLine(cur, m_cmdPrefix, qch, repeatCount());
22: break;
23: }
24: } else {
25: switch( ch ) {
26: case 'f':
27: case 'F':
28: case 't':
29: case 'T':
30: case ']':
31: case '[':
32: m_noRepeatCount = true;
33: case 'd':
34: m_cmdPrefix = ch;
35: showMessage(m_cmdString);
36: return true;
37: case ';':
38: if( m_lastFTChar != QChar() )
39: cursorMoved = moveCursorFindInLine(cur, m_lastFTCmd, m_lastFTChar, repeatCount());
40: break;
41: case ',':
42: if( m_lastFTChar != QChar() ) {
43: const ushort cmd = m_lastFTCmd ^ 'F' ^ 'f'; // f,F t,T 反転
44: cursorMoved = moveCursorFindInLine(cur, cmd, m_lastFTChar, repeatCount());
45: }
46: break;
47: .....
48: }
49: }
50: .....
51: }
実際に行内検索を行う関数の実装は以下のとおり。
1: bool moveCursorFindInLine(QTextCursor &cur, ushort cmd, const QChar &qch, int n)
2: {
3: QTextBlock block = cur.block();
4: QString text = block.text();
5: int ix = cur.position() - block.position();
6: while( --n >= 0 ) {
7: for(;;) {
8: if( cmd == 'f' || cmd == 't' ) {
9: if( ix == text.length() )
10: return false; // n番目の文字が発見出来なかった場合はカーソル移動しない
11: ++ix;
12: } else {
13: if( !ix )
14: return false; // n番目の文字が発見出来なかった場合はカーソル移動しない
15: --ix;
16: }
17: if( text[ix] == qch )
18: break;
19: }
20: }
21: if( cmd == 't' )
22: --ix;
23: else if( cmd == 'T' )
24: ++ix;
25: cur.setPosition(block.position() + ix);
26: return true;
27: }