何の脈絡もないが、今回は文字を削除する 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: }