前へ 次へ
技術文章qvi

ずっと前に h l コマンドを実装したが、行頭や行末でストップせず、前・次の行に移動してしまうという、 vi を心から愛する人には到底認めてもらえない動作だった。 そこで本稿では、h l コマンドの動作がオリジナル vi と互換になるように修正する。 (※ Vim 互換にする気はもうとう無いので、くれぐれも誤解の無きよう)
ついでに、挿入モードで Esc を押した場合もカーソルがひとつ左に移動するように修正する。

筆者にはこの vi の動作が合理的とは考えられないし、好きではない。余分にコードを記述しなくてはいけないし、 なにか意味があるとも思えない。オリジナル vi が何故このような不合理な仕様になったのかまったくもって謎だ。
ちなみに、1996年に ViVi に vi コマンドを実装した当初は上記の動作は敢えて実装していなかったのだが、 多勢に無勢の vi 愛好家の皆様の要望に負けて ViVi の動作を vi 互換に変えた経緯がある。

vi 互換 h l コマンド

左右カーソル移動だった場合は、改行を超えて移動しないよう、移動量 n を調整する。
左移動の場合は、ブロック先頭からのオフセットを算出し、それと n とを比較し、小さい方を n に設定する。
右移動の場合は、改行位置までのオフセットを算出し、それと n とを比較し、小さい方を n に設定する。

コードは下記のようにすればおk。

 1: void ViEditView::moveCursor(QTextCursor::MoveOperation mv, int n)
 2: {
 3:     .....
 4:     QTextCursor cur = textCursor();
 5:     const int pos = cur.position();
 6:     QTextBlock block = cur.block();
 7:     const int blockPos = block.position();
 8:     switch( mv ) {
 9:     case QTextCursor::Left: {
10:         n = qMin(n, pos - blockPos);
11:         break;
12:     }
13:     case QTextCursor::Right: {
14:         const QString text = block.text();
15:         if( text.isEmpty() ) return;        //  改行 or EOF オンリー行の場合
16:         const int endpos = blockPos + text.length() - 1;
17:         if( pos >= endpos ) return;
18:         n = qMin(n, endpos - pos);
19:         break;
20:     }
21:     }
22:     cur.movePosition(mv, QTextCursor::MoveAnchor, n);
23:     setTextCursor(cur);
24: }

テキストブロックのテキストは text() で取得できるが、これは改行文字を含んでいない。 なので、block.text().length() で改行位置を取得できる。

Esc でカーソル左移動

挿入モードで Esc を押してコマンドモードに遷移する時にカーソルを1文字左に移動するようにする。
コードは下記の様に修正するだけだ。 前節で h コマンドで前行に移動しないようにしたので、行頭で Esc を押しても前行に移動したりしないぞ。

 1: bool ViEngine::insModeKeyPressEvent(QKeyEvent *event)
 2: {
 3:     if( event->key() == Qt::Key_Escape ) {
 4:         if( mode() == INSERT )
 5:             m_editor->moveCursor(QTextCursor::Left);
 6:         setMode(CMD);       //  Esc が押されたらコマンドモードへ
 7:         return true;
 8:     }
 9:     return false;
10: }

Tweet


前へ 次へ
技術文章qvi