<2015-07-15 Wed> 150707以降のバージョンではアクションが追加できません!!!!!
http://emacs.rubikitch.com/helm-add-actions-2

helm はEmacsにオブジェクト指向化をもたらす偉大なるパッケージです。

候補の集合とアクションが独立しているため、
組み合わせによってはコマンドでは用意されていないことも
できるようになります。

helmの強さは複数の情報源と複数のアクション、
そしてそれらを絞り込み検索できるところです。

helmはデフォルトの状態でもたくさんのアクションが用意されていますが、
ユーザ側で簡単に追加できるようになっていないのが残念です。

そこで、簡単にアクションとキーバインドを追加できるような
elispを書いてみました。

EIEIO化された情報源

数ヶ月前から EIEIO (Emacs LispによるCLOS風オブジェクト指向)
で書かれるようになったため、情報源の定義がかなり複雑になりました。

いまや情報源は2種類の表現方法があります。

  • EIEIOのクラス
  • anything時代から使われている従来の連想リスト

今ではEIEIOのクラスを定義し、
helm-make-source 関数にて連想リスト形式に変換するようになっています。

そのせいでユーザ観点ではいたずらに複雑になってしまいました。

情報源クラスの定義をユーザが変更するには
helm-setup-user-source メソッドを定義します。

アクションを追加するにはその中で
helm-source-add-action-to-source-if を使います。

そのため、ロードの順番によっては
再び情報源の変数をセットする必要があったりと
かなりやっかいなことになってしまいました。

普通のEmacsユーザは絶対ハマッてしまいます。

というか、たかだかアクションを追加したいだけの
普通の人にEIEIOを強要させるのもアレですね。

一方で、連想リストによる情報源については
helm-add-action-to-source
helm-add-action-to-source-if を使います。

もうぐちゃぐちゃですよね。

単一情報源についてはrequire→helm-add-action-to-source

結論を言うと、単一情報源のアクションを追加するには、
requireで情報源の定義をロードした上で
helm-add-action-to-source で追加するのが楽です。

(require 'helm)
;;; お試し情報源
(defvar helm-source-test
  '((name . "test")
    (candidates "aaa" "bbb")
    (action ("foo" . foo))))
;;; アクションbarを追加
(helm-add-action-to-source "bar" 'bar helm-source-test)
;;; 実際にhelmで実行させる。
;;; TABを押せば確かにbarが追加されていることがわかる
(helm :sources 'helm-source-test)

そもそも条件を満たさない限りアクションは使わないので、
helm-add-action-to-source-if で除外するよりも
無条件に追加した方が動作確認が楽だと思います。

typeに対応するアクションは mylisp-helm-add-actions.el を使う

helmでアクションを追加したい場合、
たいていの場合はtype(型)について追加したいでしょう。

そうすることで共通してオレオレアクションが使えるのですから!

しかし、<2015-02-02 Mon>現在のhelmは
typeのアクションに対し helm-setup-user-source
呼んでくれない不具合があります。

おまけに、 helm-type-attributes を変更しても
情報源に反映されないようになってしまいました。

せめて連想リストにtypeを吐き出してくれたら、
アクションを追加するプラグインを書けるのですが、
それすらできないなんて、本当に迷惑この上ないです。

そこをなんとか読み解き、ユーザ側でアクションを追加できるようにしました。

キーバインドを簡単に設定

helmは情報源ごとにキーマップを設定できるようになっていますが、
現状のhelmでは気楽にアクションのキーバインドを設定できません。

このように、いたずらに冗長かつ無駄な記述にあふれています。

(defvar helm-find-files-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map helm-map)
    ;; 
    (define-key map (kbd "C-c @")         'helm-ff-run-insert-org-link)
    (define-key map (kbd "C-c r")         'helm-ff-run-find-file-as-root)
    ;; 
    map))
;;; helm-files.elだけでこれらのコマンドが30近く並んでいる
;;; helm全体では60個も…
(defun helm-ff-run-insert-org-link ()
  (interactive)
  (with-helm-alive-p
    (helm-quit-and-execute-action 'helm-files-insert-as-org-link)))
(defun helm-ff-run-find-file-as-root ()
  (interactive)
  (with-helm-alive-p
    (helm-quit-and-execute-action 'helm-find-file-as-root)))

それならば単純にアクションをそのままキーに
割り当てるという発想ができないのでしょうか?

(helm-define-action-key helm-find-files-map (kbd "C-c @") 'helm-files-insert-as-org-link)
(helm-define-action-key helm-find-files-map (kbd "C-c r") 'helm-find-file-as-root)

このようにたった2行で済むんですから。

ちょっとは冗長性を排除するように努力しましょうよ、ねぇ?

mylisp-helm-add-actions.el(以下のコードと同一)

;;; -*- lexical-binding: t -*-
(require 'helm)
;;; キーバインドの設定
(defun helm-define-action-key (keymap key def)
  "アクションをキーバインドに設定"
  (define-key keymap key
    (lambda ()
      (interactive)
      (with-helm-alive-p
        (helm-quit-and-execute-action def)))))

(provide 'mylisp-helm-add-actions)

type:fileに対しアクション「Stat」を追加する例 150202071912.helm-add-actions.stat.el(以下のコードと同一)

file型に対してstatを実行するアクションを追加し、
C-c C-sでも実行できるようにする例です。

helm-filesを読み込むタイミングに注意です。

強引なEIEIO化の弊害です(泣)

(require 'mylisp-helm-add-actions)

(defun file-do-stat (filename)
  (interactive "FStat File: ")
  (shell-command (format "stat %s" (shell-quote-argument filename))
                 "*Stat*"))
;;; helm-files.elロード前に設定を行う必要がある
(setq helm-user-actions-type-file
      '(("Stat" . file-do-stat)))
;;; helm-generic-files-mapを変更するためにhelm-files.el
;;; (実際はhelm-locate.el)をロードする
(require 'helm-files)
(helm-define-action-key helm-generic-files-map (kbd "C-c C-s") 'file-do-stat)

実行方法

$ wget http://rubikitch.com/f/150202071912.helm-add-actions.stat.el http://rubikitch.com/f/mylisp-helm-add-actions.el
$ emacs -Q -f package-initialize -l mylisp-helm-add-actions.el -l 150202071912.helm-add-actions.stat.el

M-x helm-mini とか M-x helm-recentf の後に
TABなりC-c C-sなり押すことで確認できます。

本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。