背景
- 以下のソースコードで“app”を“xyzzz”に置換する場合を考える
1: int main(int argc, char *argv[]) 2: { 3: QApplication app(argc, argv); 4: app.setOrganizationName("N.Tsuda"); 5: app.setApplicationName("nxiiProject"); 6: MainWindow w; 7: w.show(); 8: return app.exec(); 9: }
- :3,8s/app/xyzzz のように置換コマンド、または置換ダイアログにより置換してもよいのだが、
通常編集で確認しながら置換したい場合もある。
一箇所を書き換えた後で、他のも書き換えたくなる場合もある。 - vi コマンドで、/app<Enter> cwxyzzz<Esc> n . n . は脳内麻薬が分泌される程快楽的である。
- が、以下の欠点がある。
- vi コマンドを知らない人には使えない
- 最後に実行したコマンドしか覚えてないので、それ以前に行った処理を再実行することが出来ない
- 本稿では上記の欠点を克服するために、「字句(単語)単位編集操作の予測/例示インタフェース」を提案する。
提案UI
- vi コマンドは、コマンド{+文字列+Esc} が1セットとなっているので、その部分を記録・再実行すればよい
- vi 以外のコマンドモードを持たないエディタでは、ユーザ操作のどこからどこまでをひとつの編集操作とみなすかが問題となる
- 本UIでは、編集位置の字句(単語)に注目し、編集前後の字句(単語)文字列を認識・記録しておき、同じ編集が可能な場合はそれを例示する。
例えば、先の例では "app" を消してから "xyzzz" 入力しても、先に "xyzzz" を入力してから "app" を消しても、 "app" を選択してから "xyzzz" を上書きした場合などでも、編集順序の違いは無視し、カーソルが着目している単語上にある場合は同一単語を置換中と認識するものとする。 - 同じ字句(単語)単位編集を行う場合は、その位置に移動する必要があるので、検索も例示する
- 例示ポップアップを自動的に表示するか、それともユーザのアクションにより受動的に表示するかは微妙な問題である。
前者の場合は例示機能を利用する場合には余分な操作が不要で便利だが、例示された置換を実行しない場合にはポップアップを消す操作が必要となる。 後者の場合は、例示機能を利用するたびに1アクション必要なのと、ユーザが例示機能があることを知っていないと使うことが出来ないという問題がある。 - 機能が邪魔になってはユーザは使用しないので、
例示機能が使用可能な場合はカーソル移動後0.5秒〜5秒の間のみ以下の tip ウィンドウを表示することとする。
ただし、tip ウィンドウが頻繁に出るのは邪魔なので、同じ編集を2回以上行った場合のみ表示することとした。 - 提案UIの構成を図示すると下図の様になる。
実装
- 最初は Qt によりプロトタイプを実装した。
- 編集操作を字句(単語)単位として認識するために、void QTextDocument::contentsChange ( int position, int charsRemoved, int charsAdded ) シグナルと、
編集前後のカーソル位置とその部分の単語文字列情報を利用した。
半日ほとコーディングの神が降りてくるのを待っていたが、降りてくる気配がなかったので単純にif文を書き連ねた。 - 条件分岐は、削除の場合:7個、挿入の場合:8個、上書きの場合:6個となった。
- 単語の先頭にカーソルがあり、1文字削除した場合:
(pos, "hoge") (pos, 1, 0) (pos, "oge") → "hoge" を "oge" と編集したとみなし、記録する。 - 同一位置への編集は一連の操作とみなし、記録をマージする:
"hoge" を "oge" に編集、"oge" を "ge" に編集 → "hoge" を "ge" に編集したとのみ記録する。 - 編集位置が変わった場合は、記録を deque<pair<string, string>> に移す。
- Tipウィンドウは、カーソル移動後から指定時間(デフォルトでは 0.5秒)後にポップアップし、 指定時間(デフォルトでは5秒間)経つと自動でクローズすることにした。
- Esc でもTipウィンドウを消去可能にし、その場合は同一置換元単語でのTipウィンドウポップアップを抑止するようにした。
- ポップアップメニューにより置換と検索を例示し、選択可能とした。
評価
- 類似っぽいUIとして、置換ダイアログ、キーボードマクロ、ダイナミックマクロ、vi . コマンドが挙げられる。
- 比較項目としては、学習コスト、操作の簡便性、事前操作の必要性、有効頻度が考えられる。
- キーボードマクロ、ダイナミックマクロ、vi . コマンドは入力されたコマンドをそのまま記録し再実行するが、 本UIは編集手順をそのまま記録するのではなく、編集前後の字句(単語)文字列を記録する点が特徴的である。
- 以下のような場合に効果的
- 単語を誤編集した後、それに気づき修正した場合。
1: class QLineEdit; 2: ..... 3: QLineEdit *m_cmdLineEdit; 4: ..... 5: m_cmdLineEdit = new QLineEdit;
上記テキストで、“QLineEdit”を“CmdLineEdit”に変更するとき、1行目で誤って“CndLineEdit”に修正し、 直後に誤りに気づき“CmdLineEdit”に修正した場合、vi . コマンドでは“QLineEdit”を“CmdLineEdit”に置換することはできないが、 本方式であれば例示UIにより置換可能。
- 類似のメンバ変数を追加し、類似の処理を記述する場合
1: QAction *newAct; 2: QAction *openAct; // ← 追加 3: ..... 4: newAct = new QAction(tr("&New"), this); 5: newAct->setShortcuts(QKeySequence::New); 6: newAct->setStatusTip(tr("Create a new document")); 7: connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); 8: // 追加 ここから 9: openAct = new QAction(tr("&Open"), this); 10: openAct->setShortcuts(QKeySequence::Open); 11: openAct->setStatusTip(tr("Open an existing file")); 12: connect(openAct, SIGNAL(triggered()), this, SLOT(openFile())); 13: // 追加 ここまで
2行目を追加するとき、1行目をコピペし、“newAct”を“openAct”に修正する。
その後、4〜7行目をコピペし、本UIにより順次“newAct”を“openAct”に置換することができる。
9, 10行目はペースト後に“New”を“Open”に置換するが、これも本UIで置換可能である。
このように複数の置換パターンを並行して処理することができるのも本UIの特徴のひとつである。 - ひとつの単語に2回以上viコマンドを実行した場合。
1: ViVi version 3.07.023 2: 10-Jan-2011 3: ..... 4: 11/01/10 version 3.07.023 Dev FIX: SPR#0069, SPR#0082, SPR#0083
2行目の“10”の部分で ##(インクリメントコマンド)を4回実行し、“14”に変えた場合、 4行目の“10”の部分で . を実行しただけでは“14”に置換することはできない(4. とする必要がある)。
しかし、本UIは同一単語への編集はひとつとして認識されるので“10”→“14”が置換候補として例示される。 - 単語の途中を置換した場合
void cmdLine_returnPressed() ..... connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(cmdLine_returnPressed())); ..... void MainWindow::cmdLine_returnPressed() { ..... }
- 単語を誤編集した後、それに気づき修正した場合。
- 本機能は、vi コマンドや置換ダイアログによる置換機能を知らない・使いこなせない、どちらかというと初心者的なエディタユーザに対して効果的。
- 筆者が実際に本機能の恩恵を受けるのは1日に数回であるが、別に邪魔にはならないので、頻度は少なくても利用可能であればそれでよいと考えている。
今後の課題
- 使い勝手・精度の向上
- 不要そうな記録の抑止
- 英数字列、漢字、カタカナだけに絞る
- tipウィンドウを表示する条件
- 字句に注目するだけでなく構文にも注目
参考文献
- 増井 俊之 予測/例示インタフェースの研究動向