前へ 次へ
技術文章qvi

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

Tweet


前へ 次へ
技術文章qvi