.(dot) は直前に実行した編集コマンドを再実行するコマンドだ。カーソル移動コマンドは再実行の対象にはならない。
繰り返し回数を前置することもできる。例えば dd で行削除した後、カーソル移動し別の行で 3. を実行すると、そこから3行を削除する。
最も好きな vi コマンドは何か?というアンケートをとったら、.(dot) コマンドが一位になるに違いない。 と筆者は確信している。だって、/? 検索(ViVi ならば * でカーソル位置単語検索も)し、cw新しい文字列 Esc した後、 n . n . するのは脳内麻薬が分泌されるくらい快楽的だもん。
というわけで、本稿では vi 使いの快楽である .(dot) コマンドを実装してみる。
.(dot) 処理のために、以下のメンバ変数を追加する。要はメンバ変数にコマンドを再実行するための情報を蓄えておくわけだ。 bool 変数は false に、int 変数は 0 にコンストラクタで初期化しておく。
1: class ViEngine : public QObject 2: { 3: ..... 4: private: 5: bool m_redoRecording; // .(dot) のために挿入文字列記録中 6: bool m_redoing; // .(dot) 実行中フラグ 7: int m_redoRepeatCount; 8: QString m_redoCmd; // .(dot) で再実行するコマンド 9: QString m_insertedText; // 挿入されたテキスト 10: };
次に、編集コマンドを再実行するためのメソッドを導入する。ViEngine::doViCommand(const QChar &qch) はこれまで ViEngine::cmdModeKeyPressEvent() の中身だったのだが、再実行時にコールできるようメソッドに分割した。
1: void ViEngine::doViCommand(const QString &text) 2: { 3: for(int ix = 0; ix < text.length(); ++ix) 4: doViCommand(text[ix]); 5: } 6: bool ViEngine::doViCommand(const QChar &qch) 7: { 8: ushort ch = qch.unicode(); 9: if( ch == '0' && m_repeatCount != 0 || ch >= '1' && ch <= '9' ) { 10: m_repeatCount = m_repeatCount * 10 + (ch - '0'); 11: return true; 12: } 13: ..... 14: switch( ch ) { 15: ..... 16: } 17: ..... 18: } 19: bool ViEngine::cmdModeKeyPressEvent(QKeyEvent *event) 20: { 21: Qt::KeyboardModifiers mod = event->modifiers (); 22: if( (mod & Qt::ControlModifier) != 0 ) // Ctrl + キーは通常処理 23: return false; 24: QString text = event->text(); 25: if( text.isEmpty() ) return false; 26: return doViCommand(text[0]); 27: }
以上で準備が出来たので、あとは処理コマンドの記録と、.(ピリオド)が押された時に編集コマンドが記録されていれを実行するだけだ。
ソースは下記の様になる。.(dot) 対象は削除・挿入などの編集コマンドだけでカーソル移動コマンドは対象外である。
1: bool ViEngine::doViCommand(const QChar &qch) 2: { 3: ..... 4: switch( ch ) { 5: case '.': 6: if( !m_redoCmd.isEmpty() ) { 7: m_redoing = true; 8: doViCommand(m_redoCmd); 9: m_redoing = false; 10: } 11: break; 12: ..... 13: } 14: if( (delFrom >= 0 && delFrom < delTo || toInsertMode) && !m_redoing ) { 15: m_redoCmd = qch; 16: m_redoRepeatCount = m_repeatCount; 17: } 18: ..... 19: }
x 実行後 3. を実行すると3文字削除される。2x で2文字削除 . を実行する後2文字削除されるが、3. を実行すると3文字削除される。 これを実現するために、繰り返し回数(m_redoRepeatCount)とコマンド文字列(m_redoCmd)は別々に記録しておき、 繰り返し回数が前置されていない場合にのみ参照する。
1: int ViEngine::repeatCount() const 2: { 3: if( !m_repeatCount ) { 4: if( m_redoing && m_redoRepeatCount != 0 ) 5: return m_redoRepeatCount; 6: return 1; 7: } 8: return m_repeatCount; 9: }
次に、挿入モードで挿入されたテキストも保存しておき、redo 処理時に参照する。
1: bool ViEngine::doViCommand(const QChar &qch) 2: { 3: ..... 4: if( toInsertMode ) { 5: if( !m_redoing ) { 6: m_redoRecording = true; 7: m_insertedText.clear(); 8: setMode(INSERT); 9: } else { 10: if( !m_insertedText.isEmpty() ) { 11: for(int c = repeatCount(); c > 0; --c) 12: cur.insertText(m_insertedText); 13: } 14: moveCursor(cur, ViMoveOperation::Left); 15: m_editor->setTextCursor(cur); 16: } 17: } 18: m_repeatCount = 0; 19: return rc; 20: }
1: bool ViEngine::insModeKeyPressEvent(QKeyEvent *event) 2: { 3: if( event->key() == Qt::Key_Escape ) { 4: ..... 5: m_redoRecording = false; 6: return true; 7: } 8: if( m_redoRecording ) 9: m_insertedText += event->text(); 10: return false; 11: }
以上で、一番人気の .(dot) コマンドがとりあえず動作するようになった。
細かな問題がいろいろあるけど、長くなったので次稿に続く