夜の21時過ぎ、「どうしてもEmmetのカスタムスニペットが動かない」という状態で、Claude Codeとひたすらやりとりしていました。

tactext-align: center)やdgdisplay: grid)を1発で展開したくて。JetBrainsを使っていた頃からずっとこのあたりのEmmet略語がなくてモヤモヤしていたんですよね。Neovimに移行してからやっとカスタムスニペットで実装できそうと思ったんですが、これがぜんぜん効かない。

p2eみたいな既存の略語は動くのに、自分で追加したものが候補に出てこない。

Googleで調べても古いバージョン向けの書き方ばかりで、「これで動きます」と書いてあっても動かない。Claude Codeもあれこれ試してくれるんですが上手くいかず、最終的にはプラグインのソースコードを漁りだしていました。

そして「出来た!」……というか、既に出来てたんですが出来てないと思い込んでいただけでした。

補完候補の一番下の方に出ていて、スクロールするとやっと表示される状態だったんです。tacを入力すると他の候補(tableとかtext-alignとか)が先に並んで、ずっとスクロールしないと出てこない。なので「適用されていない」と完全に勘違いしていました。

設定自体は最初から効いていた、というオチです。


設定方法(正解ルート)

せっかくなので、正しく動いている設定を残しておきます。

1. スニペットファイルを作る

~/.config/nvim/emmet-snippets/snippets.json を作成します。

{
  "css": {
    "snippets": {
      "tac": "text-align: center;",
      "dg": "display: grid;"
    }
  }
}

ポイントは snippets キーのラッパーが必要な点。これを忘れると読み込まれません。

2. emmet_language_server に読み込ませる

lsp.luaemmet_language_server 設定に init_options を追加します。

vim.lsp.config("emmet_language_server", {
  capabilities = capabilities,
  filetypes = { ... },
  init_options = {
    extensionsPath = { vim.fn.stdpath("config") .. "/emmet-snippets" },
  },
})

変更後はNeovimの完全再起動が必要です(設定リロードでは読み込まれない)。

3. blink.cmp でカスタムスニペットを優先表示させる

これをやらないと今回みたいに候補の最下部に埋もれます。

lsp = {
  name = "LSP",
  module = "blink.cmp.sources.lsp",
  score_offset = 5,
  transform_items = function(_, items)
    for _, item in ipairs(items) do
      if item.detail == "Emmet Custom Snippet" then
        item.score_offset = (item.score_offset or 0) + 10
      end
    end
    return items
  end,
},

emmet_language_server がカスタムスニペットに detail = "Emmet Custom Snippet" を付けてくれるので、それを使って優先度を上げます。


これでやっとtacが候補の上の方に出てくるようになりました。

JetBrainsを使っていた頃はこの辺の略語が出せなくて「まぁEmmetのデフォルトで妥協するか」とずっと諦めていたんですよね。Neovim移行してからこの部分だけはJetBrainsより使いやすくなりました。最初に「できた!」と思った瞬間、思わず「やったー」とひとりごとを言っていました。