原文: http://yasnippet.googlecode.com/svn/trunk/doc/snippet-expansion.html (translated on 2010/Dec/21) ⇒ editor's room

Expanding snippets

〔重要〕 このドキュメントは YASnippet の SVN trunk に対して適用されるものです。 SVN trunk はこちらから取得できます。 他のバージョンに対するドキュメントはこちらで見られます。

展開のトリガ

YASnippet でスニペットを展開するのには、さまざまなやり方があります:

Trigger key

yas/minor-mode が有効になると、yas/trigger-key で与えられるキーバインドが有効になります。

yas/trigger-key で指定されたキーバインドは、関数 yas/expand を呼び出します。 これは、カーソル位置の直前にある スニペット省略形(あるいは snippet key とも呼ばれます)を展開しようとします。

デフォルトのキーは TAB ですが、自由に他のキーに設定することができます。

a sample of mode line

すべてのバッファで YASnippet マイナーモードを有効にするには、yas/global-mode コマンドを使います。

yas/global-mode を使っているが、あるバッファでは YASnippet を無効化したい、というときには、そのバッファのモードのフックでバッファローカル変数 yas/dont-active を設定してください。

yas/trigger-key の利用とその理解にまつわるトラブルは、YASnippet でもっとも頻繁に話題になっているものです。 FAQ を参照してください。

スニペットが展開できなかったときのふるまい

yas/fallback-behavior は設定可能な変数で、デフォルトでは 'call-other-command に割り当てられています。 展開すべきスニペットを yas/expand が見つけられなかった場合、マイナーモードを一時的に無効にして、yas/trigger-key に割り当てられている他のコマンドがないか探します。

もし他のコマンドが見つかった場合、その見つかったコマンドが呼ばれます。 たいていの場合、この仕組みはとてもうまく機能します。 スニペットがあったらそれを展開し、そうでなければ trigger key にもともと割り当てられていたコマンドを呼ぶ、というわけです。

また一方で、変数 yas/fallback-behavior をカスタマイズすることで、この挙動を変えることができます。 この変数の値を 'return-nil に設定すると、スニペットが見つからなかったときにもともとのコマンドを呼ぶ代わりに nil を返すようになります。

カーソル位置への挿入

コマンド M-x yas/insert-snippet は、現在のメジャーモードにしたがって、スニペットをカーソル位置に挿入しようとします。 このコマンドを実行すると、最初に、snippet key (訳注:スニペット省略形)の入力をうながすプロンプトが表示されます。 同じ snippet key に対して複数のテンプレートがある場合、続けてスニペットテンプレートの選択をうながすプロンプトが表示されます。

ここで表示されるリストには、現在のカーソル位置に挿入可能なスニペットが載っています。 どのスニペットが挿入可能なのかは条件判断システム(後述)にしたがいます。 現在のメジャーモードに対して適用可能なすべてのスニペットを見たいときには、コマンドの前に C-u を付けてください。

入力をうながすプロンプトを表示するメソッドは、これもまた、yas/prompt-functions によって制御されています。

スニペットのキーバインド

"Writing Snippets" の章の # binding: ディレクティブの節を参照してください。

メニューからの展開

"the YASnippet Menu" の章を参照してください。

hippie-expand での展開

hippie-expand と統合したい場合、リスト hippie-expand-try-functions-list の中に yas/hippie-try-expand を置くだけです。 ただし、リストの先頭に置いた場合、他の意味も持ってしまうかもしれません。 いずれにしても、あなたの好きな位置に置くことができます。

emacs-lisp コードからの展開

時には、Emacs-Lisp コードから直接、スニペットを展開したいことがあるかもしれません。 そんなときは yas/expand の代わりに yas/expand-snippet を呼ぶようにしてください。

メニューバーから展開するのと同様に、条件判断システムおよび複数候補は展開に影響しなくなります。 実際、YASnippet メニューからの展開は、以下のコードの評価とまったく同じ効果を持っています:

(yas/expand-snippet template)

もっと情報が必要な場合は yas/expand-snippet の内部ドキュメントを参照してください。

展開を制御する

ふさわしいスニペット

現在のカーソル位置に展開できるスニペットはどれか、を見つけ出すために、YASnippet はかなりのフィルタリングをします。

具体的に言うと、以下のものが関係します:

・現在読み込み済みの snippet table

あなたのファイルシステムのディレクトリ階層構造から読みだされる情報です。 "Organizing Snippets" の章を参照してください。 これらのテーブルは、"html-mode" や "ruby-mode" のように、メジャーモードにちなんだ名前を付けられています。

・現在のバッファのメジャーモード

現在のメジャーモードが読み込み済みの snipet table のひとつにマッチした場合、 そのテーブルにあるすべてのスニペットが展開の対象となります。 現在のメジャーモードを確認するためには、M-x describe-variable RET major-mode RET を使ってください。

・Parent table

展開可能な他のテーブルの親として定義されている snippet table もまた、展開可能とみなされます。 この解釈は再帰的に行われます。つまり、展開可能なテーブルの親の親もまた展開可能とみなされます。

・バッファローカルな変数 yas/mode-symbol

この変数を使うと、メジャーモードに一致しない名前の snippet table を展開可能とみなすようにすることができます。 この変数に適当なモード名を設定すると(たとえば rinari-minor-mode とか)、そのモードでしか展開されないスニペットを使うことができます。 もちろん、この設定を条件によって切り替えたいと思うこともあるでしょう(たとえば、あるマイナーモードに入った時にだけ、など)。 そんなときはフックを使うのがよいアイディアです。

;; When entering rinari-minor-mode, consider also the snippets in the
;; snippet table "rails-mode"
(add-hook 'rinari-minor-mode-hook
          #'(lambda ()
              (setq yas/mode-symbol 'rails-mode)))
・バッファローカルな変数 yas/buffer-local-condition

この変数を使うと、現在のバッファ内でどのスニペットが展開可能かをより細かい粒度で制御することができます。 たとえば、デフォルトでは、コメントおよび文字列リテラルの中ではスニペットを展開しないようになっています。 詳細については "The condition system" の節を参照してください。

The condition system

次のシナリオを考えてみてください: あなたはベテランの Emacs ハッカーです。 あなたは省略表記を好んでいて、yas/trigger-key に "SPC" を設定しています。 また一方では、(たとえば python-mode などで)コメントブロックや文字列の中でタイプした "if" はスニペットとして展開してほしくありません。

# condition : ディレクティブ("Writing Snippets" の章を参照)を使っている場合、 if に対する条件として (not (python-in-string/comment)) を指定するだけで済みます。 しかし、whilefor などについてはどうでしょう? すべてのスニペットに対して同じ条件を書くのは本当にうんざりするものです。 そんなわけで、YASnippet はバッファローカル変数 yas/buffer-local-condition を提供しています。 python-mode-hook の中で、この変数に (not (python-in-string/comment)) を設定すればよいのです。

次に、ある特定のスニペットだけはコメントの中でも展開できるようにしたいと思ったら、どうしたらよいでしょう? これも実現可能です! でも、話を進めるのを止めて、以下のルールをご覧ください:

上で想定しているシナリオでは、yas/buffer-local-condition を以下のように設定してください:

(add-hook 'python-mode-hook
          '(lambda ()
             (setq yas/buffer-local-condition
                   '(if (python-in-string/comment)
                        '(require-snippet-condition . force-in-comment)
                      t))))

・・・・また、コメントの中でも展開されてほしいスニペットの # condition: ディレクティブは、シンボル force-in-comment に評価されるようにしてください。 こうすると、コメント中では、if のような他のスニペットは展開されないままで、あなたが展開したいスニペットだけは展開できるようになります。

同じキーに割り当てられた複数のスニペット

上のほうで概要を述べたルールは、現在のカーソル位置に展開可能なスニペットを複数返すことがありえます。

複数の候補が見つかったとき、YASnippet はあなたにひとつ選ばせるでしょう。 複数の候補を選択するためのユーザインタフェースは、yas/prompt-functions でカスタマイズすることができます。 この変数は、スニペットに対する入力プロンプトを表示するメソッドとして、あなたの好きなメソッドを指定するためのものです。

これをカスタマイズするには、M-x customize-variable RET yas/prompt-functions RET としてください。 別のやり方として、あなたの emacs 設定ファイルに以下のコードを書いてください:

(setq yas/prompt-functions '(yas/x-prompt yas/dropdown-prompt))

現在、YASnippet では、以下のいずれかの方法が利用可能です。

X window system を使う

a sample of menu with X window system

関数 yas/x-prompt は、候補選択のためのポップアップメニューを表示します。 このメニューはあなたの使っているウインドウシステムのネイティブな部品 (widget) です。 すなわち:

ミニバッファでのプロンプト表示

a sample of minibuffer prompt with ido

昔ながらの emacs の補完メソッドのための関数 yas/completing-prompt が使えます。 または、これよりずっと見た目のよい yas/ido-prompt も使えます。 一番いいのは実際に試してみることです。 この機能はターミナル上でも使えます。

dropdown-menu.el を使う

a sample of dropdown prompt

関数 yas/dropdown-promptyas/prompt-functions リストに書くことができます。

これはウインドウシステム上でもターミナル上でも機能し、カスタマイズ可能で、C-nC-p で操作でき、 q で quit でき、6番目の候補を選択するのに 6 を押すこともできます。

自作する

変数 yas/prompt-functions のドキュメントを参照してください。

カスタマイズ可能な変数

yas/prompt-functions

関数を書いて、それをリスト yas/prompt-functions に追加することができます。 このリストに含まれている関数は、以下の引数で呼ばれます:

リスト yas/prompt-functions に含めるすべての関数の返値は、CHOICES に含まれているオブジェクトのどれかでなければなりません。 また、(DISPLAY-FN 引数が渡されたときには)DISPLAY-FN で適切に整形されたものでなければなりません。

yas/fallback-behavior

yas/expand がスニペットを展開しなかったとき、どう振る舞えばよいか。

call-other-command は、一時的に YASnippet を無効にして、yas/trigger-key に割り当てられている次のコマンドを呼び出すことを意味します。

return-nil は nil を返す(つまり、何もしない)ことを意味します。

(apply COMMAND .ARGS) という形式のエントリは、 もし ARGS が non-nil だった場合は ARGS を引数として非対話的に COMMAND を呼び出し、 そうでなければ対話的に COMMAND を呼び出すことを意味します。

yas/choose-keys-first

もし non-nil だった場合、最初に snippet key の入力プロンプトを表示し、次にテンプレートに対するプロンプトを表示します。

そうでなければ、すべての展開可能なスニペット名に対するプロンプトを表示します。

この変数は、yas/insert-snippetyas/visit-snippet-file に影響を与えます。

yas/choose-tables-first

もし non-nil だった場合で、かつ、複数の snippet table が適用可能と判定された場合、snippet table を選択するプロンプトを最初に表示します。

それ以外の場合、すべての適用可能な snippet table を合体したものからユーザがスニペットを選択することになります。

この変数は、yas/insert-snippetyas/visit-snippet-file に影響を与えます。

yas/key-syntaxes

デフォルトの検索の戦略は、きわめて強力です。 たとえば、c-mode において、barfoo_bar"#foo_bar" は、いずれも snippet key として認識されます。 さらに、検索はこの順序で行われます。 つまり、bar がある展開可能なスニペットへの key であることがわかったとき、そのスニペットが展開されて bar を置き換えます。 このとき、foo_bar"#foo_bar" を key とするスニペットは、展開可能とはみなされません。

一方で、この戦略は、yas/key-syntaxes 変数によって容易にカスタマイズできます。 設定するのは、構文ルールのリストです。 そのデフォルトの値は ("w" "w_" "w_." "^ ") です。 これは、以下のものを snippet key が見つかるまで検索する、ということを意味します:

ただし、Emacs の構文ルールがどのように機能しているのか理解したいと思わないなら、デフォルトの値のままにしておくのがベターでしょう・・・・。