世界情勢には惑わされず黙々とコマンドを実装して行こう。今回は対応する括弧に移動する % コマンドだ。
カーソルが括弧類:( ) [ ] { } にある場合、% を押すと、対応する括弧にカーソル移動する。
カーソルが括弧類上に無い場合は、行末方向に括弧類を検索し、最初に見つけた括弧に対応する括弧にカーソル移動する。
ViVi ( Vim も?)では文字列中の括弧やブロック・行コメント中の括弧は無視されるが、vi, nvi ではそれらは無視されない。
数値を前置しても無視される。
移動コマンドを実装するには、enum にそのためのシンボルを追加し、moveCursor() でそれをサポートし、 コマンド解析部分で moveCursor() をそのシンボルでコールしてやればよい。
1: namespace ViMoveOperation {
2: enum {
3: Left = 100, // h
4: .....
5: MatchParen, // %
6: };
7: }
1: bool ViEngine::doViCommand(const QChar &qch)
2: {
3: .....
4: switch( ch ) {
5: .....
6: case '%':
7: cursorMoved = moveCursor(cur, ViMoveOperation::MatchParen);
8: break;
9: }
10: .....
11: }
下記が moveCursor() の実装部分。実際に対応する括弧に移動する処理は行数が多いので別の関数にした。
1: bool moveCursor(QTextCursor &cur, int mv, int n)
2: {
3: .....
4: switch( mv ) {
5: .....
6: case ViMoveOperation::MatchParen:
7: return gotoMatchParen(cur);
8: }
9: .....
10: }
下記が対応する括弧を探す関数の実装。QTextCursor::block() でカーソル位置のテキストブロックをゲットし、
QTextBlock::text() でテキストブロックの文字列を取り出し、[] 演算子で参照している。
「 hoge( fuga() ) 」 の様に括弧がネストしている場合に対応するために、count をネストカウンターとして使用している。
現在の版では 「 hoge( ")" ) 」の様に文字リテラルがある場合や、行・ブロックコメントには対応していない。
1: bool gotoMatchParen(QTextCursor &cur)
2: {
3: QTextBlock block = cur.block();
4: int blockPos = block.position();
5: QString blockText = block.text();
6: int ix = cur.position() - blockPos;
7: bool forward = true;
8: QChar paren, dst;
9: while( ix < blockText.length() ) {
10: if( blockText[ix] == '{' ) { paren = '{'; dst = '}'; break; }
11: if( blockText[ix] == '(' ) { paren = '('; dst = ')'; break; }
12: if( blockText[ix] == '[' ) { paren = '['; dst = ']'; break; }
13: if( blockText[ix] == '}' ) { paren = '}'; dst = '{'; forward = false; break; }
14: if( blockText[ix] == ')' ) { paren = ')'; dst = '('; forward = false; break; }
15: if( blockText[ix] == ']' ) { paren = ']'; dst = '['; forward = false; break; }
16: ++ix;
17: }
18: if( paren == QChar() ) return false;
19: int count = 1;
20: if( forward ) {
21: for(;;) {
22: while( ++ix < blockText.length() ) {
23: if( blockText[ix] == paren )
24: ++count;
25: else if( blockText[ix] == dst && !--count ) {
26: cur.setPosition(blockPos + ix);
27: return true;
28: }
29: }
30: block = block.next();
31: if( !block.isValid() )
32: break;
33: blockPos = block.position();
34: blockText = block.text();
35: ix = -1;
36: }
37: } else {
38: for(;;) {
39: while( --ix >= 0 ) {
40: if( blockText[ix] == paren )
41: ++count;
42: else if( blockText[ix] == dst && !--count ) {
43: cur.setPosition(blockPos + ix);
44: return true;
45: }
46: }
47: block = block.previous();
48: if( !block.isValid() )
49: break;
50: blockPos = block.position();
51: blockText = block.text();
52: ix = blockText.length();
53: }
54: }
55: return false;
56: }
上記関数の処理時間は、Nを文書の文字数とすれば O(N)。 巨大な文書の最初と最後にバランスした括弧があると、相方を発見するには文書全体をスキャンしなくてはいけないので、 かなりの処理時間を要する心配がある。