percolを書き換えていい感じにzshで履歴検索できるようにする

スポンサード リンク
LINEで送る
Pocket

percolはインタラクティブに標準入力を行単位でしぼりこむためのpythonスクリプトである。百聞は一見にしかずというし,画像を見て欲しい。

デフォルト設定のpercolでファイルを選択する

デフォルト設定のpercolでファイルを選択する

画像はデフォルトでの挙動だが,Hpercolのカスタマイズについて書く。ちなみにヴァージョンは percol 0.08 のお話。

percolはpythonスクリプトなので,zawのようにzshに依存していない。また,起動時にrc.py(設定用pythonスクリプト)を実行してくれるのでpecoよりも柔軟に設定できて素晴らしいのだが,デフォルトの挙動では3つの不満があった。

  1. percolを呼び出すたびにzshから異世界に転移するように感じる(プロンプトが違う!)
  2. 選択中の行がマークされているか判別できない
  3. zshの履歴検索後,実行するためにもう一度Enterキーを押すのが面倒くさい(入力キーによって条件分岐したい)

これらを1つずつ解決していこう。

percolを呼び出すたびにzshから異世界に転移するように感じる

異世界とか何言ってんだこいつと思うかもしれないが,まずは私の思いを聞いて欲しい。

percolを入れると嬉しいことは沢山あるが,何はさておきzshの履歴検索をしたいというのが普通だと思う。
 zshの履歴検索は(当たり前だが)zshの機能なので,作業中のシェルから画面遷移せずに使うことができる。
一方,percolはcursesを使った外部コマンドなので,zshの画面からパッと切り替わるわけだ。
私はzshの機能として履歴検索をしたいのであって,percolのことは意識したくない。
percolの画面に遷移すると思考が断絶されてしまうので,視覚的にzshとpercolは見た目が一緒であるべきだ!

ということで,zshと同じ見た目のプロンプトを設定することにした。

プロンプトの設定は rc.py で行うことができる。rc.pyはpercolからexec()で実行されるpythonスクリプトであるので,普通にpythonの関数が使える。私のzshは

PROMPT="%F{cyan} %~ %(!.#.$)%f "
RPROMPT="%F{238}%n@%m %D{%m/%d %T}%f"

なので,rc.pyに次のように記述した。

####################################
# setting for prompt
####################################
import os
import datetime 
# get directory
home = os.path.expanduser('~')
directory = os.getcwd()
if directory.startswith(home):
    directory = directory.replace(home, '~', 1)
# get user and host name
d = datetime.datetime.today()
date = "%02d/%02d %02d:%02d:%02d" %( d.month, d.day, d.hour, d.minute, d.second)
host = os.uname()[1]
user_and_host = os.getlogin() + "@" + host
# set prompt color
color = 'cyan'
# set prompt
percol.view.PROMPT = ur" <" + color + ur">" + directory + ur" $ %q" 
percol.view.RPROMPT = ur" [%i/%I] " + "" + user_and_host + " " + date + ""

これで外見はzshと(ほぼ)同じプロンプトが表示されるので,zshで履歴検索してるんだなぁという実感が湧く。
(個人的には非常に重要なことだ!)

選択中の行がマークされているか判別できない

percolではマークをつけた複数行を返すこともできる。例えば,psコマンドと連携するときにゴミプロセスどもを一括でKILLできて超便利。

だが,percolのデフォルトの設定では,カーソルのある行(選択中の行)のハイライトでとマークされた行のハイライトが上書きされてしまうので,選択中の行がマークされているのか判別することができない。

これはpercolの設定ではなく仕様の問題なので,percol/view.py (pipなら /usr/lib/python2.7/site-packages/percol/view.py などに入ってる)を編集して選択中かつマークされているときのスタイルを設定するオプション “CANDIDATES_LINE_MARKED_AND_SELECTED” を追加することにした。本当にちょろっと書き換えただけだが,diffをとれば以下の様な感じになる。

--- view.py     2015-01-24 17:22:33.224879400 +0900
+++ view.py_new     2015-01-24 17:20:59.793748600 +0900
@@ -36,6 +36,7 @@
     CANDIDATES_LINE_SELECTED = ("underline", "on_magenta", "white")
     CANDIDATES_LINE_MARKED   = ("bold", "on_cyan", "black")
     CANDIDATES_LINE_QUERY    = ("yellow", "bold")
+    CANDIDATES_LINE_MARKED_AND_SELECTED = ("underline", "on_white", "black")

     @property
     def RESULTS_DISPLAY_MAX(self):
@@ -76,7 +77,9 @@
     def display_result(self, y, result, is_current = False, is_marked = False):
         line, find_info, abs_idx = result

-        if is_current:
+        if is_current and is_marked :
+            line_style = self.CANDIDATES_LINE_MARKED_AND_SELECTED
+        elif is_current:
             line_style = self.CANDIDATES_LINE_SELECTED
         elif is_marked:
             line_style = self.CANDIDATES_LINE_MARKED

以上のようにview.pyを書き換えてから,rc.pyに

percol.view.CANDIDATES_LINE_BASIC    = ("on_default", "default")
percol.view.CANDIDATES_LINE_SELECTED = ("underline","on_default"  ,"bold")
percol.view.CANDIDATES_LINE_MARKED   = ("black", "on_white" )
percol.view.CANDIDATES_LINE_MARKED_AND_SELECTED   = ("black", "on_cyan", "underline")
percol.view.CANDIDATES_LINE_QUERY    = ("red", "default")

とすると画像のように,選択中の行がマーク済みか区別できるようになる。

選択中の行がマーク済みか区別できるようになる

選択中の行がマーク済みか区別できるようになる

zshの履歴検索後,実行するためにもう一度Enterキーを押すのが面倒くさい(入力キーによって条件分岐したい)

ターミナル版anything的なpercolをzawの代わりに試してみた – $shibayu36->blog;にある設定

function percol-select-history() {
    local tac
    if which tac > /dev/null; then
        tac="tac"
    else
        tac="tail -r"
    fi
    BUFFER=$(history -n 1 | \
        eval $tac | \
        percol --query "$LBUFFER")
    CURSOR=$#BUFFER
    zle clear-screen
}
zle -N percol-select-history
bindkey '^r' percol-select-history

を.zshrcに書くとpercolでzshの履歴検索ができるようになる。ただし,上記の設定では履歴選択後,zshのプロンプトに戻ってからもう一度Enterを押さないと実行されない。履歴を一旦編集できてこれはこれで便利なのだが,percolへのキー入力によって

Enter : 選択した履歴をそのまま実行
C-k   : 選択した履歴はそのまま実行しない(編集できるようにする)

と使い分けたいのが人情というもの。

バッファの内容を実行するには percol-select-history() にzle accept-line の1行を加えてやれば良い。percolの終了ステータスに意味を持たせて,2が帰ってきたら「選択した履歴をそのまま実行」にするならば,以下のように書き換えることになるだろう。

function percol-select-history() {
    local tac
    if which tac > /dev/null; then
        tac="tac"
    else
        tac="tail -r"
    fi
    BUFFER=$(history -n 1 | \
        eval $tac | \
        percol --query "$LBUFFER")
    RETURN=$?
    CURSOR=$#BUFFER
    zle clear-screen
    test $RETURN -eq 2 && zle accept-line
}
zle -N percol-select-history
bindkey '^r' percol-select-history

次は,percolの設定でEnterだと終了ステータスが2,C-kだと0になるようにしたいのだがそんな機能はないのでまたパッチを当てることにする。以下のパッチでは,Percol.finish()が引数として終了ステータスをとるように書き換えた。

--- __init__.py 2015-01-24 17:22:33.204879400 +0900
+++ __init__.py_new 2015-01-24 17:48:51.208388900 +0900
@@ -283,16 +279,16 @@
     # Finish / Cancel
     # ------------------------------------------------------------ #

-    def finish(self):
+    def finish(self,value=0):
         # save selected candidates and use them later (in execute_action)
-        raise TerminateLoop(self.finish_with_exit_code())          # success
+        raise TerminateLoop(self.finish_with_exit_code(value))          # success

     def cancel(self):
         raise TerminateLoop(self.cancel_with_exit_code())          # failure

-    def finish_with_exit_code(self):
+    def finish_with_exit_code(self,value):
         self.args_for_action = self.model_candidate.get_selected_results_with_index()
-        return 0
+        return value

     def cancel_with_exit_code(self):
         return 1

ここまでくれば,rc.pyに

percol.import_keymap({
    "C-j" : lambda percol: percol.finish(value=2),
    "C-k" : lambda percol: percol.finish()
})

と書いてあげれば目標としていた挙動を実現できる。(うちの環境ではEnterの挙動はC-jでリマップされた。環境によってはとかRETで設定される場合もあるようだ。)

今回はzshの履歴検索の話だが,percolの終了ステータス(リターンコード)で条件分岐ってのはかなり応用範囲が広いはずだ。まぁ,副作用とか,もっと賢いやり方があるかもしれないけど今回の方法はかなりお手軽で良いと思う。

もしpercol本体を書き換えないで,rc.pyだけでなんとか出来る方法があったら教えて下さい。モンキーパッチ的なことをしたいが,スコープの問題でなんともならないような…… (ド素人なのでよくわかっていません)

関連する記事

LINEで送る
Pocket



カテゴリー: CentOS, Debian, Fedora, Python, コマンドメモ パーマリンク

コメントを残す

メールアドレスが公開されることはありません。

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
また,TeX の記法で数式を入力する場合は [latexpage] とコメントの最初に入力してください(QuickLaTeX の機能です)。
例: [latexpage] $\frac{1}{2}$

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.