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:
hrsh7th
2022-01-31 14:16:48 +09:00
committed by GitHub
parent d93104244c
commit f4bb6ffdb1
4 changed files with 143 additions and 108 deletions

View File

@@ -26,6 +26,7 @@ jobs:
- name: Setup neovim
uses: rhysd/action-setup-vim@v1
with:
version: nightly
neovim: true
- name: Setup lua

View File

@@ -18,7 +18,7 @@ async.throttle = function(fn, timeout)
stop = function()
time = nil
timer:stop()
end
end,
}, {
__call = function(self, ...)
local args = { ... }

View File

@@ -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
end
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)
---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
return rhs
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

View File

@@ -37,46 +37,102 @@ 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()
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('<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('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>')
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('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 = 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('recursive expr', function()
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 = 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('recursive callback', function()
it('expr-callback', function()
pcall(function()
vim.api.nvim_buf_set_keymap(0, 'i', '(', '', {
expr = true,
@@ -87,12 +143,13 @@ describe('keymap', function()
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)
end)
end)
end)
describe('realworld', function()
before_each(spec.before)
@@ -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)