連続操作を素敵にするsmartrep.el作った


この日記はEmacs Advent Calendar jp: 2011 : ATNDの19日目です。 昨日は、おきゃんさんのelispでカップリングするcoupling.elでした。

どのキーにバインドするか

Emacsを使っていると、多くの人が経験するであろう悩みの一つにキーバインドがあります。

当たり前ですがバインドできるキーは有限で、しかも楽して打てるキーともなるとそんなに多くはないように思います。VimのようなモードをもたないEmacsでは、これは宿命のようなものです。

なので、この問題を解消する多くの拡張があって、key-chord.elsmartchr.elは使われている方も多いんではないかと思います。

ただ、どちらも万能というわけではなくて、例えば、key-chord.elは通常の入力とバッティングすることが往々にしてあるので、バインドするキーの選択にはかなり用心する必要がありますし、smartchr.elも入力パターンがトグルするという特徴上、連続する入力とかには向いていません。

こんな感じで今のところこの枯渇問題を解消する決定打というのはなくて、いろいろと組み合わせて使うのが良いんではないかと思います。

prefixキーで大丈夫か

prefixキーと組み合わせたキーをバインドするというのも、枯渇問題対策として有効な方法の一つだと思います。僕もあまり使わないC-qをprefixにしてキーバインドを定義しています。

(defvar ctl-q-map (make-keymap))
(define-key global-map "\C-q" ctl-q-map) 

また、標準のEmacsで使えるので、マークアップ言語のメジャーモードやOrg-modeなどキーバインドがたくさん定義されているモードでは、この方法でキーバインド枯渇問題を克服しています。

ただ、prefixキーによる方法も時と場合によってはかなりださいです。例えば先程挙げたOrg-mode。ヘッダー間の移動をするoutline-next-visible-headingとoutline-previous-visible-headingというのが定義されていてそれぞれC-c C-n, C-c C-pというC-cをprefixとするキーがバインドされています。

こういった移動系コマンドは複数回連続して入力することがとても多いと思いますが、このコマンドを使ってちょっと離れたヘッダーまで移動しようともなるとC-c C-nやC-c C-pという長たらしいキーを延々と入力させられることとなります。

https://cacoo.com/diagrams/D85Mnj5qnKif3HX0-4C93F.png

とてもやってられません。

smartrep.elとは

smartrep.elとは、連続して操作する際のprefixキー入力をキャンセルさせるためのelispです。 インストールには、auto-install.elをお使いの方はM-x auto-install-from-emacswiki してsmartrep.elと入力してください。Emacs24かpackage.elをお使いの方は、Marmaladeをリポジトリに登録してから、M-x list-packages としてsmartrep.elをインストールしてください。詳しいことは以前書いた日記を参考にしてください。

Marmaladeはお手軽感が素敵なEmacs Lispのリポジトリサイト

後は.emacsなどに(require ‘smartrep)と書いておけば、終わりです。

さて、言葉で説明してもなかなか伝わりにくそうなのでここからは使用例というか、自分が使っている設定を参考に説明しようと思います。

まずは、先程のOrg-modeのキーバインドで楽するために次のようなものを書きます。

(eval-after-load "org"
        '(progn
           (smartrep-define-key 
            org-mode-map "C-c" '(("C-n" . (lambda () 
                                            (outline-next-visible-heading 1)))
                                 ("C-p" . (lambda ()
                                            (outline-previous-visible-heading 1)))))))

smartrep-define-keyの引数は順に適用するキーマップ、使うprefix、そして、prefixに続けるキーと関数の連想配列の三つとなっています。

この式を評価したならば、先程のまどろっこしいキー操作は必要ありません。2回目移行の操作はprefixキーを省略することができます。

https://cacoo.com/diagrams/D85Mnj5qnKif3HX0-9BD00.png

このキーバインドをキャンセルしたい時は、C-n, C-p以外のキーをなにか入力してください。通常のキー操作に戻ります。

二画面分割時の画面移動

画面を分割して、片方にレファレンスを開き片方でコーディングなどありがちなパターンではないでしょうか。

当然キャレットはコーディングしているウィンドウにあることがほとんどでリファレンスを開いているウィンドウにいくことはあまりありません。

と言っても、ページを送ったり見えない部分を見たい時は操作する必要があるわけで。標準でもscroll-other-windowがC-M-vにバインドされていますが移動幅大きいし、あまり使い勝手がよくありません。そこで、となりのウィンドウをキャレットを移動させることなく操作できるようにしてみます。

(smartrep-define-key 
 global-map "C-q" '(("n" . (lambda () (scroll-other-window 1)))
                    ("p" . (lambda () (scroll-other-window -1)))
                    ("N" . 'scroll-other-window)
                    ("P" . (lambda () (scroll-other-window '-)))
                    ("a" . (lambda () (beginning-of-buffer-other-window 0)))
                    ("e" . (lambda () (end-of-buffer-other-window 0)))))

n,pだと1行のスクロール、N,Pで1ページ分のスクロール、a,eでバッファの先頭と最後に移動します。

Firefoxをリモート操作

先程の例と同様、ブラウザで資料などを見ながらEmacsでコーディングとか、Webプログラマの方とでしたら書いたコードの実行結果をブラウザで確認など結構やるんではないでしょうか。

ページを送ったり、リロードするだけのためにフォーカスをブラウザに移すとかめんどすぎます。Emacsからリモートでブラウザを操作できるようにしたいところです。

これを実現するにはFirefoxのmozreplとmoz.elを使うと良いです。導入などについては、以下のサイトで詳しく説明されています。

Firefox と Emacs の会話 | Amrta

Mozreplとmoz.elを導入したならば、以下のようなコードを評価します。

(autoload 'moz-minor-mode "moz" "Mozilla Minor and Inferior Mozilla Modes" t)
(moz-minor-mode t)

(defun moz-send-message (moz-command)
  (comint-send-string
   (inferior-moz-process)
   (concat moz-repl-name ".pushenv('printPrompt', 'inputMode'); "
           moz-repl-name ".setenv('inputMode', 'line'); "
           moz-repl-name ".setenv('printPrompt', false); undefined; "))
  (comint-send-string
   (inferior-moz-process)
   (concat moz-command
           moz-repl-name ".popenv('inputMode', 'printPrompt'); undefined;\n")))

(defun moz-scrolldown-1 ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollLineDown');\n")) 

(defun moz-scrolldown ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollPageDown');")) 

(defun moz-scrollup-1 ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollLineUp');\n")) 

(defun moz-scrollup ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollPageUp');")) 

(defun moz-top ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollTop');\n"))

(defun moz-bottom ()
  (interactive)
   (moz-send-message "goDoCommand('cmd_scrollBottom');\n"))

(require 'smartrep)
(smartrep-define-key 
 global-map "M-g" '(("n" . 'moz-scrolldown-1)
                    ("N" . 'moz-scrolldown)
                    ("p" . 'moz-scrollup-1)
                    ("P" . 'moz-scrollup)
                    ("a" . 'moz-top)
                    ("e" . 'moz-bottom)))

先程の画面分割の操作と同じような感じでFirefoxを操作できるようになると思います。

他の利用方法

smartrep.elは非常にシンプルなelispですが、いろいろと応用できるケースがあるんではないかと思います。

例えば今回紹介した移動系のコマンド意外でも、フレームやウィンドウのサイズ変更も連続的な操作が必要になりそうですし、undo、redo関係のコマンドでも結構連打している気がします。

HTMLなどのメジャーモードでは移動系のコマンドが多いので、smartrep.elを使うことでスマートに移動できるようになりそうですね。

というわけで、smartrep.elのご紹介でした。Emacs Advent Calendarはまだまだ続きます。 明日は、ken_mさん、今からとても楽しみです。