From f4bb6ffdb1d5377be93283ff087a5ff97e23524f Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Mon, 31 Jan 2022 14:16:48 +0900 Subject: [PATCH] Improve keymap (#768) * Improve keymap * improve * rename * Add tests * Use nightly for testing * Enable failing test * Add manual test case --- .github/workflows/integration.yaml | 1 + lua/cmp/utils/async.lua | 2 +- lua/cmp/utils/keymap.lua | 109 +++++++++------------- lua/cmp/utils/keymap_spec.lua | 139 ++++++++++++++++++++--------- 4 files changed, 143 insertions(+), 108 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 9dee7f2..4bac4ba 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -26,6 +26,7 @@ jobs: - name: Setup neovim uses: rhysd/action-setup-vim@v1 with: + version: nightly neovim: true - name: Setup lua diff --git a/lua/cmp/utils/async.lua b/lua/cmp/utils/async.lua index 2751c4d..bc9f77a 100644 --- a/lua/cmp/utils/async.lua +++ b/lua/cmp/utils/async.lua @@ -18,7 +18,7 @@ async.throttle = function(fn, timeout) stop = function() time = nil timer:stop() - end + end, }, { __call = function(self, ...) local args = { ... } diff --git a/lua/cmp/utils/keymap.lua b/lua/cmp/utils/keymap.lua index c65e5ce..5cbfd73 100644 --- a/lua/cmp/utils/keymap.lua +++ b/lua/cmp/utils/keymap.lua @@ -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 `` because `=` will display gabage message. ---- In cmdline-mode, we shouldn't re-map as `` because `cmap (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 `=` 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('(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('(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('=v:lua.vim.json.decode(%s)'), 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('(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 diff --git a/lua/cmp/utils/keymap_spec.lua b/lua/cmp/utils/keymap_spec.lua index e1b71d6..959783f 100644 --- a/lua/cmp/utils/keymap_spec.lua +++ b/lua/cmp/utils/keymap_spec.lua @@ -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', '(', '()', { - 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('', function() + vim.api.nvim_buf_set_keymap(0, 'i', '(pairs)', '()', { noremap = true }) + vim.api.nvim_buf_set_keymap(0, 'i', '(', '(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', '(', '"()"', { - 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('=', function() + vim.api.nvim_buf_set_keymap(0, 'i', '(', '="()"', {}) + 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(''), '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('()') + return '()' .. keymap.t('') 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 ', function() + -- local fallback = keymap.fallback(0, 'c', keymap.get_map('c', '')) + -- 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', '(', '()', { + 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', '(', '"()"', { + 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('()') + 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', '', 'recursive', { - -- noremap = true - -- }) - -- vim.api.nvim_buf_set_keymap(0, 'i', '', 'recursive', { - -- noremap = false - -- }) - -- keymap.listen('i', '', function(_, fallback) fallback() end) - -- feedkeys.call(keymap.t('i'), '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', '', 'recursive', { + noremap = true, + }) + vim.api.nvim_buf_set_keymap(0, 'i', '', 'recursive', { + noremap = false, + }) + keymap.listen('i', '', function(_, fallback) + fallback() + end) + feedkeys.call(keymap.t('i'), 'tx') + assert.are.same({ '', 'recursive' }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) + end) end) end)