VS Codeのスニペット自作入門|本体はタブストップ設計だった

VS Codeのスニペット自作入門|本体はタブストップ設計だった

毎回同じ import を手打ちしてる。if __name__ == "__main__": を今週何回書いたか数えたくない。ファイルの頭に書くお決まりのコメント、React の関数コンポーネントの枠、そういう「考えなくても書ける数行」に、毎回ちゃんと打鍵とタイポのリスクを払ってる。

それ、全部スニペットにできる。

目次 / INDEX

スニペットは何が嬉しいのか

スニペットは「短い合図の文字を打つと、決めておいた定型コードが展開される」機能。それだけ聞くと単なる打鍵の節約に見えるけど、本当の旨みは別のところにある。

事故が減るんだ。

定型コードの怖さは、手が覚えてるせいで脳がサボること。__main__ のアンダースコアを1本落とす、閉じ括弧を忘れる、ヘッダコメントの日付を先週のままコピペする。どれも「気づくのが5分後」系のミスで、地味に時間を食う。

スニペットなら展開される中身は毎回一字一句同じだから、この種のミスが構造的に起きない。

3分で作る最初の1個

作り方は簡単。コマンドパレット(Windows/Linux は Ctrl+Shift+P、Mac は Cmd+Shift+P)を開いて「Snippets: Configure Snippets」を実行する。メニューの基本設定(Preferences)から「ユーザースニペットの構成」でも同じ場所に行ける。

選ぶと3種類出てくる。

  1. 言語別スニペット(例: python.json)。その言語のファイルでだけ有効
  2. 新しいグローバルスニペットファイル(.code-snippets)。scope で言語を絞れる
  3. このワークスペース用

最初は言語別かグローバルで十分。ワークスペース用はチーム開発とかプロジェクト固有の型ができてからでいい。

中身は JSON。キーがスニペットの名前で、その中に3つ書く。

  • prefix: 呼び出す文字列。配列で複数指定もできる
  • body: 挿入される中身。文字列の配列で、各要素が1行になる
  • description: 補完候補に出る説明

Python の main ガードなら、python.json にこう書く。

{
  "main guard": {
    "prefix": "ifmain",
    "body": [
      "if __name__ == \"__main__\":",
      "\tmain()"
    ],
    "description": "if __name__ == \"__main__\": の定型"
  }
}

body の中の " は \"、タブは \t とエスケープする。保存したら、エディタで ifmain と打つと補完候補に出て、Tab か Enter で展開される。

これだけ。3分かからない。

あと、補完候補の並びで他の候補に埋もれるのが気になるなら、設定の editor.snippetSuggestions を top にすると自作スニペットが上に来る。

本体はタブストップ。「埋める順番」を自分で設計する

ここからが本題。単なる文字展開なら、正直テキスト置換ツールでもできる。VS Code のスニペットが強いのは、展開した後の「カーソルがどこに飛ぶか」まで設計できるところ。

body の中に $1 $2 $3 と書いておくと、展開後に Tab を押すたび、その番号の順でカーソルが飛ぶ。$0 が最後の着地点。

つまり「この定型で、人間が埋めるべき穴はここと、ここ。埋める順番はこう」というのをコードの型と一緒に定義できる。

React の関数コンポーネントで例を出す。

{
  "React functional component": {
    "prefix": "rfc",
    "body": [
      "export function ${1:ComponentName}({ $2 }) {",
      "\treturn (",
      "\t\t<div>$0</div>",
      "\t);",
      "}"
    ],
    "description": "関数コンポーネントの枠"
  }
}

rfc で展開すると、まずコンポーネント名(${1:ComponentName})が選択状態で待ってる。${1:デフォルト値} の形式はプレースホルダといって、仮の文字が入った状態で選択されて飛ぶから、そのまま打てば上書きされる。

名前を打って Tab、次は props の穴($2)、また Tab で中身($0)に着地。枠を書く作業が消えて、埋める作業だけが順番に流れてくる。

同じ番号を2箇所に書くともっと面白い。例えば $1 を関数名と docstring(関数の説明文)の2箇所に置くと、片方を打てばもう片方も同時に変わる(ミラーという)。名前の書き間違いがそもそも起きない。

選択肢も出せる。${1|public,private,protected|} のように | で囲んでカンマ区切りにすると、展開時にドロップダウンが出て選ぶだけになる。タイポの余地がない。

日付やファイル名は変数で自動化する

穴を人間が埋める必要すらないものもある。日付とかファイル名がそれで、変数を書いておけば展開時に勝手に入る。

  • $TM_FILENAME: 今のファイル名。$TM_FILENAME_BASE なら拡張子なし
  • $CURRENT_YEAR / $CURRENT_MONTH / $CURRENT_DATE: 今日の日付
  • $CLIPBOARD: クリップボードの中身
  • $TM_SELECTED_TEXT: 選択中のテキスト。選択してから展開すると、そのテキストを型で囲める

ファイルヘッダのコメントに使うとこうなる。

{
  "file header": {
    "prefix": "hdr",
    "body": [
      "# $TM_FILENAME_BASE",
      "# created: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE",
      "# $1",
      "$0"
    ],
    "description": "日付入りファイルヘッダ"
  }
}

ヘッダの日付が先週のままコピペされる事故、これで防げる。ファイル名も日付も自動で入って、人間が書くのは説明の1行($1)だけ。

ちなみに $ を文字としてそのまま出したいときは \$ とエスケープする(JSONの中に書くので、実際には \\$ と打つ)。シェルスクリプトの定型を作るときに引っかかりやすいので覚えておくといい。

どれをスニペットにすべきか。判断の型

作り方が分かると何でもスニペットにしたくなるけど、それは罠だと思う。prefix が増えるほど「あれ、なんて名前で登録したっけ」が起きて、思い出すコストが打鍵の節約を食い潰す。

僕の判断の型は3条件。

  1. よく書く(週2回以上が目安)
  2. 毎回ほぼ同じ(変わる部分が2〜3箇所以内)
  3. 手打ちすると事故る(タイポや埋め忘れの実害が出たことがある)

この3つが揃う定型だけ登録する。逆に、たまにしか書かないもの、毎回中身が大きく変わるものは登録しない。

前者は忘れるし、後者はそもそもスニペット向きじゃない。そういうのは次の話に繋がる。

AI補完との棲み分け

「AI補完があるならスニペット要らなくない?」とは僕も思った。でも使い分けてみると、この2つは競合じゃなくて役割が違う。

AI補完は、文脈から「今書きたいもの」を推測してくれる。前後のコードを読んで、その場に合った続きを提案する。強いのは、毎回中身が変わる文脈依存の処理。

その代わり、出てくるものは推測だから毎回微妙に揺れるし、提案を読んで正しいか確認する工程が必ず挟まる。

スニペットは推測しない。自分が決めた型を、確実に、一発で、一字一句同じに出す。確認が要らない。

だから切り分けはこうなる。ゆらぎのない定型はスニペット、その場の文脈依存はAI。「この数行は毎回絶対にこの形」と自分で言い切れるものだけスニペットに寄せて、残りはAIに任せる。

判断の主導権が自分にあるのがスニペット、AIにあるのがAI補完、と言ってもいい。

打鍵を減らす話は他にも書いてる。VS Codeのキーバインドを最小限だけ自作する話も同じ発想の続きなので、環境を整えたい人はどうぞ。拡張と settings.json の基本はVS Codeの拡張と設定の記事にまとめてある。

まとめ

スニペットの作り方自体は3分で覚えられる。Configure Snippets を開いて、prefix と body を書くだけ。

でも本体はそこじゃなくて、$1 $2 $0 で「埋める穴と順番」を自分で設計するところにある。型は機械が出し、人間は穴だけ埋める。この分業にしておくと、定型の書き間違いはほぼ起きなくなる。

まずは1個でいい。今週2回以上コピペした数行を思い出して、それをスニペットにしてみてほしい。3分の投資で、その定型を手で打つことはなくなる。

よくある質問

VS Codeで自作スニペットを作るには?

コマンドパレットで「Snippets: Configure Snippets」を実行し、言語別(python.jsonなど)かグローバル(.code-snippets)を選ぶ。JSONにprefix(呼び出す文字)・body(挿入される行の配列)・descriptionを書いて保存すれば、prefixを打った時に補完候補に出る。

スニペットの$1や$0は何?

タブストップ。展開後にTabを押すたび$1、$2の順にカーソルが飛び、$0が最後の着地点になる。${1:ComponentName}のように書けば仮の文字が入った状態で選択される(プレースホルダ)。

スニペットとAI補完はどう使い分ける?

毎回一字一句同じでいい定型はスニペット、前後の文脈で中身が変わる処理はAI補完。スニペットは推測せず確実に同じものを出すので、提案を読んで確認する工程が要らない。

次に読むなら

VS Codeのキーバインドカスタムは3つでいいスニペットの次は、よく使う操作をキーに割り当てる読む →