Improve keymap (#768)
* Improve keymap * improve * rename * Add tests * Use nightly for testing * Enable failing test * Add manual test case
This commit is contained in:
1
.github/workflows/integration.yaml
vendored
1
.github/workflows/integration.yaml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
- name: Setup neovim
|
||||
uses: rhysd/action-setup-vim@v1
|
||||
with:
|
||||
version: nightly
|
||||
neovim: true
|
||||
|
||||
- name: Setup lua
|
||||
|
||||
@@ -18,7 +18,7 @@ async.throttle = function(fn, timeout)
|
||||
stop = function()
|
||||
time = nil
|
||||
timer:stop()
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
__call = function(self, ...)
|
||||
local args = { ... }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local cache = require('cmp.utils.cache')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
@@ -105,14 +104,9 @@ keymap.listen = function(mode, lhs, callback)
|
||||
local fallback = keymap.fallback(bufnr, mode, existing)
|
||||
keymap.set_map(bufnr, mode, lhs, function()
|
||||
if mode == 'c' and vim.fn.getcmdtype() == '=' then
|
||||
vim.api.nvim_feedkeys(fallback.keys, 'it' .. (fallback.noremap and 'n' or 'm'), true)
|
||||
fallback()
|
||||
else
|
||||
callback(
|
||||
lhs,
|
||||
misc.once(function()
|
||||
vim.api.nvim_feedkeys(fallback.keys, 'it' .. (fallback.noremap and 'n' or 'm'), true)
|
||||
end)
|
||||
)
|
||||
callback(lhs, misc.once(fallback))
|
||||
end
|
||||
end, {
|
||||
expr = false,
|
||||
@@ -121,76 +115,57 @@ keymap.listen = function(mode, lhs, callback)
|
||||
})
|
||||
end
|
||||
|
||||
---Fallback existing mapping.
|
||||
--- NOTE:
|
||||
--- In insert-mode, we should all mapping fallback to the `<Plug>` because `<C-r>=` will display gabage message.
|
||||
--- In cmdline-mode, we shouldn't re-map as `<Plug>` because `cmap <Tab> <Plug>(map-to-tab)` will broke native behavior.
|
||||
--- We should resolve recursive mapping because existing mapping will feed by `feedkeys` that doesn't solve recursive mapping.
|
||||
--- We use `<C-r>=` to solve recursive mapping.
|
||||
---@param map table
|
||||
keymap.fallback = setmetatable({
|
||||
cache = cache.new(),
|
||||
}, {
|
||||
__call = function(self, bufnr, mode, map)
|
||||
local fallback = self.cache:ensure({ bufnr, mode, map.lhs }, function()
|
||||
return string.format('<Plug>(cmp.u.k.fallback:%s)', misc.id('cmp.utils.keymap.fallback'))
|
||||
end)
|
||||
|
||||
---Fallback
|
||||
keymap.fallback = function(bufnr, mode, map)
|
||||
return function()
|
||||
if map.expr then
|
||||
keymap.set_map(bufnr, mode, fallback, function()
|
||||
local lhs = keymap.t(map.lhs)
|
||||
local rhs = (function()
|
||||
if map.callback then
|
||||
return map.callback()
|
||||
end
|
||||
return vim.api.nvim_eval(keymap.t(map.rhs))
|
||||
end)()
|
||||
if not map.noremap then
|
||||
rhs = keymap.recursive(lhs, rhs)
|
||||
end
|
||||
return rhs
|
||||
local fallback_expr = string.format('<Plug>(cmp.u.k.fallback_expr:%s)', map.lhs)
|
||||
keymap.set_map(bufnr, mode, fallback_expr, function()
|
||||
return keymap.solve(bufnr, mode, map).keys
|
||||
end, {
|
||||
expr = true,
|
||||
noremap = map.noremap,
|
||||
script = map.script,
|
||||
silent = mode ~= 'c',
|
||||
nowait = map.nowait,
|
||||
})
|
||||
elseif mode ~= 'c' or map.callback then
|
||||
local rhs = map.rhs
|
||||
if not map.noremap then
|
||||
rhs = keymap.recursive(map.lhs, rhs)
|
||||
end
|
||||
keymap.set_map(bufnr, mode, fallback, rhs, {
|
||||
expr = false,
|
||||
callback = map.callback,
|
||||
noremap = map.noremap,
|
||||
script = map.script,
|
||||
silent = mode ~= 'c' and map.silent,
|
||||
nowait = map.nowait,
|
||||
silent = map.silent and mode ~= 'c',
|
||||
})
|
||||
vim.api.nvim_feedkeys(keymap.t(fallback_expr), 'itm', true)
|
||||
else
|
||||
local lhs = keymap.t(map.lhs)
|
||||
local rhs = keymap.t(map.rhs)
|
||||
if not map.noremap then
|
||||
rhs = keymap.recursive(lhs, rhs)
|
||||
if map.callback then
|
||||
map.callback()
|
||||
else
|
||||
local solved = keymap.solve(bufnr, mode, map)
|
||||
vim.api.nvim_feedkeys(solved.keys, solved.mode, true)
|
||||
end
|
||||
return { keys = rhs, noremap = map.noremap }
|
||||
end
|
||||
return { keys = keymap.t(fallback), noremap = false }
|
||||
end,
|
||||
})
|
||||
|
||||
---Solve recursive mapping.
|
||||
---@param lhs string
|
||||
---@param rhs string
|
||||
---@return string
|
||||
keymap.recursive = function(lhs, rhs)
|
||||
if string.find(rhs, lhs, 1, true) == 1 then
|
||||
local expr = string.format(keymap.t('<C-r>=v:lua.vim.json.decode(%s)<CR>'), vim.fn.string(vim.json.encode(keymap.t(lhs))))
|
||||
return string.gsub(rhs, '^' .. vim.pesc(lhs), expr)
|
||||
end
|
||||
return rhs
|
||||
end
|
||||
|
||||
---Solve
|
||||
keymap.solve = function(bufnr, mode, map)
|
||||
local lhs = keymap.t(map.lhs)
|
||||
local rhs
|
||||
if map.expr then
|
||||
rhs = map.callback and map.callback() or vim.api.nvim_eval(keymap.t(map.rhs))
|
||||
else
|
||||
rhs = keymap.t(map.rhs)
|
||||
end
|
||||
|
||||
if map.noremap then
|
||||
return { keys = rhs, mode = 'itn' }
|
||||
end
|
||||
|
||||
if string.find(rhs, lhs, 1, true) == 1 then
|
||||
local recursive = string.format('<Plug>(cmp.u.k.recursive:%s)', lhs)
|
||||
keymap.set_map(bufnr, mode, recursive, lhs, {
|
||||
noremap = true,
|
||||
script = map.script,
|
||||
nowait = map.nowait,
|
||||
silent = map.silent and mode ~= 'c',
|
||||
})
|
||||
return { keys = keymap.t(recursive) .. string.gsub(rhs, '^' .. vim.pesc(lhs), ''), mode = 'itm' }
|
||||
end
|
||||
return { keys = rhs, mode = 'itm' }
|
||||
end
|
||||
|
||||
---Get map
|
||||
|
||||
@@ -37,60 +37,117 @@ describe('keymap', function()
|
||||
describe('fallback', function()
|
||||
before_each(spec.before)
|
||||
|
||||
local keys = function(keys, mode)
|
||||
local run_fallback = function(keys, fallback)
|
||||
local state = {}
|
||||
feedkeys.call(keys, mode, function()
|
||||
feedkeys.call(keys, '', function()
|
||||
fallback()
|
||||
end)
|
||||
feedkeys.call('', '', function()
|
||||
if api.is_cmdline_mode() then
|
||||
state.buffer = { api.get_current_line() }
|
||||
else
|
||||
state.buffer = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
end
|
||||
state.cursor = api.get_cursor()
|
||||
state.wildmenumode = vim.fn.wildmenumode() == 1
|
||||
end)
|
||||
feedkeys.call('', 'x')
|
||||
return state
|
||||
end
|
||||
|
||||
it('recursive', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '()<Left>', {
|
||||
expr = false,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = keys('i' .. fallback.keys, fallback.noremap and 'n' or 'm')
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
describe('basic', function()
|
||||
it('<Plug>', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Plug>(pairs)', '()<Left>', { noremap = true })
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '<Plug>(pairs)', { noremap = false })
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('recursive expr', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '"()<Left>"', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = keys('i' .. fallback.keys, fallback.noremap and 'n' or 'm')
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
it('<C-r>=', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '<C-r>="()"<CR><Left>', {})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('recursive callback', function()
|
||||
pcall(function()
|
||||
it('callback', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
callback = function()
|
||||
vim.api.nvim_feedkeys('()' .. keymap.t('<Left>'), 'int', true)
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr-callback', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
callback = function()
|
||||
return keymap.t('()<Left>')
|
||||
return '()' .. keymap.t('<Left>')
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = keys('i' .. fallback.keys, fallback.noremap and 'n' or 'm')
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
-- it('cmdline default <Tab>', function()
|
||||
-- local fallback = keymap.fallback(0, 'c', keymap.get_map('c', '<Tab>'))
|
||||
-- local state = run_fallback(':', fallback)
|
||||
-- assert.are.same({ '' }, state.buffer)
|
||||
-- assert.are.same({ 1, 0 }, state.cursor)
|
||||
-- end)
|
||||
end)
|
||||
|
||||
describe('recursive', function()
|
||||
it('non-expr', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '()<Left>', {
|
||||
expr = false,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '"()<Left>"', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
|
||||
it('expr-callback', function()
|
||||
pcall(function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
|
||||
expr = true,
|
||||
noremap = false,
|
||||
silent = true,
|
||||
callback = function()
|
||||
return keymap.t('()<Left>')
|
||||
end,
|
||||
})
|
||||
local fallback = keymap.fallback(0, 'i', keymap.get_map('i', '('))
|
||||
local state = run_fallback('i', fallback)
|
||||
assert.are.same({ '()' }, state.buffer)
|
||||
assert.are.same({ 1, 1 }, state.cursor)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -113,16 +170,18 @@ describe('keymap', function()
|
||||
assert.are.same({ 'aiueo', 'aiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
-- it('#744', function()
|
||||
-- vim.api.nvim_buf_set_keymap(0, 'i', '<C-r>', 'recursive', {
|
||||
-- noremap = true
|
||||
-- })
|
||||
-- vim.api.nvim_buf_set_keymap(0, 'i', '<CR>', '<CR>recursive', {
|
||||
-- noremap = false
|
||||
-- })
|
||||
-- keymap.listen('i', '<CR>', function(_, fallback) fallback() end)
|
||||
-- feedkeys.call(keymap.t('i<CR>'), 'tx')
|
||||
-- assert.are.same({ '', 'recursive' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
-- end)
|
||||
it('#744', function()
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<C-r>', 'recursive', {
|
||||
noremap = true,
|
||||
})
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<CR>', '<CR>recursive', {
|
||||
noremap = false,
|
||||
})
|
||||
keymap.listen('i', '<CR>', function(_, fallback)
|
||||
fallback()
|
||||
end)
|
||||
feedkeys.call(keymap.t('i<CR>'), 'tx')
|
||||
assert.are.same({ '', 'recursive' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user