これは、カレントバッファ/リージョンを、外部フィルタプログラムで変換する emacs の機能拡張です。
vi の、!(外部プログラム) に相当する機能が emacs にないのを知って悔しいので作りました。
以前に一度 oneliner-el として公開しましたが、同名の有名ソフトウエアがあることを指摘されたので、external-filter-el として改称して出直すことに致しました。
簡単にいうと、カレントバッファ/リージョン を入力として、外部フィルタを実行して、その出力でカレントバッファ/リージョンを置換する、というものです。
本拡張によって、例えば、カレントバッファの置換をするときに、とっても使いにくい elisp の正規表現を使わずに perl や ruby, あるいは super sed(おすすめ) でスマートに置換をする、といったことが可能になります。
また、エラーハンドリングや、コマンド入力ヒストリ機能も充実させ、意図した置換が実現しなかった時にやりなおすのも簡単に出来るようになっています。
現在作成中です ; % make ; make install ;
(add-to-list 'load-path "~/elisp/oneliner-el/") (require 'oneliner)
;; ○キーバインド (define-key global-map [(control ?c) ?! ?b] 'oneliner-process-buffer) (define-key global-map [(control ?c) ?! ?B] 'oneliner-process-buffer-via-shell) (define-key global-map [(control ?c) ?! ?r] 'oneliner-process-region) (define-key global-map [(control ?c) ?! ?R)] 'oneliner-process-region-via-shell) ;; 入力を Shell に与える場合に使う shellのパス (setq oneliner-shell-cmd "/bin/sh") ;; エラー出力においてエラーを起したコマンドとエラーログを分離するセパレタ文字列" (setq oneliner-log-separator "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n") ;; 入力ヒストリをセーブするファイル名、セーブを行なわない場合は nil にする (setq oneliner-history-file "~/.oneliner-history") ;; 入力ヒストリ保管上限値 (setq oneliner-history-limit 1000) ;; 入力ヒストリに、ユニークなエントリだけを追加するかどうかのフラグ ;; non nil の場合、ユニークなエントリだけがヒストリに追加される (setq oneliner-history-save-only-uniq-entry t) ;; OneLinerメニューアイテムを非表示にしたい時は nil にする (setq oneliner-display-menu-p t)
M-x oneliner-process-buffer(C-c ! b) で、"Enter one-liner: " というプロンプトが出るので、カレントバッファを処理するフィルタプログラムを入力する。ここでの入力は、シェルに解釈されないので、通常のコマンドライン入力の時のようなグロブ展開や、ヒストリ展開などが行なわれない。
通常のコマンドライン入力の時同様に、shell 機能のグロブ展開、ヒストリ展開等を行ないたい場合は、代りに oneliner-process-buffer-via-shell(C-c ! B) を使う。
なお、デフォールトでは、このとき "/bin/sh" を使うようになっているが、oneliner-shell-cmd の値を変更して、zsh 等他のシェルを使うことも出来る。
コマンドが正常終了しなかった場合は、書換えをキャンセルして、一時バッファに、コマンドと、そのエラー出力を表示する。
同様に、M-x oneliner-process-region(C-c ! b) で、カレントリージョンを、外部フィルタコマンドで処理する。
入力コマンドをshell で解釈したい場合は、矢張り同様に oneliner-process-region-via-shell(C-c ! R) を使う。
リージョンを設定していない時に、リージョンに対するコマンドを実行すると、カレントポジションに、入力なしで実行された外部プログラムの出力を挿入する。
フィルタで書換えではなく、外部コマンドの実行結果を挿入したい時などに活用されたい。
oneliner-el は、独立した入力ヒストリテーブルを保持し、ミニバッファ入力の再に M-f/b で、過去入力を上下したり、特定のエントリを編集たりして、再利用することができる。
変数 oneliner-history-file に、入力ヒストリ保存ファイルを設定していると(デフォールト動作)、そのファイルに、コマンド実行毎に 入力エントリを保存する。
コマンドは、成功・失敗しても、ヒストリに保存されるので、入力コマンドがエラーとなった場合でも、M-b で前のコマンドに戻って、誤りを修正して再実行することができる。
更に、変数 oneliner-history-save-only-uniq-entry の値が non-nil(デフォールト) であると、入力ヒストリは、重複するエントリがあるとき、ヒストリから古い重複エントリを削除する。
入力ヒストリ保存ファイルを直接編集して、よく使うコマンドをあらかじめ準備しておくことも出来る。
一時バッファに、入力ヒストリを表示する。
M-x oneliner-version でミニバッファにバージョンを表示する
コマンドに数引数をつけると、ミニバッファに表示する代りに、カレントポジションにバージョンを挿入する。
カレントリージョンを書換える時の基本機能は oneliner-el の同等コマンドと同じですが、コマンド実行失敗時のケアなど、人間が対話的に使うことを想定していない設計になっていると思います。但しプログラムから呼出す時は、shell-command-on-region()で十分だと思います。
対話的に使うときは、これからは oneliner-process-region/oneliner-process-region-via-shellを使いましょう。
マークが設定されていない時は、カレントポジションに、長さゼロのリージョンがあるように動作するのが、人間の直感が期待する動作だと思いますが、そう設計されていません。これは駄目なデザインだと思います。
また、これを interactive() 中に記述すると、リーダビリティが悪くなってしまうので、僕はリージョンの獲得に interactive() は使わないことにしています。
駄目なものは駄目なのです。
僕は emacs 使いなので、vi の操作は、うっかり vi が立ち上がったときのために、終了操作しかしらない。
単に自分が無知であることすら知らない、というダケだが、根拠レスに vi で出来ることはすべて emacs で出来ると思っていたのだが、カゼを引いて寝ている時にsed の勉強 をしていて、オライリーの sed & awk の最後に載っているスクリプトの例で、emacs にはない vi の素晴しい機能を発見し、vi についての認識を改めるに至った。
vi の ! コマンドは、カレントバッファのアドレスに指定した部分(無指定ならばバッファ全域)を入力として、シェルが認識するコマンド(パスの通った自作スクリプトも可)を実行して、その出力をカレントバッファと置換する、という目的によっては素晴しく便利なコマンド。
つまり簡単にいうと、!(外部コマンド)で、バッファをフィルタで書換えることができる、ということ。
emacs にも shell-command とか shell-command-on-region とかいった、shell コマンドを実行する機能があるにはあるが、カレントバッファをデータとしてプロセスする、という発想ではない。
で、そのうえにだ、elisp の正規表現は、とっても書きにくく、そしてそれ以上に読みにくいので、emacs の中から、こうした elisp が苦手な仕事を得意とする perl や ruby や sed でバッファをプロセスすることができるととても嬉しいヒトは多い筈だ。
たまたま filter-el(発表済みのconv-elの後継ソフト)という逆の発想のソフト(メカニズムは同じ)を興味本位で作っていたので、それが終ったら、派生品としての vi の ! 相当の elisp も書いてみることにする。