本稿から検索コマンドを実装する。まずは 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: }