yasnippet を導入してみた

yasnippet がなんだかすごい盛り上がっているようなので使ってみることにした。

導入手順

yasnippet関連の設定 - antipopや、anything-c-yasnippetをcodereposにコミットしました - IMAKADOの日記で紹介されていたものを自分なりにまとめてみたら、.emacs に追加する内容はこんな感じになった。

;;; yasnippet の設定
;; 参考 http://d.hatena.ne.jp/antipop/20080321/1206090430
(require 'yasnippet)
;; メニューは使わない
;; (setq yas/use-menu nil)
;; トリガはSPC, 次の候補への移動はTAB
(setq yas/trigger-key (kbd "SPC"))
(setq yas/next-field-key (kbd "TAB"))
;; ポップアップを dropdown-list にする (効果なし?)
(require 'dropdown-list)
(setq yas/text-popup-function #'yas/dropdown-list-popup-for-template)
;; コメントやリテラルではスニペットを展開しない
(setq yas/buffer-local-condition
      '(or (not (or (string= "font-lock-comment-face"
                             (get-char-property (point) 'face))
                    (string= "font-lock-string-face"
                             (get-char-property (point) 'face))))
           '(require-snippet-condition . force-in-comment)))
;; 複数のディレクトリからスニペットを読み込む。
;; yasnippet の snippet を置いてあるディレクトリ
(setq yas/root-directory (expand-file-name "~/.emacs.d/snippets"))
;; 自分用スニペットディレクトリ(リストで複数指定可)
(defvar my-snippet-directories
  (list (expand-file-name "~/.emacs.d/my-snippets")))
;; yasnippet 公式提供のものと、
;; 自分用カスタマイズスニペットをロード同名のスニペットが複数ある場合、
;; あとから読みこんだ自分用のものが優先される。
;; また、スニペットを変更、追加した場合、
;; このコマンドを実行することで、変更・追加が反映される。
(defun yas/load-all-directories ()
  (interactive)
  (yas/reload-all)
  (mapc 'yas/load-directory-1 my-snippet-directories))
;;; yasnippet展開中はflymakeを無効にする
(defvar flymake-is-active-flag nil)
(defadvice yas/expand-snippet
  (before inhibit-flymake-syntax-checking-while-expanding-snippet activate)
  (setq flymake-is-active-flag
        (or flymake-is-active-flag
            (assoc-default 'flymake-mode (buffer-local-variables))))
  (when flymake-is-active-flag
    (flymake-mode-off)))
(add-hook 'yas/after-exit-snippet-hook
          '(lambda ()
             (when flymake-is-active-flag
               (flymake-mode-on)
               (setq flymake-is-active-flag nil))))
;; yasnippet の anything インターフェース anything-c-yasnippet
;; http://d.hatena.ne.jp/IMAKADO/20080324/1206370301
(require 'anything-c-yasnippet)
(setq anything-c-yas-space-match-any-greedy t)
(global-set-key (kbd "C-c y") 'anything-c-yas-complete)
;; anything-c-yasnippet を使うモードの登録
(add-to-list 'yas/extra-mode-hooks 'ruby-mode-hook)
(add-to-list 'yas/extra-mode-hooks 'cperl-mode-hook)
;; yasnippet の初期化
(yas/initialize)
(yas/load-all-directories)

スニペットの置き場所

id:antipop さんは自分用のスニペットを保存するディレクトリをいくつかに分けていたけど
僕は今のところ1つで十分なので、公式の snippets ディレクトリと同じ場所に、
my-snippets ディレクトリを作ってそれを自分用のディレクトリにすることにした。
my-snippets は snippets をコピーして名前を書き換えてしまうのがいいと思う。
で、僕は ruby-mode のスニペットの1行ブロックが、外人さんの好きな
each { |e| code }
って書き方になってるのが気に入らなかったので
each {|e| code }
に直しておいた。
こういう作業は moccur-grep-find 使うと早い。
~/.emacs.d/my-snippets/ruby-mode/ を "{ |\$e|" で検索して結果を書き換えた。
(moccur-grep-find の詳しい使い方についてはこちらを参照のこと → Emacs で wdired と moccur-edit を使っていない人は(ry - ひげぽん OSとか作っちゃうかMona-)

dropdown-list.el について

dropdown-list.el は正直よく分からない。
もしかしたら機能しているのかもしれないけど
僕の Meadow3 では設定をしても何も変化が見られない。
EmacsWiki: dropdown-list.el

anything-c-yasnippet.el について

仕事早い!すばらしい!
でも1つだけ気になることがある。

選択後にminibufferにどのkeyに割り当てられているが表示されるのでよく使うsnippetは覚えられるというメリットもあります。

yasnippet, anything-c-yasnippetのまとめエントリー - IMAKADO::BLOG

この機能なんだけど、一覧で表示できるようにしてもらえたらすごく嬉しい!
例えば、ruby-mode で anything-c-yas-complete を呼び出したときに以下のように列挙されるけど

all? { |...| ... }
any? { |...| ... }
attr_accessor ...
attr_reader ...
attr_writer ...

これをこんな感じにしてもらえたらいいなぁ…。

all     | all? { |...| ... }
any     | any? { |...| ... }
rw      | attr_accessor ...
r       | attr_reader ...
w       | attr_writer ...

理由は2つある。
1つ目。僕は auto-save-buffers を使って
キーボード入力後 0.5 秒間何もしないと自動的にファイルが保存されるようにしている。
だから、補完後にミニバッファに表示されたメッセージは
すぐに上書きされてしまうので、確認している余裕があまりない。
2つ目。キーの割り当てを確認するときは一覧で見れた方が便利だと思う。


もしかしたら、anything のインターフェースを使うために
わざと上のようなことをしていないのかもしれないけど
そうだとしたらごめんなさい!

TAG がトリガーだと挙動があやしい

でも1ついやんなところがあって、1つのsnippetを発動させるとそのsnippetのすべての入力欄をTABキーで移動して書き終わらないと次の snippetが発動しないような挙動になってるようです。また、そのsnippetをすべて書き終わる前にその部分を消去してしまうと、それ以降の snippetが発動しないっぽい。でもなにかの弾みで発動したり…。どんな条件でリセットされてるのかよくわかりませんでした。

yasnippet.elをインストールしてみた

Clouder さんの言っているいやんな現象は僕の環境でも再現された。
Clouder さんの yasnippet は 0.3.1 の bundle で
僕が今回インストールした yasnippet は 0.4.4 の bundle じゃないやつだけど
現在最新の 0.4.4 でもこの問題はまだ改善されてないみたい。
でもトリガーを TAB から SPC にしたらおかしな挙動はなくなった。
C-g で必ずリセットもかけられる。これで安心。

SPC トリガー万歳!

トリガーを SPC にするっていうのは id:antipop さんのアイディアなわけだけど、これお勧めです。
SPC の方が TAB より絶対気持ちよく使える。
例えば、ruby-mode で ruby-electric を使っている人は SPC トリガーを併用することで
あたかも ruby-electric がパワーアップしたかのように使える。
any まで入力して SPC 押すと any?{|e| } って展開されるとかね。
ruby-electric と同じインターフェースで ruby-electric だけじゃできなかったことができるようになる。


僕は RSpec の describe とか before とか it とかを
ruby-electric が補完してくれたらなぁ…と思っていて、
実は yasnippet を試す前は、このために ruby-electric を拡張しようかと考えていた。
でも yasnippet + SPC トリガーのおかげでその必要はなくなった。
ruby-electric を無理にいじらなくても
yasnippet に新しいスニペットを追加すればいいことが分かったからだ。


一例を挙げると、describe 用のスニペットはこんな感じだ。

#name : describe ..., "..." do ... end
# --
describe ${1:klass}, "${2:when}" do
  $0
end

このコードは ~/.emacs.d/my-snippets/text-mode/ruby-mode/desc として保存している。
細かい仕様はまだよく分からないけど、スニペットを作るのはあまり難しくはない。
ダウンロードしたときにいっしょについてくる他のスニペットを読めば
どう書けばいいかは何となく分かると思う。

その他

  • 新しいスニペットを追加したら、「M-x yas/load-all-directories」を使うことでロードできる。
  • if SPC した後に、そこから C-b SPC 繰り返すとおもしろい。
  • TAG トリガーの挙動があやしい原因は思いつく限り3つ。
    • Meadow
    • auto-save-buffers との相性問題
    • インデント


なんか久しぶりに長いエントリを書いた。