これまでは、vi コマンドに対応したカーソル移動メソッドを ViEditView が持っていた。
最初は、vi コマンドに依存する処理が、カーソル移動メソッドに含まれていなかったからだ。
しかし今や、h は前の行には移動せず、l は改行には移動しない等、vi コマンドの仕様に依存する処理を含んでしまっている。
特に大規模なシステムでは、依存性除去を行うことが開発効率に後々大きく影響してくる。
なので、vi コマンドに依存するカーソル移動処理は ViEditView から ViEngine の方に移動することにする。
カーソルオブジェクトはビューが保有するものであり、ViEngine が保有するものではないので、 カーソル移動処理メソッドを ViEngine が保持するのは筋違いである。 よって、moveCursor(QTextCursor&, int moneOperation, int n) : bool という関数を定義し、これを使ってカーソル移動を行うことにする。
1: m_editor->moveCursor(mv, repeatCount());
これまで上記のように記述していた部分を下記のように変更する。
1: QTextCursor cur = m_editor->textCursor(); 2: moveCursor(cur, mv, repeatCount()); 3: m_editor->setTextCursor(cur);
行数の少なさが正義だったはずなのに、行数が増えているじゃないか。しかも何もメリットが無い、と思うかもしれない。
しかし、カーソル移動処理と、移動後のカーソルをビューに反映させる処理を分けたことで、
その間に必要なら別の処理を挟むことができるようになった。
このメリットは、後で cw などの編集+移動コマンド処理を行うときに効いてくるのだ。
1: bool moveCursor(QTextCursor &cur, int mv, int n)
2: {
3: const int pos = cur.position();
4: QTextBlock block = cur.block();
5: const int blockPos = block.position();
6: const QString blockText = block.text();
7: switch( mv ) {
8: case ViMoveOperation::Up:
9: mv = QTextCursor::Up;
10: break;
11: case ViMoveOperation::Down:
12: mv = QTextCursor::Down;
13: break;
14: case ViMoveOperation::Left:
15: n = qMin(n, pos - blockPos);
16: mv = QTextCursor::Left;
17: break;
18: case ViMoveOperation::Right: {
19: if( blockText.isEmpty() ) return false; // 改行 ora EOF オンリー行の場合
20: const int endpos = blockPos + blockText.length() - 1;
21: if( pos >= endpos ) return false;
22: n = qMin(n, endpos - pos);
23: mv = QTextCursor::Right;
24: break;
25: }
26: .....
27: }
28: cur.movePosition(static_cast(mv), QTextCursor::MoveAnchor, n);
29: return true;
30: }
1: bool ViEngine::cmdModeKeyPressEvent(QKeyEvent *event)
2: {
3: .....
4: QTextCursor cur = m_editor->textCursor();
5: bool cursorMoved = false;
6: bool toInsertMode = false;
7: switch( ch ) {
8: case '0':
9: cursorMoved = moveCursor(cur, QTextCursor::StartOfBlock);
10: break;
11: case '^':
12: cursorMoved = moveCursor(cur, ViMoveOperation::FirstNonBlankChar);
13: break;
14: case '$':
15: cursorMoved = moveCursor(cur, ViMoveOperation::LastChar);
16: break;
17: case 'i':
18: toInsertMode = true;
19: break;
20: case 'A':
21: cursorMoved = moveCursor(cur, QTextCursor::EndOfBlock);
22: toInsertMode = true;
23: break;
24: .....
25: }
26: if( cursorMoved )
27: m_editor->setTextCursor(cur);
28: if( toInsertMode )
29: setMode(INSERT);
30: .....
31: }
x X コマンド処理も、ViEditView::doDelete(int n) が vi の仕様に依存していて好ましくないので、 このメソッドは廃止し、代わりに引数の範囲の文字を削除する doDelete(int from, int to) というメソッドを導入する。 これなら汎用的で vi の仕様にはいっさい依存してないぞ。
1: void ViEditView::doDelete(int from, int to)
2: {
3: QTextCursor cur = textCursor();
4: cur.setPosition(from);
5: cur.setPosition(to, QTextCursor::KeepAnchor);
6: cur.deleteChar();
7: }
1: bool ViEngine::cmdModeKeyPressEvent(QKeyEvent *event)
2: {
3: .....
4: int delFrom = -1;
5: int delTo = -1;
6: switch( ch ) {
7: .....
8: case 'x':
9: delFrom = cur.position();
10: moveCursor(cur, ViMoveOperation::RightForA, repeatCount());
11: delTo = cur.position();
12: break;
13: case 'X':
14: delTo = cur.position();
15: moveCursor(cur, ViMoveOperation::Left, repeatCount());
16: delFrom = cur.position();
17: break;
18: .....
19: }
20: if( delFrom >= 0 && delFrom < delTo )
21: m_editor->doDelete(delFrom, delTo);
22: .....
23: }