@peccul is peccu

(love peccu '(emacs lisp cat outdoor bicycle mac linux coffee))

= (car emacs-advent-calendar-2010) ;=>defadvice

[2010-12-04 12:28:30]
>>>[emacs]

Emacs Advent Calendar jp: 2010 : ATNDも6日目です.
5日目はid:kiwanamiさんのEmacs上のATOKで快適日本語生活 / 2010 Emacs Advent Calendar - 技術日記@kiwanamiで,7日目はid:moozさんです.楽しみ!

特に書くネタを考えていたわけじゃないんですが,先日anything-mac-itunes.elが公開されたので,それにからめて書いてみることにします.

流れ

ようするにdefadvice入門記事です.

長くなっちゃったので,気になるところだけつまんだ方がいいかもしれませんorz

applescriptを使ったelisp

MacAppleScriptを利用しているelispが幾つかあるので紹介します.

ミニバッファで入れた単語を辞書.appで調べる

anythingインタフェースでiTunesのライブラリを絞り込み検索して,再生したり,前後の曲へ移動,再生停止の操作ができます

Mac用のEvernoteアプリケーションにノートを渡せるようにしたもの

CSSやHTMLを編集した時にSafariでリロードする

こんな感じでEmacsからMacのアプリケーションにメッセージを送っていろんなことができるので,使い用によってはかなり便利です.

AppleScriptの実行方法も(do-applescript "AppleScriptのコード")とするだけでいいのでとても簡単です.

Carbon Emacsにおけるdo-applescript

とっても便利なAppleScriptですが,Carbon Emacs(Emacs22)のdo-applescript文字コードをShift-JISでやり取りしているみたいなんですね.

Cocoa Emacs(Emacs23)ならutf-8でやり取りしているのでそのまま使えるのですが,へたれな私はまだCarbon Emacsを使っているので,なんとかしたいなと.

そこでdefadviceの登場ですよ.

文字コードが違うなら文字コード変えてAppleScriptに渡したらいいんですよね.というわけで

(when (string-match "^22" emacs-version)
  (defadvice do-applescript (before do-applescript-before-advice (script))
    (ad-set-arg 0 (encode-coding-string (ad-get-arg 0) 'shift_jis)))
  (defadvice do-applescript (after do-applescript-after-advice (script))
    (setq ad-return-value (decode-coding-string ad-return-value 'shift_jis)))
  ;;(ad-deactivate 'do-applescript)
  (ad-activate 'do-applescript))

こんな感じにアドバイスしてあげるとdo-applescriptを実行する前にAppleScriptのコードをshift_jis
に変換して,do-applescriptの実行結果をshift_jisからutf8(そのとき使っている文字コード)に変換してくれるようになります.

ちなみにapplescript-string-literal.elで曲名が文字化けしていたのでこんな感じに書きました.

(when (eq system-type 'darwin)
  (require 'anything-mac-itunes)
  (when emacs22-p                       ; (string-match "^22" emacs-version)
    (defadvice do-applescript (before do-applescript-before-advice (script))
      (ad-set-arg 0 (encode-coding-string (ad-get-arg 0) 'shift_jis)))
    (defadvice do-applescript (after do-applescript-after-advice (script))
      (setq ad-return-value (decode-coding-string ad-return-value 'shift_jis)))
    ;;(ad-deactivate 'do-applescript)
    (ad-activate 'do-applescript))
  (global-set-key (kbd "C-c s m") 'anything-mac-itunes)
  (global-set-key (kbd "C-c s b") 'anything-mac-itunes-back-track)
  (global-set-key (kbd "C-c s n") 'anything-mac-itunes-next-track)
  (global-set-key (kbd "C-c s p") 'anything-mac-itunes-playpause-track)
  (global-set-key (kbd "C-c s c") 'anything-mac-itunes-show-current-track-info)
  )

defadvice入門

さて,defadviceが出てきたので,どういうことかという話

defadviceっていうのは,すでに定義されている関数の動作を変更するマクロで,関数を書き直すよりもスマートなカスタマイズができます.(あとCで書かれた関数はこれじゃないといじられへんと思う)

さっきのコードで簡単に説明してみます

(when (string-match "^22" emacs-version) ;(1)
  (defadvice do-applescript              ;(2)
    (before do-applescript-before-advice (script))    ;(3)
    (ad-set-arg 0 (encode-coding-string (ad-get-arg 0) 'shift_jis)))          ;(4)
  (defadvice do-applescript
    (after do-applescript-after-advice (script))      ;(5)
    (setq ad-return-value (decode-coding-string ad-return-value 'shift_jis))) ;(6)
  ;;(ad-deactivate 'do-applescript)      ;(7)
  (ad-activate 'do-applescript))         ;(8)

(1)はemacsのメジャーバージョンが22かどうかチェックしてます.23だったらこのアドバイスがいらないからね.

ここではdo-applescriptに二つのアドバイスをしています.

(2)はこのアドバイスがdo-applescriptという関数に対するアドバイスってことを示してます

(3)で,アドバイスの名前がdo-applescript-before-advice.
このアドバイスが実行されるのはdo-applescriptが実行される前(before)ということを示しています.
(4)の最初のafterでdo-applescriptの実行後にアドバイスdo-applescript-after-adviceが実行されるってことを意味してます.

(4)がなにしてるかっていうと,(ad-set-arg 0 hoge)で0番の引数(1つめ)にhogeを設定するって意味で,(ad-get-arg 0)は0番の引数を取り出すって意味なので,引数scriptの文字コードshift_jisに変換して,引数scriptに戻してることになります.

(6)ではad-return-valueが関数の返り値を表すことを使って,do-applescriptの返り値をshift_jisから現在のエンコードに変換して,返り値を設定し直していることになります.

(7)(8)はそれぞれdo-applescriptにたいするアドバイスを無効,有効にする記述です

before,afterの代わりにaroundを指定するとdo-applescriptそのものを書き換えることができます.

(defadvice do-applescript
  (around do-applescript-around-advice (script))
  (ad-set-arg 0 (encode-coding-string (ad-get-arg 0) 'shift_jis))
  ad-do-it
  (setq ad-return-value (decode-coding-string ad-return-value 'shift_jis)))

ad-do-itがdo-applescriptそのものを呼び出します

細かい話

GNU Emacs Lispリファレンスマニュアル: 16. Emacs Lisp関数のアドバイスみると詳しいんだけども,かいつまんで書きます.

詳しく見るとこんな感じに書きます.*がついてるのは省略可能です.

(defadvice アドバイスしたい関数名
   (どの部分にアドバイスするか アドバイスにつける名前 *何番目のアドバイスにするか *引数のリスト 使い方フラグ)
   *ドキュメント
   本体)
  • どの部分にアドバイスするかはbefore,around,after,activation,deactivationが指定できて,それぞれアドバイスしたい関数(some-funcとする)にたいして,some-funcの前に実行するのか,some-funcのあとに実行するのか,その両方で実行するかを示してます.activationとdeactivationはわかりませんでした.
  • アドバイスにつける名前はM-x describe-function some-funcしたときに*Help*バッファに表示されたり,アドバイスを解除する時,有効/無効にする時に使う名前なんで,かぶらんかったら何でもいいです
  • 何番目かってのは,同じ関数に何個もアドバイスした時に何番目のアドバイスにするか.っていうので,0番目から順番に実行されていきます.普段は使わない.競合してうまく動かん時に思い出せたらいいと思います
  • 引数のリストは,some-funcの定義と同じ個数で引数を書いていればアドバイス内で引数を参照することができます
  • 使い方フラグ.これはこの記事を書くまで知りませんでした笑.てか今まで書いたことなかったけど動いてたな...
フラグ 説明
activate アドバイスをすぐ有効にする
protect 保護する.別のアドバイスやsome-func自身でエラーが発生しても実行されるようにする
compile 結合定義っていうのをコンパイルするらしいです.わかりません
disable とりあえず有効にしない(ad-activateで有効化する必要がある)
preactivate アドバイスをコンパイルする時にsome-funcのアドバイスを有効にしておく
  • ドキュメントを書くとsome-funcのドキュメントに追加して*Help*に表示されるようになります
  • 本体にアドバイスの内容を書きます

好きなことを書けます.引数をいじったり返り値を変えたりできます.

よくやるのは,特定の関数を呼ぶ時だけ設定を変えるってやつやね.

引数,返り値の参照

(ad-get-arg n) n番目の引数を返す
(ad-get-args n) n番目以降の引数リストを返す
(ad-set-arg n value) n番目の引数の値をvalueに設定する
(ad-set-args n values) n番目以降の引数の値をvalues(リスト)に設定する
ad-return-value 返り値を表す変数.参照すれば直前のアドバイスとかの実行結果が入ってるし,値を設定すれば返り値の値を設定できる

yasnippet

忘れるところだった.というかいつも忘れるんだけど,defadviceって覚えらんないからyasnippetスニペットほりこんだら楽に書けるよねー

というわけでスニペットです.emacs-lisp-modeでdefadviceと打って,TABキー押すと展開されて,関数名,どの部分にアドバイスするかをTABで順番に入力していけます

# -*- mode: snippet -*-
# name: define advice
# key: defadvice
# binding: "keybinding"
# expand-env: 
# --
(defadvice $1 (${2:$$(yas/choose-value '("before" "around" "after" "activation" "deactivation"))} $1-$2-advice ($3))$>
  $0$>
)$>
(ad-activate '$1)
;; (ad-deactivate '$1)

長くなりすぎましたが.最後まで読んでくれてありがとうございます.Emacs Advent Calendarもまだまだ続きます!

素敵なEmacsライフを!