From 753f5b7c92da0302efffc5ce6780dffe0602bdf3 Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Sun, 14 Nov 2021 02:47:31 +0900 Subject: [PATCH] Fix #516 (#517) * Fix #516 * Fix * Fix and tests * fmt lint --- lua/cmp/core.lua | 56 ++++++++++---------- lua/cmp/core_spec.lua | 78 ++++++++++++++++++++++++++++ lua/cmp/init.lua | 1 - lua/cmp/utils/api.lua | 8 +-- lua/cmp/utils/api_spec.lua | 1 - lua/cmp/utils/feedkeys_spec.lua | 24 ++++++++- lua/cmp/utils/keymap.lua | 15 +++--- lua/cmp/utils/spec.lua | 24 ++++++++- lua/cmp/view/custom_entries_view.lua | 14 +++-- lua/cmp/view/ghost_text_view.lua | 2 +- 10 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 lua/cmp/core_spec.lua diff --git a/lua/cmp/core.lua b/lua/cmp/core.lua index df05216..ad981ad 100644 --- a/lua/cmp/core.lua +++ b/lua/cmp/core.lua @@ -308,40 +308,29 @@ core.confirm = function(self, e, option, callback) -- Close menus. self.view:close() - -- Simulate behavior and store the `.` register. - async.step(function(next) + feedkeys.call('', 'n', function() local ctx = context.new() local keys = {} table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1))) table.insert(keys, e:get_word()) table.insert(keys, keymap.undobreak()) - feedkeys.call(table.concat(keys, ''), 'nt', next) - - -- Restore the state again without modify the `.` register. - end, function(next) + feedkeys.call(table.concat(keys, ''), 'int') + end) + feedkeys.call('', 'n', function() local ctx = context.new() if api.is_cmdline_mode() then local keys = {} table.insert(keys, keymap.backspace(ctx.cursor.character - vim.str_utfindex(ctx.cursor_line, e:get_offset() - 1))) table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset())) - feedkeys.call(table.concat(keys, ''), 'nt', next) + feedkeys.call(table.concat(keys, ''), 'int') else - vim.api.nvim_buf_set_text( - 0, - ctx.cursor.row - 1, - e:get_offset() - 1, - ctx.cursor.row - 1, - ctx.cursor.col - 1, - { - string.sub(e.context.cursor_before_line, e:get_offset()) - } - ) + vim.api.nvim_buf_set_text(0, ctx.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, { + string.sub(e.context.cursor_before_line, e:get_offset()), + }) vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 }) - next() end - - -- Async additionalTextEdits @see https://github.com/microsoft/vscode/blob/main/src/vs/editor/contrib/suggest/suggestController.ts#L334 - end, function(next) + end) + feedkeys.call('', 'n', function() if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then local pre = context.new() e:resolve(function() @@ -371,10 +360,8 @@ core.confirm = function(self, e, option, callback) else vim.fn['cmp#apply_text_edits'](vim.api.nvim_get_current_buf(), e:get_completion_item().additionalTextEdits) end - next() - - -- Expand completion item - end, function(next) + end) + feedkeys.call('', 'n', function() local ctx = context.new() local completion_item = misc.copy(e:get_completion_item()) if not misc.safe(completion_item.textEdit) then @@ -407,18 +394,27 @@ core.confirm = function(self, e, option, callback) body = new_text, insert_text_mode = completion_item.insertTextMode, }) + else + local texts = vim.split(completion_item.textEdit.newText, '\n') + local position = completion_item.textEdit.range.start + position.line = position.line + (#texts - 1) + if #texts == 1 then + position.character = position.character + vim.str_utfindex(texts[1]) + else + position.character = vim.str_utfindex(texts[#texts]) + end + local pos = types.lsp.Position.to_vim(0, position) + vim.api.nvim_win_set_cursor(0, { pos.row, pos.col - 1 }) end - next() else local keys = {} table.insert(keys, string.rep(keymap.t(''), diff_before)) table.insert(keys, string.rep(keymap.t(''), diff_after)) table.insert(keys, new_text) - feedkeys.call(table.concat(keys, ''), 'n', next) + feedkeys.call(table.concat(keys, ''), 'int') end - - -- Finalize - end, function() + end) + feedkeys.call('', 'n', function() e:execute(vim.schedule_wrap(function() release() self.event:emit('confirm_done', e) diff --git a/lua/cmp/core_spec.lua b/lua/cmp/core_spec.lua new file mode 100644 index 0000000..1df5076 --- /dev/null +++ b/lua/cmp/core_spec.lua @@ -0,0 +1,78 @@ +local spec = require('cmp.utils.spec') +local feedkeys = require('cmp.utils.feedkeys') +local types = require('cmp.types') +local core = require('cmp.core') +local source = require('cmp.source') +local keymap = require('cmp.utils.keymap') + +describe('cmp.core', function() + describe('confirm #confirm', function() + before_each(spec.before) + + local confirm = function(request, filter, completion_item) + local c = core.new() + local s = source.new('spec', { + complete = function(_, _, callback) + callback({ completion_item }) + end, + }) + c:register_source(s) + feedkeys.call(request, 'n', function() + c:complete(c:get_context({ reason = types.cmp.ContextReason.Manual })) + vim.wait(5000, function() + return #c.sources[s.id].entries > 0 + end) + end) + feedkeys.call(filter, 'n', function() + c:confirm(c.sources[s.id].entries[1], {}) + end) + local state = {} + feedkeys.call('', 'x', function() + feedkeys.call('', 'n', function() + state.buffer = vim.api.nvim_buf_get_lines(0, 0, -1, false) + state.cursor = vim.api.nvim_win_get_cursor(0) + end) + end) + return state + end + + it('label only', function() + local state = confirm('iA', 'IU', { + label = 'AIUEO', + }) + assert.are.same(state.buffer, { 'AIUEO' }) + assert.are.same(state.cursor, { 1, 5 }) + end) + + it('text edit', function() + local state = confirm(keymap.t('i***AEO***'), 'IU', { + label = 'AIUEO', + textEdit = { + range = { + start = { + line = 0, + character = 3, + }, + ['end'] = { + line = 0, + character = 6, + }, + }, + newText = 'foo\nbar\nbaz', + }, + }) + assert.are.same(state.buffer, { '***foo', 'bar', 'baz***' }) + assert.are.same(state.cursor, { 3, 3 }) + end) + + it('snippet', function() + local state = confirm('iA', 'IU', { + label = 'AIUEO', + insertText = 'AIUEO($0)', + insertTextFormat = types.lsp.InsertTextFormat.Snippet, + }) + assert.are.same(state.buffer, { 'AIUEO()' }) + assert.are.same(state.cursor, { 1, 6 }) + end) + end) +end) diff --git a/lua/cmp/init.lua b/lua/cmp/init.lua index 83970ab..b1ebf9d 100644 --- a/lua/cmp/init.lua +++ b/lua/cmp/init.lua @@ -5,7 +5,6 @@ local feedkeys = require('cmp.utils.feedkeys') local autocmd = require('cmp.utils.autocmd') local keymap = require('cmp.utils.keymap') local misc = require('cmp.utils.misc') -local api = require('cmp.utils.api') local cmp = {} diff --git a/lua/cmp/utils/api.lua b/lua/cmp/utils/api.lua index deeb85e..9f9879d 100644 --- a/lua/cmp/utils/api.lua +++ b/lua/cmp/utils/api.lua @@ -6,13 +6,13 @@ local CTRL_S = vim.api.nvim_replace_termcodes('', true, true, true) api.get_mode = function() local mode = vim.api.nvim_get_mode().mode:sub(1, 1) if mode == 'i' then - return 'i' -- insert + return 'i' -- insert elseif mode == 'v' or mode == 'V' or mode == CTRL_V then - return 'x' -- visual + return 'x' -- visual elseif mode == 's' or mode == 'S' or mode == CTRL_S then - return 's' -- select + return 's' -- select elseif mode == 'c' and vim.fn.getcmdtype() ~= '=' then - return 'c' -- cmdline + return 'c' -- cmdline end end diff --git a/lua/cmp/utils/api_spec.lua b/lua/cmp/utils/api_spec.lua index 1b05686..5363b48 100644 --- a/lua/cmp/utils/api_spec.lua +++ b/lua/cmp/utils/api_spec.lua @@ -44,4 +44,3 @@ describe('api', function() end) end) end) - diff --git a/lua/cmp/utils/feedkeys_spec.lua b/lua/cmp/utils/feedkeys_spec.lua index 70f9dc4..d024bb0 100644 --- a/lua/cmp/utils/feedkeys_spec.lua +++ b/lua/cmp/utils/feedkeys_spec.lua @@ -28,7 +28,29 @@ describe('feedkeys', function() feedkeys.call(keymap.t('iifend') .. keymap.autoindent(), 'nx') assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), { 'if', - 'end' + 'end', }) end) + + it('testability', function() + feedkeys.call('i', 'n', function() + feedkeys.call('', 'n', function() + feedkeys.call('aiueo', 'in') + end) + feedkeys.call('', 'n', function() + feedkeys.call(keymap.t(''), 'in') + end) + feedkeys.call('', 'n', function() + feedkeys.call(keymap.t('abcde'), 'in') + end) + feedkeys.call('', 'n', function() + feedkeys.call(keymap.t(''), 'in') + end) + feedkeys.call('', 'n', function() + feedkeys.call(keymap.t('12345'), 'in') + end) + end) + feedkeys.call('', 'x') + assert.are.same(vim.api.nvim_buf_get_lines(0, 0, -1, false), { '12345' }) + end) end) diff --git a/lua/cmp/utils/keymap.lua b/lua/cmp/utils/keymap.lua index bc30bb9..d0ca963 100644 --- a/lua/cmp/utils/keymap.lua +++ b/lua/cmp/utils/keymap.lua @@ -81,7 +81,7 @@ keymap.autoindent = function() table.insert(keys, keymap.t('setlocal indentkeys+=!^F')) table.insert(keys, keymap.t('')) table.insert(keys, keymap.t('setlocal %scindent'):format(vim.bo.cindent and '' or 'no')) - table.insert(keys, keymap.t('setlocal indentkeys=%s'):format(vim.bo.indentkeys:gsub( '|', '\\|'))) + table.insert(keys, keymap.t('setlocal indentkeys=%s'):format(vim.bo.indentkeys:gsub('|', '\\|'))) return table.concat(keys, '') end @@ -110,9 +110,12 @@ keymap.listen = function(mode, lhs, callback) return vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true) end - callback(lhs, misc.once(function() - vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true) - end)) + callback( + lhs, + misc.once(function() + vim.api.nvim_feedkeys(keymap.t(fallback.keys), fallback.mode, true) + end) + ) end, { expr = false, noremap = true, @@ -237,7 +240,7 @@ end ---Set keymapping keymap.set_map = setmetatable({ - callbacks = {} + callbacks = {}, }, { __call = function(self, bufnr, mode, lhs, rhs, opts) if type(rhs) == 'function' then @@ -255,7 +258,7 @@ keymap.set_map = setmetatable({ else vim.api.nvim_buf_set_keymap(bufnr, mode, lhs, rhs, opts) end - end + end, }) misc.set(_G, { 'cmp', 'utils', 'keymap', 'set_map' }, function(id) return keymap.set_map.callbacks[id]() or '' diff --git a/lua/cmp/utils/spec.lua b/lua/cmp/utils/spec.lua index f461875..6996cc4 100644 --- a/lua/cmp/utils/spec.lua +++ b/lua/cmp/utils/spec.lua @@ -1,6 +1,7 @@ local context = require('cmp.context') local source = require('cmp.source') local types = require('cmp.types') +local config = require('cmp.config') local spec = {} @@ -22,7 +23,28 @@ spec.before = function() setlocal virtualedit=all setlocal completeopt=menu,menuone,noselect ]]) - vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'x', true) + config.set_global({ + sources = { + { name = 'spec' }, + }, + snippet = { + expand = function(args) + local ctx = context.new() + vim.api.nvim_buf_set_text(ctx.bufnr, ctx.cursor.row - 1, ctx.cursor.col - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, vim.split(string.gsub(args.body, '%$0', ''), '\n')) + for i, t in ipairs(vim.split(args.body, '\n')) do + local s = string.find(t, '$0', 1, true) + if s then + if i == 1 then + vim.api.nvim_win_set_cursor(0, { ctx.cursor.row, ctx.cursor.col + s - 2 }) + else + vim.api.nvim_win_set_cursor(0, { ctx.cursor.row, s - 1 }) + end + break + end + end + end, + }, + }) end spec.state = function(text, row, col) diff --git a/lua/cmp/view/custom_entries_view.lua b/lua/cmp/view/custom_entries_view.lua index 08ba49a..56dd1b0 100644 --- a/lua/cmp/view/custom_entries_view.lua +++ b/lua/cmp/view/custom_entries_view.lua @@ -319,13 +319,17 @@ custom_entries_view._insert = setmetatable({ local release = require('cmp').suspend() feedkeys.call('', '', function() local cursor = api.get_cursor() - feedkeys.call(keymap.backspace(1 + cursor[2] - self.offset) .. word, 'int', vim.schedule_wrap(function() - this.pending = false - release() - end)) + feedkeys.call( + keymap.backspace(1 + cursor[2] - self.offset) .. word, + 'int', + vim.schedule_wrap(function() + this.pending = false + release() + end) + ) end) end - end + end, }) return custom_entries_view diff --git a/lua/cmp/view/ghost_text_view.lua b/lua/cmp/view/ghost_text_view.lua index ed92363..786288e 100644 --- a/lua/cmp/view/ghost_text_view.lua +++ b/lua/cmp/view/ghost_text_view.lua @@ -66,7 +66,7 @@ ghost_text_view.text_gen = function(self, line, cursor_col) if nchars > 0 then text = string.sub(word, vim.str_byteindex(word, word_clen - nchars) + 1) else - text = "" + text = '' end return text end