## 事象
[[nvim-cmp]]にて[[LuaSnip]]の `expand_or_jump` コマンドを登録している。
```lua
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")
require("luasnip.loaders.from_snipmate").lazy_load()
cmp.setup({
-- 中略
mapping = cmp.mapping.preset.insert({
["<Tab>"] = cmp.mapping(function(fallback)
if luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { "i", "s" }),
```
`markdown.snippets` で登録された以下のスニペットに対し
```lua
snippet _
- [ ] ${1}
```
以下のように、スニペット挿入し、移動して、[[挿入モード]]に入り、`<Tab>` を押すと、`${1}` の場所に飛んでしまう。
![[2024-12-30-15-57-40.webm]]
## 原因
### `expand_or_jumpable` を使って かつ `${1}` にしているから
`expand_or_jumpable` は、今もプレイスホルダーを順に移動するべきかを確認するコマンド。`${1}` は `<Tab>` 移動時の位置を示すindexであり、`${0}` に到達するまでの間は上記は `true` と見なされる。
`${0}` が明示的に指定されていない場合は、スニペットの最後が `${0}` とみなされるので、一度は `<Tab>` を押さなければ `expand_or_jumpable` は `false` にならない。
## 解決方法
`expand_or_locally_jumpable` を使う。このコマンドは **カーソル位置がスニペットの外に出ている場合は `false` を返す** ので、関係ないところで `<Tab>` を押したときに予期せぬ移動が発生するリスクは軽減される。
```diff
mapping = cmp.mapping.preset.insert({
["<Tab>"] = cmp.mapping(function(fallback)
- if luasnip.expand_or_jumpable() then
+ if luasnip.expand_or_locally_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { "i", "s" }),
```
あわせて、`<Tab>` による移動が不要な場合は `${1}` ではなく `${0}` を指定するようにしたほうがよい。
```lua
snippet _
- [ ] ${0}
```
プレースホルダーが複数存在する場合も、最後に `${0}` を入れておいたほうが安全。
```lua
snippet _
- [ ] [${1}](${0:URL})
```
## 参考
- [LuaSnip/DOC\.md at master · L3MON4D3/LuaSnip](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#api-2)