mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
feat(snippet): set snippet keymaps permanent instead of dynamic (#31887)
Problem: Given that `vim.snippet.expand()` sets temporary `<tab>`/`<s-tab>` keymaps there is no way to build "smart-tab" functionality where `<tab>` chooses the next completion candidate if the popup menu is visible. Solution: Set the keymap permanent in `_defaults`. The downside of this approach is that users of multiple snippet engine's need to adapt their keymaps to handle all their engines that are in use. For example: vim.keymap.set({ 'i', 's' }, "<Tab>", function() if foreign_snippet.active() then return "<Cmd>lua require('foreign_snippet').jump()<CR>" elseif vim.snippet.active({ direction = 1 }) then return "<Cmd>lua vim.snippet.jump(1)<CR>" else return key end end, { expr = true }) Upside is that using `vim.keymap.set` to override keymaps is a well established pattern and `vim.snippet.expand` calls made by nvim itself or plugins have working keymaps out of the box. Co-authored-by: Maria José Solano <majosolano99@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6401b433f7
commit
123f8d229e
@ -4551,16 +4551,6 @@ vim.snippet.active({filter}) *vim.snippet.active()*
|
|||||||
Returns `true` if there's an active snippet in the current buffer,
|
Returns `true` if there's an active snippet in the current buffer,
|
||||||
applying the given filter if provided.
|
applying the given filter if provided.
|
||||||
|
|
||||||
You can use this function to navigate a snippet as follows: >lua
|
|
||||||
vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
|
||||||
if vim.snippet.active({ direction = 1 }) then
|
|
||||||
return '<Cmd>lua vim.snippet.jump(1)<CR>'
|
|
||||||
else
|
|
||||||
return '<Tab>'
|
|
||||||
end
|
|
||||||
end, { expr = true })
|
|
||||||
<
|
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
• {filter} (`vim.snippet.ActiveFilter?`) Filter to constrain the search
|
• {filter} (`vim.snippet.ActiveFilter?`) Filter to constrain the search
|
||||||
with:
|
with:
|
||||||
@ -4585,14 +4575,15 @@ vim.snippet.jump({direction}) *vim.snippet.jump()*
|
|||||||
Jumps to the next (or previous) placeholder in the current snippet, if
|
Jumps to the next (or previous) placeholder in the current snippet, if
|
||||||
possible.
|
possible.
|
||||||
|
|
||||||
For example, map `<Tab>` to jump while a snippet is active: >lua
|
By default `<Tab>` is setup to jump if a snippet is active. The default
|
||||||
|
mapping looks like: >lua
|
||||||
vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
||||||
if vim.snippet.active({ direction = 1 }) then
|
if vim.snippet.active({ direction = 1 }) then
|
||||||
return '<Cmd>lua vim.snippet.jump(1)<CR>'
|
return '<Cmd>lua vim.snippet.jump(1)<CR>'
|
||||||
else
|
else
|
||||||
return '<Tab>'
|
return '<Tab>'
|
||||||
end
|
end
|
||||||
end, { expr = true })
|
end, { descr = '...', expr = true, silent = true })
|
||||||
<
|
<
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
|
@ -218,6 +218,27 @@ do
|
|||||||
end, { desc = 'vim.lsp.buf.signature_help()' })
|
end, { desc = 'vim.lsp.buf.signature_help()' })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
---@param direction vim.snippet.Direction
|
||||||
|
---@param key string
|
||||||
|
local function set_snippet_jump(direction, key)
|
||||||
|
vim.keymap.set({ 'i', 's' }, key, function()
|
||||||
|
if vim.snippet.active({ direction = direction }) then
|
||||||
|
return string.format('<Cmd>lua vim.snippet.jump(%d)<CR>', direction)
|
||||||
|
else
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
end, {
|
||||||
|
desc = 'vim.snippet.jump if active, otherwise ' .. key,
|
||||||
|
expr = true,
|
||||||
|
silent = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
set_snippet_jump(1, '<Tab>')
|
||||||
|
set_snippet_jump(-1, '<S-Tab>')
|
||||||
|
end
|
||||||
|
|
||||||
--- Map [d and ]d to move to the previous/next diagnostic. Map <C-W>d to open a floating window
|
--- Map [d and ]d to move to the previous/next diagnostic. Map <C-W>d to open a floating window
|
||||||
--- for the diagnostic under the cursor.
|
--- for the diagnostic under the cursor.
|
||||||
---
|
---
|
||||||
|
@ -2,8 +2,6 @@ local G = vim.lsp._snippet_grammar
|
|||||||
local snippet_group = vim.api.nvim_create_augroup('nvim.snippet', {})
|
local snippet_group = vim.api.nvim_create_augroup('nvim.snippet', {})
|
||||||
local snippet_ns = vim.api.nvim_create_namespace('nvim.snippet')
|
local snippet_ns = vim.api.nvim_create_namespace('nvim.snippet')
|
||||||
local hl_group = 'SnippetTabstop'
|
local hl_group = 'SnippetTabstop'
|
||||||
local jump_forward_key = '<tab>'
|
|
||||||
local jump_backward_key = '<s-tab>'
|
|
||||||
|
|
||||||
--- Returns the 0-based cursor position.
|
--- Returns the 0-based cursor position.
|
||||||
---
|
---
|
||||||
@ -213,64 +211,9 @@ function Session.new(bufnr, snippet_extmark, tabstop_data)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:set_keymaps()
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Sets the snippet navigation keymaps.
|
|
||||||
---
|
|
||||||
--- @package
|
|
||||||
function Session:set_keymaps()
|
|
||||||
local function maparg(key, mode)
|
|
||||||
local map = vim.fn.maparg(key, mode, false, true) --[[ @as table ]]
|
|
||||||
if not vim.tbl_isempty(map) and map.buffer == 1 then
|
|
||||||
return map
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function set(jump_key, direction)
|
|
||||||
vim.keymap.set({ 'i', 's' }, jump_key, function()
|
|
||||||
return vim.snippet.active({ direction = direction })
|
|
||||||
and '<cmd>lua vim.snippet.jump(' .. direction .. ')<cr>'
|
|
||||||
or jump_key
|
|
||||||
end, { expr = true, silent = true, buffer = self.bufnr })
|
|
||||||
end
|
|
||||||
|
|
||||||
self.tab_keymaps = {
|
|
||||||
i = maparg(jump_forward_key, 'i'),
|
|
||||||
s = maparg(jump_forward_key, 's'),
|
|
||||||
}
|
|
||||||
self.shift_tab_keymaps = {
|
|
||||||
i = maparg(jump_backward_key, 'i'),
|
|
||||||
s = maparg(jump_backward_key, 's'),
|
|
||||||
}
|
|
||||||
set(jump_forward_key, 1)
|
|
||||||
set(jump_backward_key, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Restores/deletes the keymaps used for snippet navigation.
|
|
||||||
---
|
|
||||||
--- @package
|
|
||||||
function Session:restore_keymaps()
|
|
||||||
local function restore(keymap, lhs, mode)
|
|
||||||
if keymap then
|
|
||||||
vim._with({ buf = self.bufnr }, function()
|
|
||||||
vim.fn.mapset(keymap)
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
vim.api.nvim_buf_del_keymap(self.bufnr, mode, lhs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
restore(self.tab_keymaps.i, jump_forward_key, 'i')
|
|
||||||
restore(self.tab_keymaps.s, jump_forward_key, 's')
|
|
||||||
restore(self.shift_tab_keymaps.i, jump_backward_key, 'i')
|
|
||||||
restore(self.shift_tab_keymaps.s, jump_backward_key, 's')
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the destination tabstop index when jumping in the given direction.
|
--- Returns the destination tabstop index when jumping in the given direction.
|
||||||
---
|
---
|
||||||
--- @package
|
--- @package
|
||||||
@ -604,7 +547,7 @@ end
|
|||||||
|
|
||||||
--- Jumps to the next (or previous) placeholder in the current snippet, if possible.
|
--- Jumps to the next (or previous) placeholder in the current snippet, if possible.
|
||||||
---
|
---
|
||||||
--- For example, map `<Tab>` to jump while a snippet is active:
|
--- By default `<Tab>` is setup to jump if a snippet is active. The default mapping looks like:
|
||||||
---
|
---
|
||||||
--- ```lua
|
--- ```lua
|
||||||
--- vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
--- vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
||||||
@ -613,7 +556,7 @@ end
|
|||||||
--- else
|
--- else
|
||||||
--- return '<Tab>'
|
--- return '<Tab>'
|
||||||
--- end
|
--- end
|
||||||
--- end, { expr = true })
|
--- end, { descr = '...', expr = true, silent = true })
|
||||||
--- ```
|
--- ```
|
||||||
---
|
---
|
||||||
--- @param direction (vim.snippet.Direction) Navigation direction. -1 for previous, 1 for next.
|
--- @param direction (vim.snippet.Direction) Navigation direction. -1 for previous, 1 for next.
|
||||||
@ -656,18 +599,6 @@ end
|
|||||||
--- Returns `true` if there's an active snippet in the current buffer,
|
--- Returns `true` if there's an active snippet in the current buffer,
|
||||||
--- applying the given filter if provided.
|
--- applying the given filter if provided.
|
||||||
---
|
---
|
||||||
--- You can use this function to navigate a snippet as follows:
|
|
||||||
---
|
|
||||||
--- ```lua
|
|
||||||
--- vim.keymap.set({ 'i', 's' }, '<Tab>', function()
|
|
||||||
--- if vim.snippet.active({ direction = 1 }) then
|
|
||||||
--- return '<Cmd>lua vim.snippet.jump(1)<CR>'
|
|
||||||
--- else
|
|
||||||
--- return '<Tab>'
|
|
||||||
--- end
|
|
||||||
--- end, { expr = true })
|
|
||||||
--- ```
|
|
||||||
---
|
|
||||||
--- @param filter? vim.snippet.ActiveFilter Filter to constrain the search with:
|
--- @param filter? vim.snippet.ActiveFilter Filter to constrain the search with:
|
||||||
--- - `direction` (vim.snippet.Direction): Navigation direction. Will return `true` if the snippet
|
--- - `direction` (vim.snippet.Direction): Navigation direction. Will return `true` if the snippet
|
||||||
--- can be jumped in the given direction.
|
--- can be jumped in the given direction.
|
||||||
@ -689,8 +620,6 @@ function M.stop()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
M._session:restore_keymaps()
|
|
||||||
|
|
||||||
vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
|
vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
|
||||||
vim.api.nvim_buf_clear_namespace(M._session.bufnr, snippet_ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(M._session.bufnr, snippet_ns, 0, -1)
|
||||||
|
|
||||||
|
@ -18,6 +18,20 @@ local retry = t.retry
|
|||||||
describe('vim.snippet', function()
|
describe('vim.snippet', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
clear()
|
clear()
|
||||||
|
exec_lua(function()
|
||||||
|
local function set_snippet_jump(direction, key)
|
||||||
|
vim.keymap.set({ 'i', 's' }, key, function()
|
||||||
|
if vim.snippet.active({ direction = direction }) then
|
||||||
|
return string.format('<Cmd>lua vim.snippet.jump(%d)<CR>', direction)
|
||||||
|
else
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
end, { silent = true, expr = true })
|
||||||
|
end
|
||||||
|
|
||||||
|
set_snippet_jump(1, '<Tab>')
|
||||||
|
set_snippet_jump(-1, '<S-Tab>')
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
after_each(clear)
|
after_each(clear)
|
||||||
|
|
||||||
@ -289,24 +303,4 @@ describe('vim.snippet', function()
|
|||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('restores snippet navigation keymaps', function()
|
|
||||||
-- Create a buffer keymap in insert mode that deletes all lines.
|
|
||||||
local curbuf = api.nvim_get_current_buf()
|
|
||||||
exec_lua('vim.api.nvim_buf_set_keymap(..., "i", "<Tab>", "<cmd>normal ggdG<cr>", {})', curbuf)
|
|
||||||
|
|
||||||
test_expand_success({ 'var $1 = $2' }, { 'var = ' })
|
|
||||||
|
|
||||||
-- While the snippet is active, <Tab> should navigate between tabstops.
|
|
||||||
feed('x')
|
|
||||||
poke_eventloop()
|
|
||||||
feed('<Tab>0')
|
|
||||||
eq({ 'var x = 0' }, buf_lines(0))
|
|
||||||
|
|
||||||
exec_lua('vim.snippet.stop()')
|
|
||||||
|
|
||||||
-- After exiting the snippet, the buffer keymap should be restored.
|
|
||||||
feed('<Esc>O<cr><Tab>')
|
|
||||||
eq({ '' }, buf_lines(0))
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
|
@ -1241,7 +1241,9 @@ describe('vim.lsp.completion: integration', function()
|
|||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
feed('<tab>')
|
exec_lua(function()
|
||||||
|
vim.snippet.jump(1)
|
||||||
|
end)
|
||||||
eq(
|
eq(
|
||||||
#'hello friends',
|
#'hello friends',
|
||||||
exec_lua(function()
|
exec_lua(function()
|
||||||
|
Reference in New Issue
Block a user