deferred.el であそんでみる

deferred:loop を接続したいと思っていろいろやってみた。

ためしみた

最初に書いたのがこれ。

(deferred:$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda () 
      (message "%s" x)))
  (deferred:loop (loop for x from 1000 downto 0 collect x)
    (lambda () 
      (message "%s" x)))
  (deferred:nextc it
    (lambda ()
      (message "end"))))

うまくいかない。最初のloopと次のloopが一緒に走ってる感じ。次に deferred:nextc で loop を囲んでみた。

(deferred:$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda (x) 
      (message "%s" x)))
  (deferred:nextc it
    (lambda ()
      (deferred:loop (loop for x from 1000 downto 0 collect x)
        (lambda (x) 
          (message "%s" x)))))
  (deferred:nextc it
    (lambda ()
      (message "end"))))

これだとうまくいった。deferred:loop は前の deferred と接続できないから、deferred:nextc で開始を遅延させる。

しらべてみた

微妙に不思議なのが 2回目の loop が終わってから "end" ってメッセージがでること。2回目の loop がはじまったらそのまま deferred:nextc が終わってすぐに "end" って出るんじゃないかと思った。

そこで deferred:nextc の実装を見てみた。deferred:nextc は簡単。 callback を呼び出す deferred を作って 引数の d と繋ぐだけ。

(defun deferred:nextc (d callback)
  "Create a deferred object with OK callback and connect it to the given deferred object."
  (let ((nd (make-deferred :callback callback)))
    (deferred:set-next d nd)))

ここから呼ばれてる deferred:set-next で目的の処理をしてた。deferred:exec-task の返り値が deferred ならそれを返り値としてる。

(defun deferred:set-next (prev next)
  "[internal] Connect deferred objects."
...
    (let ((ret (deferred:exec-task
                 next 'ok (deferred-value prev))))
      (if (deferred-p ret) ret
        next))
...)

RAEDME の「実行時に接続」とか「lambdaの返り値に気を使う」は多分これのこと。
やっとなっとくした。

もうちょっとあそんでみた

まだほとんど使ってないけど、deferred:$ を使うときって大体 deferred:nextc で接続するんじゃないかと思う。そう思って引数の関数を deferred として順次実行する deferred:sequential ってのを作ってみた。

(defun deferred:sequential (&rest args)
  (deferred:loop args 'funcall))

これでさっきの loop のやつはこんな風に書ける。少しみやすくなった。

(deferred:sequential
  (lambda ()
    (deferred:loop (loop for x from 0 to 1000 collect x)
      (lambda (x) 
        (message "%s" x))))
  (lambda ()
    (deferred:loop (loop for x from 1000 downto 0 collect x)
      (lambda (x) 
        (message "%s" x))))
  (lambda ()
    (message "end")))

lambda を書くのが面倒ならこんなのを定義するのもいいかもしれない。

(defmacro deferred:$$ (&rest elements)
  (lexical-let ((elements elements))
    `(deferred:sequential
       ,@(loop for i in elements
               collect `(lambda () ,i)))))

これを使うと lambda を書かなくてよくなる。やりすぎっぽけど。

(deferred:$$
  (deferred:loop (loop for x from 0 to 1000 collect x)
    (lambda (x) 
      (message "%s" x)))
  (deferred:loop (loop for x from 1000 downto 0 collect x)
    (lambda (x) 
      (message "%s" x)))
  (message "end"))

いろいろためしてみたけど

test-deferred.el を見るのが一番はやく理解できるんじゃないかと思った。というわけで deferred.el を使いたい人は test-deferred.el を見るのがいいと思います。

generic.elで俺々モードを作る

Emacs Advent Calendar jp: 2010 : ATND 19日目です。昨日は [twitter:@r_takaishi] さんの org-modeとAnythingが交差するとき,物語は始まる - うどん駆動開発 でした。

Web漁ったり会社で Emacs 使ってる人に聞いたりしてると、みんな generic.el の事を知らないらしいので、ちょっと紹介してみようかと思います。

generic.el はハイライト程度の簡単なメジャーモードをお手軽に作るためのライブラリです。Emacs20くらいの昔から標準添付です。「このファイル用のモードってEmacsに無いなぁ」なんて時に使ったりします。

たとえば

例えばこんなシンタックスのファイルがあるとします

  • "#" ではじまるのはコメント
  • "/*", "*/" でかこまれてるのはコメント
  • hoge, fuga, piyo はキーワード
  • 数字には色を付けたい

そんなときはこんなのを.emacsとかに書いとけばいいんです。

(define-generic-mode hoge-fuga-piyo-mode
  ;; コメントになる文字列の指定
  '("#"
    ("/*" . "*/"))
  ;; キーワードの指定
  '("hoge" "fuga" "piyo")
  ;; もうちょっと難しいキーワードの指定
  '(("[0-9]+" . font-lock-constant-face))
  nil nil
  "Major mode for hoge, fuga, piyo")

そうするとこんな感じでハイライトしてくれます。便利。

ここで作った hoge-fuga-piyo-mode はちゃんとしたメジャーモードなので auto-mode-alist に設定することもできるし、hoge-fuga-piyo-mode-hook もちゃんと存在します。

define-generic-mode について

サンプルで大体分かるとは思いますが、define-generic-mode マクロの簡単な説明をしときます。

define-generic-mode はこんな風に宣言されてます。

(define-generic-mode MODE COMMENT-LIST KEYWORD-LIST FONT-LOCK-LIST AUTO-MODE-LIST FUNCTION-LIST &optional DOCSTRING)
  • MODE: モード名。説明不要ですね。
  • COMMENT-LIST: コメントにしたい文字のリストを指定します。指定できるのは「文字」「2文字までの文字列」「コンスセル」。一行コメントの場合は文字にして、ブロックコメントを作りたい場合はコンスセルを使います。
  • KEYWORD-LIST: ハイライトするキーワードです。
  • FONT-LOCK-LIST: 直接 font-lock-keywords を書く場所。KEYWORD-LISTじゃ足りない場合にはこっちを使います。普通は "(REGEXP . FACE)" の羅列で済むと思います。
  • AUTO-MODE-LIST: ここに正規表現を書いておくと勝手に auto-mode-alist に登録してくれます。
  • FUNCTION-LIST: hook の実行前にここにある関数を実行してくれます。
  • DOCSTRING: docstringです。書いときましょう。

詳細は define-generic-mode の docstring を見てください。

auto-complete と組み合わせる

せっかくキーワードを指定してるんだから auto-complete と組み合わせないともったいないです。こんなのを定義しておけば、define-generic-mode で作ったモードで、キーワードの auto-complete ができるようになります。

(defvar ac-generic-keywords nil "Generic keywords for auto complete")
(defadvice generic-mode-internal
  (after
   generic-mode-with-ac
   (mode comment-list keyword-list font-lock-list function-list)
   activate)
  "Add generic keywords into `ac-generic-keywords'."
  (set (make-local-variable 'ac-generic-keywords)
       keyword-list)
  (setq ac-sources (cons 'ac-source-generic ac-sources)))
(defun ac-generic-candidates ()
  ac-generic-keywords)
(ac-define-source generic
  '((candidates . ac-generic-candidates)
    (symbol . "d")))

おしまい

こんな感じで generic.el の紹介はおしまいです。generic.el は Emacs 初心者さんでも新しいハイライトを作れるし個人的には結構気に入ってる機能の一つです。キーワードファイルから作れたりするともっといいかもしれないですね。

さて、明日は [twitter:@khiker] さんです。おたのしみに。

region を入れかえる trans-regions.el を作った

インストールは以下で。
M-x install-elisp-from-emacswiki trans-regions.el

使い方は簡単。M-x trans-regions ってするだけ。そのあとは以下のように操作する。

  • すでに region を選んでるときは二つめの region を選択する
  • 選んでないときは、最初の region を選択して、次に二つめの region を選択する。

どちらも region を選択したらモードラインに書かれてる通り decide すれば region を決定する。二つの region が決定したら入れかえてくれる。

ごちゃごちゃ書いたけど、使ってみた方が速いと思うんで、興味のある人は試してみて下さいです。

EmacsWiki に swap-region と anchored-transpose っていう region 入れかえ用の elisp が既にあるんだけど、多分その子たちよりは使いやすいと思う。

この手のコマンドを作りたい場合

入れ替えをやるための標準の関数には以下がある

  • transpose-regions
  • transpose-subr

おすすめは transpose-regions の方。こっちの方が断然使いやすい。Emacsの各種入れ替えコマンドは transpose-subr を使ってるけど、使いにくい上にドキュメントが無いからやめた方がいい。ドリューたんも #3249 - 23.0.93; doc of transpose-subr and transpose-regions - GNU bug report logs で嘆いてた。

追記

  • Emacs22をサポートしてみた。

最新のauto-completeを使って思ったこと

twitterでつぶやこうかと思ったんだけど、ここにつらつらと書いてみる

  • popup-menuが案外completing-read の代わりになる。今回のisearch対応でさらにいい感じ。
  • auto-start する source としない source を設定できるといいかも
    • omni 補完する奴は auto-start して ac-source-words-in-* とかは明示的に入れたときに出るとか。
  • やっぱ omni 補完が選択されたときも他の候補が出て欲しい
    • auto-start じゃないときだけでいいかも。ac-source-functions 使ってて思った。
    • 自分の枝で一応は実装してるけど、auto-complete の理念に合うのかが微妙でパッチ送ってない。
  • eclipse みたいに複数のメニュー切り替えられるといいかも。
  • やっぱ action に選択した候補が渡ってくれた方がうれしい。
    • init で開始位置覚えとけばいいんだけど、ソースごとにそれやるのはめんどい。
  • auto-start が nil のときの挙動が微妙に不安定
    • これは自分とこで直してみてる。まだ途中。多分 nil の人はあまりいないだろうし。

今後も期待。

descbinds-anything と vline を更新した。

新しいのを取得するには以下で。

それぞれの変更点はこんな:

  • descbinds-anything
    • Major Mode とかが先にくるようにした。
    • persistent-action 足した。
  • vline
    • vline-visual が有効でも速度出るようになった

追記

  • 2010-01-05 デフォルトアクションが実行できないバグがあったので修正。

ac-companyでdocumentに対応した。

ものは以下からどうぞ。
M-x install-elisp http://github.com/buzztaiki/auto-complete/raw/master/ac-company.el

company backend が doc-buffer か meta をサポートしてればそれを表示する。
あと、ac-company-define-source に引数追加して symbol とか足せるようにしてみた。例えば以下のようにすると、"s"ってシンボルが付いて、候補が100個まで出るようになる。

(ac-company-define-source ac-source-company-elisp company-elisp
                          (symbol . "s")
			  (limit . 100))