何の脈絡もないが、今回は文字を削除する x, X コマンドと undo/redo を行う u, U コマンドを実装してみる。
x はカーソル位置の文字を、X はカーソル直前文字を削除する。3x の様に削除する文字数を指定することもできる。
オリジナル vi では undo は直前のコマンドを取り消すのみで、再度 u を実行すると再実行(redo)となる (更に u を実行すると undo となる。以下繰り返し)。 が、今時そんな undo では vi 原理主義者の人でも許してくれそうにないので、U を再実行(redo)コマンドに割り当てることにする。
x, X, u, U コマンドを認識するには、ViEngine::cmdModeKeyPressEvent() の switch 文に case 文を追加するだけだ。 実際の処理は ViEditView の各処理メソッドに任せる。
ソースは下記の様になる。
1: bool ViEngine::cmdModeKeyPressEvent(QKeyEvent *event) 2: { 3: ..... 4: switch( ch ) { 5: case 'x': 6: m_editor->doDelete(repeatCount()); 7: break; 5: case 'X': 6: m_editor->doDelete(-repeatCount()); 7: break; 8: case 'u': 9: m_editor->doUndo(repeatCount()); 10: break; 11: case 'U': 12: m_editor->doRedo(repeatCount()); 13: break; 14: ..... 15: } 16: ..... 17: }
まずは削除処理メソッド。
基本的には textcursor() でカーソルを取得し、カーソル位置に対する削除メソッドを呼んであげればよい。
テキストが選択されている場合は選択文字列を削除、そうでない場合は指定文字数だけ削除を行う。
ただし、x, X コマンドでは改行は削除されないので、n が大きくても改行を超えて削除しないように処理を行う。
1: void ViEditView::doDelete(int n) 2: { 3: QTextCursor cur = textCursor(); 4: if( !cur.hasSelection() ) { 1: const int pos = cur.position(); 2: int dst; 3: if( n > 0 ) { 4: cur.movePosition(QTextCursor::EndOfBlock); 5: const int endpos = cur.position(); 6: if( pos == endpos ) return; // 改行は x では削除されない 7: dst = qMin(pos + n, endpos); // n 文字移動 or 改行位置 8: cur.setPosition(pos); // 現カーソル位置へ移動 9: } else { 10: const int blockPos = cur.block().position(); 11: if( pos == blockPos ) return; // 行頭より前は X では削除されない 12: dst = qMax(pos + n, blockPos); // n 文字移動 or 改行位置 13: } 14: cur.setPosition(dst, QTextCursor::KeepAnchor); // カーソル位置からn文字 or 改行直前まで選択 15: } 16: cur.deleteChar(); 17: }
undo/redo の処理は簡単だ。QPlainTextEdit が既に undo/redo 機能を持っているのでそれを繰り返し回数だけ呼べばよい。
1: void ViEditView::doUndo(int n) 2: { 3: for(int i = 0; i < n; ++i) 4: undo(); 5: } 6: void ViEditView::doRedo(int n) 7: { 8: for(int i = 0; i < n; ++i) 9: redo(); 10: }