EmacsでVimperatorやKeysnailのヒントモード機能を提供するpophint.elを作りました

2013/09/14追記
機能や設定については、Wikiにまとめることにしました。
バージョンアップにより、これらは変更されている可能性があります。
最新の情報はWikiを確認して下さい。

これは何?

FirefoxVimライクに操作できるVimperator/KeysnailというFirefoxのアドオンがあります。
自分はVimperatorを使っていて、これが無いと悶絶してしまうくらいの超絶便利なアドオンなのですが、これらのアドオンにはヒントモードなる機能があります。

f:id:aki2o:20130430095654p:plain

"f"を押すと上のようにリンクにポップアップが表示され、続けて辿りたいリンクのアルファベットを押せば、そのページに行けるというものです。
で、これと同じインタフェースをEmacs上で提供する拡張を作成しました。

スクリーンショット

一番シンプルな使用例を以下に示します。
以下のようにバッファが開かれている状態で、

f:id:aki2o:20130430100808p:plain

本拡張で提供されるコマンドを実行すると以下のようになり、

f:id:aki2o:20130430101334p:plain

表示されたアルファベットを押すと該当の箇所にジャンプします。
以下は"yj"と押した場合。表示は大文字ですが、入力は大文字でも小文字でもOKです。

f:id:aki2o:20130430101640p:plain

特徴

拡張性

本拡張は、表示されているバッファの中で、どこかにヒントをポップアップし、選択されたヒントの箇所に対して、なにかを行うというインタフェースを提供します。
で、どこにヒントを表示するか、それに対して何を行うかは、sourceを定義することで変えられるようにしています。
このsourceはanything.elやauto-complete.elのsourceと同じような構造のalistですので、それらをお使いの方はとっつきやすいかと思います。

また、何を行うか(action)については、それがどのsourceに対しても有用なactionの場合は、actionのみ別に定義できます。
例えば、その箇所にカーソルを持っていくとか、その箇所をコピーするといったことは、どのsourceに対しても行いたいことではないかと思っています。

定義済みの機能やsourceについては、下記のpophint-config.elの箇所を見て下さい。

複数のsourceを素早く切り替えられる

複数sourceを扱うコマンドを実行すると、ヒント表示時、以下のようにミニバッファにsourceがリストされ、特定のキー(デフォルトは"s")を押すことで、sourceを切り替えられます。

f:id:aki2o:20130430110437p:plain

カレントでないウィンドウも対象にできる

例えばコーディングする時、別のファイルを参照したりコピペしたりするために、別ウィンドウを開いて行ったり来たりすることが時々ありませんか?
隣のウィンドウのあそこにカーソル持って行きたいとか、あの文章コピりたいとか。結構面倒くさくないですか?
私は心が折れそうです。
なので、カレントでないウィンドウにもヒント表示するようにしました。ウィンドウが複数ある場合は以下のようにミニバッファが表示され、特定のキー(デフォルトは"w")を押すことで、対象のウィンドウが変わります。

f:id:aki2o:20130430112209p:plain

pophint-config.el

本拡張作成にあたり、自分が欲しかった機能やsourceの定義を、pophint-config.elにまとめました。
Elisp書ける方は、このファイルを見てもらえれば、拡張方法はわかると思います。
以下に現在定義されている内容について説明します。

pophint:global-sources

表示名 説明
Sym シンボル構成文字
Quoted クォートされた(\"とかで囲まれた)テキスト部分
URL/Path URL/Filepath形式のテキスト部分
Cmt コメント部分
Line 1行のテキスト

※pophint:global-sourcesは、どのバッファでも使用されるsourceのリストです。

pophint:sources

対象モード 表示名 説明
w3m-mode Link リンク部分 (action:リンク先を表示する)
help-mode Link リンク部分 (action:リンク先を表示する)
info-mode Link リンク部分 (action:リンク先を表示する)
dired-mode Node ディレクトリ内のファイル、ディレクトリ名部分

※pophint:sourcesは、バッファ固有のsourceのリストです。
※上記のLinkがVimperatorのヒントモードと同等の機能かと思います。

共通action

表示名 説明
Yank 選択された箇所のテキストをコピーする
RangeYank 選択された箇所を始点として、終端候補をヒント表示し、その選択箇所までをコピーする

※共通actionの使用については、下記のインストール/設定の箇所を見て下さい。

マークした時にヒント表示

set-mark-command(デフォルトでC-SPC)した時に、リージョンの終端に移動するためのヒント表示を自動で開始し、選択された範囲をコピーします。
この機能の有効無効は設定で変えられます。

isearchの一致箇所に対してヒント表示

ちょっと離れた箇所にカーソル移動させたい時、皆さんはどうしてますか?
私は isearch(C-s) → 適度にワード入力 → C-s連打 → 目的の箇所でisearch-exit(RET) をちょくちょくやります。
が、面倒くさいと常々思ってました。
なので、isearch-exitした時にマッチしている箇所に対してヒント表示して、選択箇所にカーソル移動させられるようにしました。
私と同じことをしている方は、これを使えば、C-s連打しなくてよくなり、ワード入力も短くてよくなります。
この機能の有効無効は設定で変えられます。

インストール

ソース置き場:https://github.com/aki2o/emacs-pophint

package.elを使う場合

2013/07/19 melpaリポジトリからインストール可能になりました。

el-get.elを使う場合

2013/05/01 現在el-getに登録申請中です。
2013/06/30 一向に登録される気配がありません。
2013/07/26 インストール可能になりました。ただし、まだmasterにしか登録されていません。

auto-install.elを使う場合

(auto-install-from-url "https://raw.github.com/aki2o/emacs-pophint/master/pophint.el")
(auto-install-from-url "https://raw.github.com/aki2o/emacs-pophint/master/pophint-config.el")

加えて、下記の依存拡張もそれぞれインストールする必要があります。

手動の場合

pophint.el/pophint-config.elをダウンロードし、load-pathの通った場所に配置して下さい。
加えて、下記の依存拡張もそれぞれインストールする必要があります。

依存拡張

設定

コマンド/API

本拡張により、提供されるユーザ向けコマンドには以下があります。

コマンド 説明
pophint:do 基本となるコマンド。指定されたsourceやactionでヒント表示を開始する。
pophint:do-flexibly pophint:global-sourcesとpophint:sourcesを使ってpophint:doを実行する。
pophint:do-interactively 共通actionの中から実行するactionをユーザに選択させ、pophint:do-flexiblyを実行する。
pophint:redo 最後に実行したpophint:doを再実行する。

また、APIには以下があります。

関数名 説明
pophint:defsource sourceを定義する。本関数実行により、pophint:source-で始まる変数と、pophint:do-で始まるコマンドが定義される。
pophint:defaction 共通actionを定義する。本関数実行により、pophint:do-interactivelyの選択肢が増え、pophint:do-flexibly-で始まるコマンドが定義される。

※本拡張は独自のモードやキーマップは定義しません。コマンドはdefine-keyなどで適宜キーバインドして下さい。
※その他のAPIやカスタマイズ変数については、ソースを見て頂くか、M-x customize-group "pophint" とかして下さい。

設定項目

pophint-config.elにより、以下の設定項目が追加されます。

項目名 説明
pophint-config:set-automatically-when-marking マークした時にヒント表示するかどうか
pophint-config:set-yank-immediately-when-marking マークした時にヒント表示するとした場合、ヒント選択した時点で、そこを終端としてkill-ring-saveを自動実行するかどうか
pophint-config:set-automatically-when-isearch isearch-exitした時にヒント表示するかどうか
pophint-config:set-relayout-when-rangeyank-start 共通actionのRangeYankにおいて、リージョンの終端のヒント表示する際に、ウィンドウ構成を一時的に変更するかどうか (※1)

※1:始点選択した箇所がバッファの下の方だった場合、終端としたい箇所が見えない場合があったのでウィンドウ構成を一時的に変更するようにしましたが、ウィンドウ構成がぴょこぴょこ変わるのがウザいという人もいると思います。

設定例

上記を踏まえ、私は以下のようにしています。

(require 'pophint)
(require 'pophint-config)

;; 表示するヒント数。デフォルトは200です。
(setq pophint:popup-max-tips 400)

;; C-SPCしたら、すぐヒント表示開始
(pophint-config:set-automatically-when-marking t)
;; ヒント選択したら、すぐコピー
(pophint-config:set-yank-immediately-when-marking t)
;; isearch完了時にヒント表示して、カーソル移動できるようにしておく
(pophint-config:set-automatically-when-isearch t)
;; ウィンドウ構成が変わっても、今のところそんなにウザくないので有効にしておく
(pophint-config:set-relayout-when-rangeyank-start t)

;; キーバインド
(define-key global-map (kbd "C-;") 'pophint:do)
(define-key global-map (kbd "C-+") 'pophint:do-flexibly)
(define-key global-map (kbd "M-;") 'pophint:redo)
(define-key global-map (kbd "C-M-;") 'pophint:do-interactively)

;; 特定のaction用のキーバインド
(define-key global-map (kbd "M-y") 'pophint:do-flexibly-yank)
(define-key global-map (kbd "C-M-y") 'pophint:do-rangeyank)

;; Vimperatorと同じイメージで操作できるようにキーバインド追加
(define-key view-mode-map (kbd ";") 'pophint:do-flexibly)
(add-to-list 'w3m-mode-hook
             '(lambda () (local-set-key (kbd ";") 'pophint:do-flexibly))
             t)
(add-to-list 'Info-mode-hook
             '(lambda () (local-set-key (kbd ";") 'pophint:do-flexibly))
             t)

※ご自分の使いやすいように適宜設定して下さい。

その他

Enjoy!!!