Bash(readline)のviモード

自分はBashをviモードで使用しています。

set editing-mode vi
set show-mode-in-prompt on
set vi-cmd-mode-string "\1\e[2 q\2"
set vi-ins-mode-string "\1\e[6 q\2"
 
$if mode=vi
    set keymap vi-insert
        Control-l: clear-screen
$endif

テキストオブジェクトなど使用できない機能はたくさんありますがccでの行削除やjkでの履歴移動はとても便利です。

カーソルの形状で自分がどのモードにいるかも判別できます。

demo_vi-mode.gif

fzf

jkでの履歴移動は便利ですが履歴を検索したいこともあります。

readlinereverse-search-historyで履歴検索をすることも可能ですがリッチなUIがほしいのと、あいまい検索ができるためfzfを使用しています。

.bashrcの中でfzfのセットアップを行っています。

type fzf >/dev/null 2>&1 && eval "$(fzf --bash)"

fzf --bashを読み込むことでいくつかのキーバインドと関数が追加されます。

意図しないタイミングで__fzf_cd__が起動してしまう現象が発生

echo Hello Worldと入力後にCtrl-[でノーマルモードへ移動、ccで行を削除を行うつもりが__fzf_cd__が起動してしまいます。

cccを入力した時点で__fzf_cd__が起動し検索欄にcが入力されます。

normal_fzf-cd.gif

上のgifを生成したvhs

Output demo.gif
 
Require echo
Require fzf
 
Set Shell "bash"
Set FontSize 32
Set Width 800
Set Height 600
Set TypingSpeed 50ms
 
Hide
Type `eval "$(fzf --bash)"`
Enter
Show
 
Type "echo Hello World"
 
Sleep 1s
 
Ctrl+[
Sleep 100ms
Type "cc"
 
Sleep 3s

Ctrl+[の直後にあるSleep 100ms500ms以上にすると想定した動作になります。

normal_cc.gif

原因

fzf --bashを読み込んで追加されるキーバインドの一つにAlt-c__fzf_cd__を呼び出すものがあります。

bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'

bind -XSでbashにどのようなキーバインドが追加されているかを調べることができます。

Alt-cESC-cと同じキーコードを送信するものであるため、それと同様のキーコードを送信するCtrl-[cに反応していたようです。

odで実際にAlt-cCtrl-[cが同じであることが確かめることもできます。

od -Ax -tx1a
000000  1b  63  1b  63
       esc   c esc   c

解決方法

fzfのセットアップを行った後に\ecのキーバインドを削除しています。

そもそもfzfでディレクトリを移動したいことがないためあまり不便には感じていませんが今後別の方法を取ることもあり得ます。

type fzf >/dev/null 2>&1 && eval "$(fzf --bash)" && bind -r '\ec'

別の解決方法としてreadlineの設定でkeyseq-timeoutを小さな値にする方法があります。

デフォルトで500ms待つためもう少し短くしても良いかもしれません。

参考

端末アプリで Ctrl-[ が Esc になる理由

vi キーバインドで、なぜ J が下で K が上なのか?