今回は、行内移動コマンドの 0 ^ $ と、次の行・前の行に移動する Enter + - を実装してみる。
0 は行頭に、^ は最初の非空白文字に、$ は行の最後の文字にジャンプするコマンドだ。数値を前置しても無視される。
Enter、+ は次の行の最初の非空白文字に、- は前の行の最初の非空白文字に移動する。 数値を前置して何行先・前に移動するかを指定することもできる。
移動コマンドなので、ViEngine::cmdModeKeyPressEvent() でコマンドを認識し、ViEditView::moveCursor() をコールするだけだ。 これまでは QTextCursor::MoveOperation で間に合っていたのだが、行の最初の非空白文字にジャンプする ^ コマンドは 対応するものが無いので、ViEditView::moveCursor() の第1引数を int に変更し、 enum ViMoveOperation を導入し、無いものはそこで宣言することにする。
1: namespace ViMoveOperation { 2: enum { 3: FirstNonBlankChar = 100, // ^ 4: LastChar, // $ 5: NextLine, // + Enter 6: PrevLine, // - 7: }; 8: }
1: bool ViEngine::cmdModeKeyPressEvent(QKeyEvent *event) 2: { 3: ..... 4: switch( ch ) { 5: case '0': 6: m_editor->moveCursor(QTextCursor::StartOfBlock); 7: break; 8: case '^': 9: m_editor->moveCursor(ViMoveOperation::FirstNonBlankChar); 10: break; 11: case '$': 12: m_editor->moveCursor(ViMoveOperation::LastChar); 13: break; 14: case '\r': // Enter 15: case '+': 16: m_editor->moveCursor(ViMoveOperation::NextLine); 17: break; 18: case '-': 19: m_editor->moveCursor(ViMoveOperation::PrevLine); 20: break; 21: ..... 22: } 23: ..... 24: }
1: void ViEditView::moveCursor(int mv, int n) 2: { 3: QTextCursor cur = textCursor(); 4: const int pos = cur.position(); 5: QTextBlock block = cur.block(); 6: const int blockPos = block.position(); 7: const QString blockText = block.text(); 8: bool moved = false; 9: switch( mv ) { 10: ..... 11: case ViMoveOperation::FirstNonBlankChar: 12: cur.setPosition(blockPos + firstNonBlankCharPos(blockText)); 13: moved = true; 14: break; 15: case ViMoveOperation::LastChar: { 16: int ix = blockText.length(); 17: if( ix != 0 ) --ix; 18: cur.setPosition(blockPos + ix); 19: moved = true; 20: break; 21: } 22: } 23: if( !moved ) 24: cur.movePosition(static_cast<QTextCursor::MoveOperation>(mv), QTextCursor::MoveAnchor, n); 25: setTextCursor(cur); 26: }
1: inline bool isTabOrSpace(const QChar ch) 2: { 3: return ch == '\t' || ch == ' '; 4: } 5: int firstNonBlankCharPos(const QString &text) 6: { 7: int ix = 0; 8: while( ix < text.length() && isTabOrSpace(text[ix]) ) 9: ++ix; 10: return ix; 11: }
1: void ViEditView::moveCursor(int mv, int n) 2: { 3: ..... 4: switch( mv ) { 5: ..... 6: case ViMoveOperation::NextLine: 7: cur.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, n); 8: moveToFirstNonBlankChar(cur); 9: moved = true; 10: break; 11: case ViMoveOperation::PrevLine: 12: cur.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor, n); 13: moveToFirstNonBlankChar(cur); 14: moved = true; 15: break; 16: } 17: ..... 18: }
1: void moveToFirstNonBlankChar(QTextCursor &cur) 2: { 3: QTextBlock block = cur.block(); 4: const int blockPos = block.position(); 5: const QString blockText = block.text(); 6: if( !blockText.isEmpty() ) 7: cur.setPosition(blockPos + firstNonBlankCharPos(blockText)); 8: }