yasnippet 8.0の導入からスニペットの書き方、anything/helm/auto-completeとの連携

yasnippet 8.0の導入からスニペットの書き方、anything.elとの連携

yasnippet 7.0を使っていましたが、8.0が出たということで変えてみました。

ほとんど変わっている様子はなさそうなので、古いドキュメントから要約してまとめています。

次の環境で進めましたが、EmacsならWindowsでもLinux,Macでもほとんど同じだと思います。

  • Mac OSX Lion 10.7.4
  • Cocoa Emacs 24.1
  • github capitaomorte/yasnippet 8.0

yasnippet導入

capitaomorte / yasnippet

githubにおいてあるものが最新版です。現時点での最終更新日は2012年8月22日です。

まずはelispを置くディレクトリ(私の場合はホームの.emacs.d/elispに置いています)に移動して、ダウンロードします。


cd ~/.emacs.d/elisp
git clone https://github.com/capitaomorte/yasnippet.git

cygwinの場合ssl関係でエラーが出るかもしれないので、https部分をgitに変えてみてください。普通にダウンロードして解凍するのもありです。

emacsの設定ファイル(.emacs, init.el)に以下を書きます。

yasnippet8.0は7.0と関数名が微妙に違うので注意。


(require 'cl)
;; 問い合わせを簡略化 yes/no を y/n
(fset 'yes-or-no-p 'y-or-n-p)

;; yasnippetを置いているフォルダにパスを通す
(add-to-list 'load-path
             (expand-file-name "~/.emacs.d/elisp/yasnippet"))
(require 'yasnippet)
;; ~/.emacs.d/にsnippetsというフォルダを作っておきましょう
(setq yas-snippet-dirs
      '("~/.emacs.d/snippets" ;; 作成するスニペットはここに入る
        "~/.emacs.d/elisp/yasnippet/snippets" ;; 最初から入っていたスニペット(省略可能)
        ))
(yas-global-mode 1)

;; 単語展開キーバインド (ver8.0から明記しないと機能しない)
;; (setqだとtermなどで干渉問題ありでした)
;; もちろんTAB以外でもOK 例えば "C-;"とか
(custom-set-variables '(yas-trigger-key "TAB"))

;; 既存スニペットを挿入する
(define-key yas-minor-mode-map (kbd "C-x i i") 'yas-insert-snippet)
;; 新規スニペットを作成するバッファを用意する
(define-key yas-minor-mode-map (kbd "C-x i n") 'yas-new-snippet)
;; 既存スニペットを閲覧・編集する
(define-key yas-minor-mode-map (kbd "C-x i v") 'yas-visit-snippet-file)

Emacsを再起動して反映します。

これで最初に入っていたスニペットを使えるようになりましたが、スニペット呼び出しはこのままでは不便ですのでanything/helm.elの力を借ります。

anything/helmとの連携

anything.elを導入。

anything導入のeverything ~3分で使えるanything.el~

連携にはこちらのelispを使わせていただきました。(;; anything interfaceの部分)

dotfiles / .emacs.d / conf / 04-yasnippet.el

ちなみにanythingという文字をすべてhelmに置換すればhelm.elでも動きます。

Emacsを再起動します。

これでスニペット挿入([C-x i i] or M-x yas-new-snippet)をしてみてください。anythingインターフェースで挿入したいスニペットを選択できるはずです。

しかし、閲覧・編集([C-x i v] or M-x yas-visit-snippet-file)はanythingでは出てこないので、yasnippet.elの2371行目yas-visit-snippet-file関数中の(yas-prompt-functions '(yas-ido-prompt yas-completing-prompt))をコメントアウトします。一時的なプロンプト変更処理を抑制するためです。これでanythingインターフェースに対応します。

auto-completeとの連携

auto-complete.elの1.4とyasnippet.el 0.8は連携が可能なようです。

ソースはこちらauto-complete.el 1.4

elispを置くフォルダに移動して、gitコマンドで取得・展開します。


cd ~/.emacs.d/elisp
git clone https://github.com/auto-complete/auto-complete.git
cd auto-complete
git submodule update --init

設定の一例


;;; パスを通す
(defvar ac-dir (expand-file-name "~/.emacs.d/elisp/auto-complete"))
(add-to-list 'load-path ac-dir)
(add-to-list 'load-path (concat ac-dir "/lib/ert"))
(add-to-list 'load-path (concat ac-dir "/lib/fuzzy"))
(add-to-list 'load-path (concat ac-dir "/lib/popup"))

;;; 適用するメジャーモードを足す
(add-to-list 'ac-modes 'scss-mode)
(add-to-list 'ac-modes 'web-mode)
(add-to-list 'ac-modes 'coffee-mode)

;;; ベースとなるソースを指定
(defvar my-ac-sources
              '(ac-source-yasnippet
                ac-source-abbrev
                ac-source-dictionary
                ac-source-words-in-same-mode-buffers))

;;; 個別にソースを指定
(defun ac-scss-mode-setup ()
  (setq-default ac-sources (append '(ac-source-css-property) my-ac-sources)))
(defun ac-web-mode-setup ()
  (setq-default ac-sources my-ac-sources))
(defun ac-coffee-mode-setup ()
  (setq-default ac-sources my-ac-sources))
(add-hook 'scss-mode-hook 'ac-scss-mode-setup)
(add-hook 'web-mode-hook 'ac-web-mode-setup)
(add-hook 'coffee-mode-hook 'ac-coffee-mode-setup)

(global-auto-complete-mode t)

;;; C-n / C-p で選択
(setq ac-use-menu-map t)


;;; yasnippetのbindingを指定するとエラーが出るので回避する方法。
(setf (symbol-function 'yas-active-keys)
      (lambda ()
        (remove-duplicates (mapcan #'yas--table-all-keys (yas--get-snippet-tables)))))

基本的なスニペットの書き方チュートリアル

test.htmlを用意して開きます。

C-x C-f test.html

htmlファイルなので、メジャーモードがhtml-modeだとします。

C-x i n で *new snippet* バッファが開きます。


# -*- mode: snippet -*-
# name:
# key:
# binding: direct-keybinding
# expand-env: ((some-var some-value))
# type: command
# --

1行目はsnippet-modeを自動で適用させるものです。# --は区切りで、これ以前はコメントができます。以下のようにしてみましょう。


# -*- mode: snippet -*-
# name: footer_tag
# key: fo
# binding: C-c M-l
# コメント例
# これはfooterタグです。
# --
<footer>
  $0
</footer>

書き終わったら[C-c C-c]で反映させます。(snippet-modeの時のみ有効)

保存するメジャーモードを選択します。html-modeのままでいいので空白でRET(Enter)
Choose or enter a table (yas guesses html-mode):

yを押します。
[yas] Looks like a library or new snippet. Save to new file? (y or n)

フォルダを作成していない初回のみ問われます。yを押します。
Guessed directory (~/.emacs.d/snippets/html-mode) for table "html-mode" does not exist! Create? (y or n)

ファイル名を問われます。初期値にnameに設定した文字がでます。そのままRET
File name to create in ~/.emacs.d/snippets/html-mode? footer

~/.emacs.d/snippets/html-mode/footerという感じでスニペットが出来ました。

test.htmlに戻って、「fo」とどこかに入力しoの右側にポインタ(カーソル)を置いてTABを押します。


<footer>

</footer>

footerタグが展開され、ポインタがタグの間、スニペットで$0の位置に来ました。

foと入力しTABを押さなくても、スニペット作成時に指定したキーバインド[C-c M-l]でも展開可能です。

C-x i i のスニペット挿入で普通に呼び出すこともできます。名前がうろ覚えでも、anythingで探せるので名前はしっかり付けましょう。

keyとbindingはどちらも省略可能です。bindingはキーバインド資源を消費し、誤作動も招くので、よほど頻繁に使うものでない限りは控えましょう。

展開後のポインタ位置を複数指定する

先ほどは$0の位置にポインタが来ましたが、これを複数指定してTABでぴょんぴょん移動できるようになります。


# -*- mode: snippet -*-
# name: img_tag
# key: img
# --
<img src="$1" width="$2" height="$3">$0

これを展開した場合、最初にポインタは$1の位置に現れ、そのまま入力することができます。入力が終わったらTABを押し、次の$2の位置へジャンプします。

$0は最終的に行き着く場所で、省略した場合はポインタは最後に末尾にいきます。

また、TAB移動は途中でハイライト外の位置に移動するか、C-gで解除でき、その後通常通りに編集できます。

プレースホルダまたは初期値を指定

いきなりポインタ位置に飛ばされても、ここ何入力するんだったっけとなる場合もあります。もしくは必要か不要の2択である場合。


# -*- mode: snippet -*-
# name: inline_style_tag
# key: style
# binding: C-c s s
# --
<style${1: type="text/css"}>
  $0
</style>

これを展開すると、「 type="text/css"」がハイライトされTABを押すとそれを残しつつ$0の位置へ。C-dを押すとハイライト部分を削除して$0の位置へ移動します。ハイライト中に文字を入力すればその文字で上書きされます。ヒントを書いておくのもありですね。

入れ子もできちゃいます。


# -*- mode: snippet -*-
# name: section_tag
# key: section
# --
<section${1: id="${2:id_name}"}${3: class="${4:class_name}"}>
  $0
</section>

例えばsectionのid付きだけ欲しい場合は、展開>TABでid編集を選択>id名入力>C-dでクラス省略という流れになります。id,class不要な場合は、展開>C-d>C-d。

同じ文字を複数の場所に書きたい

ミラーリングというやつです。試しにコメントブロックを作ってみます。


# -*- mode: snippet -*-
# name: comment_wrap
# key: cw
# --
<!-- ----------  $1 Start   ---------- -->
$0
<!-- ----------  $1 End     ---------- -->

1個目の$1に最初フォーカスし入力すると同時に、2個目もシンクロ状態で入力されます。

先ほどのプレースホルダーと組み合わせることも可能です。


# -*- mode: snippet -*-
# name: comment_wrap2
# key: cw2
# --
<!-- ----------  ${1:comment} Start   ---------- -->
$0
<!-- ----------  $1 End     ---------- -->

選択候補をリストアップする

選択候補が予め決まっている場合に有用です。CSSを例にとってみます。


# -*- mode: snippet -*-
# name: position
# key: pos
# --
position: ${1:$$(yas-choose-value '("relative" "absolute" "fixed" "static"))};

展開すると、4つの選択肢がanythingインターフェースで出てきます。これまた便利ですね。

yasnippet 7.0では関数名がyas/choose-valueでしたが、どちらでも動くようです。

挿入時インデントがずれる対策

スニペット挿入時にメジャーモードによってインデントが自動調整されることがあります。

対策は簡単です。# expand-env: ((yas/indent-line 'fixed) (yas/wrap-around-region 'nil))を加えるだけです。

おおげさにインデントを広くしてみました。

アスキーアートのスニペット作るには必須です。


# -*- mode: snippet -*-
# name: footer
# key: fo
# expand-env: ((yas/indent-line 'fixed) (yas/wrap-around-region 'nil))
# --
<footer>
        $0
</footer>

このオプションはよく使うので、snippet-modeのスニペットとして登録しておきましょう。保存時にsnippet-modeにすることをお忘れなく。


# -*- mode: snippet -*-
# name: indent_for_snippet
# key: indent
# --
# expand-env: ((yas/indent-line 'fixed) (yas/wrap-around-region 'nil))

もしくは*new snippet*呼び出し時のテンプレートを後述のようにあらかじめ設定しておきましょう。

emacs lispを使う

バッククオートの間にemacs lispを記述します。例として現在のディレクトリを取得してみます。

鬼Elipserはおそらくこれを使いこなしているでしょう。


# -*- mode: snippet -*-
# name: echo_directory
# key: dir
# --
このファイルのディレクトリは
`(file-name-directory (buffer-file-name))`です。

全部大文字にする

elispとyasnippetのyas-textを使い通常入力をすべて大文字に変えます。


# -*- mode: snippet -*-
# name: caps_lock
# key: cap
# binding: C-c a p
# --
${1:caps_lock$(upcase yas-text)}

CAPS_LOCKという大文字が展開され、それに続く文字も大文字になります。


# -*- mode: snippet -*-
# name: caps_lock
# key: cap
# binding: C-c a p
# --
${1:caps_lock$(upcase yas-text)}

デフォルト文字を残したくない場合は空欄にして、$を$$に変えます。


# -*- mode: snippet -*-
# name: caps_lock
# key: cap
# binding: C-c a p
# --
${1:$$(upcase yas-text)}

$[0-9]と`の出力はエスケープが必要

$[0-9]やバッククオート"`"はyasnippetでは特別な記号で、文字として出力するにはエスケープします。

JavaScriptのライブラリなんかで、$1や$2などの正規表現が使われている場合は注意が必要ですね。


# -*- mode: snippet -*-
# name: dollar_one_two
# key: dol
# --
dollar_one : \$1
dollar_two : \$2
back_quote : \`

$1と$2にポインタは飛ばず、そのまま$1,$2が表示されます。

メジャーモード違うけど共有したい

例えば、scssとcssファイルを扱うけど、どちらもスニペットにほとんど差がないので使いまわしたいという場面です。

最初の設定に指定した~/.emacs.d/snippetsフォルダのなかにcss-modeとscss-modeというフォルダがあります。無ければ作ります。

あとは、scss-modeフォルダの中に「.yas-parents」というファイルを作ります。そのファイル内容は「css-mode」1行のみ(カギ括弧は必要ありません)を記入しEmacsを再起動します。

ようするに親(ここではscss-mode)が子(css-mode)のスニペットファイルを扱えるってことです。.yas-parentsファイルに半角スペースまたは改行で区切れば子のモードを複数扱えます。全メジャーモードで共通のスニペットをつくるなんてことも可能ですね。

次のような場合に注意が必要です。css-modeフォルダの.yas-parentsファイルにhtml-modeが記述されていて、かつhtml-modeフォルダの.yas-parentsファイルにcss-modeが書いてあるときは確実に悪影響がでます。どっちが親か分からなくなりますよね。無限ループが起きてるのでしょうか。

もう一つ注意ですが、新規スニペットを作成する際にスニペット保存先は現在のメジャーモードになっています。Choose or enter a table (yas guesses css-mode):と聞かれたときに変更しておきましょう。間違えた場合は後からファイルを移して再起動すれば事足ります。

注意: .yas-parentsファイルに矛盾が生じると次のようなエラーが出ます。


Error in post-command-hook (yas-global-mode-check-buffers): (error "Lisp nesting exceeds `max-lisp-eval-depth'")

同じメジャーモードの中で分類したい

デフォルトで用意されているhtml-modeフォルダ(~/.emacs.d/elisp/yasnippet/html-mode)がいい例です。フォルダ中に「.yas-make-groups」という空ファイルと、header,list,meta,tableといったフォルダが存在します。

.yas-make-groupsが存在する場合、html-modeフォルダ内に存在するフォルダ下のスニペットファイルが1グループとして見なされるようになります。

anythingインターフェースを使うときは役に立ちませんが、M-x yas-describe-tablesや上部メニューからGUI(マウスを使用)でスニペットを開く場合に有用です。

name,key,bindingを一覧で確認する

M-x yas-describe-tables でスニペット名とその展開キー、キーバインド一覧が閲覧できます。

呼び出し時のテンプレートを変更する

名前、キー、インデントをあらかじめ指定しておくと便利です。


(custom-set-variables '(yas-new-snippet-default "\
# -*- mode: snippet -*-
# name: $1
# key: ${2:${1:$(yas--key-from-desc yas-text)}}
# expand-env: ((yas/indent-line 'fixed) (yas/wrap-around-region 'nil))
# --
$0"))

私のスニペットを一部公開

整理できたSCSSとCSSだけです。他はカオスなので(笑)

ShingoFukuyama / css-scss-yasnippet

参考

ちょっと古いですが、これ以降ないです(><)
YASnippet Document 0.6.1c