yasnippetのanythingインターフェイス

を書いてみました。


今日書いたばかりで、ほとんど使っていない状態なので不具合等残っていると思いますがとりあえず現段階のコードを書いておきます。
もし使ってくださる方がいましたらツッコミ歓迎です。

(require 'cl)
(require 'anything)
(require 'yasnippet)

(defvar anything-c-yas-snippets-alist nil)
(defadvice yas/load-directory-1 (around anything-yas-build-alist activate)
  (let* ((directory (ad-get-arg 0))
         (mode-sym (intern (file-name-nondirectory directory)))
         (snippets nil))
    (with-temp-buffer
      (dolist (file (yas/directory-files directory t))
        (when (file-readable-p file)
          (insert-file-contents file nil nil nil t)
          (push (cons (file-name-nondirectory file)
                      (yas/parse-template))
                snippets))))
    (push `(,mode-sym . ,snippets) anything-c-yas-snippets-alist)
    ad-do-it))

(defun anything-c-yas-get-cur-context ()
  "Return list (initial-input point-start point-end)"
  (let ((start (point))
        (end (point))
        (syntax "w_"))
    (condition-case nil
        (save-excursion
          (skip-syntax-backward syntax)
          (setq start (point))
          (values (buffer-substring-no-properties start end) start end))
      (error (values "" (point) (point))))))

;;; nameがないものは対象にならない
(defun anything-c-yas-complete ()
  (interactive)
  (multiple-value-bind (yas-initial-input yas-point-start yas-point-end)
                       (anything-c-yas-get-cur-context)
    (let* ((yas-snippets (assoc-default major-mode anything-c-yas-snippets-alist 'eq))
           (tmpls nil)
           (transformed-alist nil)
           (cands (loop for snippet in yas-snippets
                        for name = (nth 2 snippet)
                        and template = (nth 1 snippet)
                        when (and (not (null template))
                                  (not (null name))
                                  (stringp template)
                                  (string-match (concat "^" (regexp-quote yas-initial-input)) name)) ;今のところnameで絞り込む
                              collect template into templates
                        when (and (not (null name)) (stringp name)) collect `(,name . ,template) into loop-alist
                        finally (setq tmpls templates
                                      transformed-alist loop-alist)))
           (source `((name . "completions")
                     (candidates . ,tmpls)
                     (candidate-transformer
                      . (lambda (candidates) (loop for template in candidates
                                                   for transformed = (rassoc template transformed-alist)
                                                   unless (null transformed) collect transformed)))
                (action . (("insert" . (lambda (tmlp)
                                         (yas/expand-snippet yas-point-start yas-point-end tmlp)
                                         (message "in %s this snippet is bound to [ %s ]"
                                                  major-mode
                                                  (car (rassoc-if (lambda (ls) (equal tmlp (car ls))) yas-snippets))))))))))
      (let ((anything-sources (list source)))
        (anything)))))

(provide 'anything-c-yasnippet)

使い方は、上のコードを anything-c-yasnippet.el としてパスの通った場所に置き、emacsの設定ファイルの中でyas/load-directory でsnippetを読み込む前に (require 'anything-c-yasnippet) をすればOKだと思います。
自分のyasnippetの.設定は今のところこんな感じです。
モードによってはマイナーモードと被るかもしれないので、global-set-keyの部分はお好みのキーバインドで。

(require 'yasnippet)
(require 'anything-c-yasnippet)
(yas/initialize)
(yas/load-directory "path/to/snippets/")
(add-to-list 'yas/extra-mode-hooks 'ruby-mode-hook)
(global-set-key (kbd "C-c y") 'anything-c-yas-complete)

ガストで書きました。かなり強引な部分もあってあんまり綺麗じゃないです。
clを使っています。でもanything.elでrequireされているので問題ないと思います。
むしろ個人的にはcommon lispも興味があるので積極的に使っています。

わからない部分や疑問など、

  • このコードの中でも書いちゃってるんだけど、yas/load-directory-1で文字列からシンボルを得るときにobarrayにinternするのは大丈夫なのかどうか…(make-vector) してそこにinternする方法を考えたほうが良いのか?
  • anything-sourcesを一時的に変えて呼ぶときはletの中で(anything)でよいのかunwind-protectで囲むべきなのか
  • prefixは anything-c-yas-* 使ってるけど適切かな。

楽しいなー、 emacs++

追記

(require 'anything) が抜けていたので追加しました。