From c0fcdd9c6196f1be94fe243f688e62611228d4bf Mon Sep 17 00:00:00 2001 From: hrsh7th Date: Tue, 24 Aug 2021 22:43:02 +0900 Subject: [PATCH] Improve API stability --- README.md | 108 +++++++++++++++++------------------------ lua/cmp/core.lua | 5 +- lua/cmp/init.lua | 63 ++++++++++++++++++++++++ lua/cmp/mapping.lua | 96 ++++++++++++++++-------------------- lua/cmp/utils/misc.lua | 12 +++++ 5 files changed, 165 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index b224465..7ff0473 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # nvim-cmp -A completion plugin for neovim written in Lua. +A completion plugin for neovim coded by Lua. Status @@ -9,11 +9,11 @@ Status not yet stable but ok to use (for testing). -Features +Concept ==================== - Support pairs-wise plugin automatically -- Fully customizable via Lua functions (WIP) +- Fully customizable via Lua functions - Fully supported LSP's Completion capabilities - Snippets - CommitCharacters @@ -29,7 +29,7 @@ Features Setup ==================== -First, You should install core and sources by your favorite plugin manager. +First, You should install nvim-cmp itself and completion sources by your favorite plugin manager. The `nvim-cmp` sources can be found in [here](https://github.com/topics/nvim-cmp). @@ -46,20 +46,19 @@ Then setup configuration. lua <'] = cmp.mapping.prev_item(), - [''] = cmp.mapping.next_item(), - [''] = cmp.mapping.scroll(-4), - [''] = cmp.mapping.scroll(4), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), [''] = cmp.mapping.complete(), [''] = cmp.mapping.close(), [''] = cmp.mapping.confirm({ @@ -70,7 +69,7 @@ lua <'] = cmp.mapping.prev_item(), - [''] = cmp.mapping.next_item(), - [''] = cmp.mapping.scroll(-4), - [''] = cmp.mapping.scroll(4), - [''] = cmp.mapping.complete(), - [''] = cmp.mapping.close(), - [''] = cmp.mapping.confirm({ - behavior = cmp.ConfirmBehavior.Replace, - select = true, - }) + ... }, sources = { ... }, ... } ``` -### mapping (type: table) - -_TODO: This API is not stable yet. It can be changed with no announcement._ +### mapping (type: table) Define mappings with `cmp.mapping` helper. -The `cmp.mapping` helper has the below functions. - -- *cmp.mapping.confirm({ select = true or false, behavior = cmp.ConfirmBehavior.Insert or cmp.ConfirmBehavior.Replace })* -- *cmp.mapping.complete()* -- *cmp.mapping.close()* -- *cmp.mapping.next_item()* -- *cmp.mapping.prev_item()* -- *cmp.mapping.scroll(delta = number)* - -You can use ``and `` for navigating menu. +You can use the following functions as mapping configuration like this. ```lua --- This is just an example of LusSnip integration. You have to adjust it yourself. -local luasnip = require'luasnip' -local cmp = require'cmp' -cmp.setup { - mapping = { - [''] = cmp.mapping.mode({ 'i', 's' }, function(_, fallback) - if vim.fn.pumvisible() == 1 then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'n') - elseif luasnip.expand_or_jumpable() then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('luasnip-expand-or-jump', true, true, true), '') - else - fallback() - end - end), - [''] = cmp.mapping.mode({ 'i', 's' }, function(_, fallback) - if vim.fn.pumvisible() == 1 then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'n') - elseif luasnip.jumpable(-1) then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('luasnip-jump-prev', true, true, true), '') - else - fallback() - end - end) - } +mapping = { + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping.complete(), + [''] = cmp.mapping.close(), + [''] = cmp.mapping.confirm({ + behavior = cmp.ConfirmBehavior.Replace, + select = true, + }) +} +``` + +- *cmp.mapping.select_prev_item()* +- *cmp.mapping.select_next_item()* +- *cmp.mapping.scroll_docs(number)* +- *cmp.mapping.complete()* +- *cmp.mapping.close()* +- *cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })* + +In addition, You can specify vim's mode to those mapping functions like this. + +```lua +mapping = { + ... + [''] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' }) + ... } ``` diff --git a/lua/cmp/core.lua b/lua/cmp/core.lua index e5d2b10..505dc25 100644 --- a/lua/cmp/core.lua +++ b/lua/cmp/core.lua @@ -75,10 +75,11 @@ core.on_keymap = function(keys, fallback) for key, action in pairs(config.get().mapping) do if key == keys then if type(action) == 'function' then - return action(core, fallback) + action(fallback) else - return action.invoke(core, fallback) + action.invoke(fallback) end + return end end diff --git a/lua/cmp/init.lua b/lua/cmp/init.lua index 103a2c0..c028f8b 100644 --- a/lua/cmp/init.lua +++ b/lua/cmp/init.lua @@ -1,4 +1,5 @@ local core = require('cmp.core') +local keymap = require('cmp.utils.keymap') local source = require('cmp.source') local config = require('cmp.config') local autocmd = require('cmp.autocmd') @@ -31,6 +32,68 @@ cmp.unregister_source = function(id) core.unregister_source(id) end +---Invoke completion manually +cmp.complete = function() + core.complete(core.get_context({ reason = cmp.ContextReason.Manual })) + return true +end + +---Close current completion +cmp.close = function() + if vim.fn.pumvisible() == 1 then + core.reset() + return true + else + return false + end +end + +---Select next item if possible +cmp.select_next_item = function() + if vim.fn.pumvisible() == 1 then + vim.fn.feedkeys(keymap.t(''), 'n') + return true + else + return false + end +end + +---Select prev item if possible +cmp.select_prev_item = function() + if vim.fn.pumvisible() == 1 then + vim.fn.feedkeys(keymap.t(''), 'n') + return true + else + return false + end +end + +---Scrolling documentation window if possible +cmp.scroll_docs = function(delta) + if core.menu.float:is_visible() then + core.menu.float:scroll(delta) + return true + else + return false + end +end + +---Confirm completion +cmp.confirm = function(option) + option = option or {} + local e = core.menu:get_selected_entry() or (option.select and core.menu:get_first_entry() or nil) + if e then + core.confirm(e, { + behavior = option.behavior, + }, function() + core.complete(core.get_context({ reason = cmp.ContextReason.TriggerOnly })) + end) + return true + else + return false + end +end + ---@type cmp.Setup cmp.setup = setmetatable({ global = function(c) diff --git a/lua/cmp/mapping.lua b/lua/cmp/mapping.lua index acefac8..a9660fa 100644 --- a/lua/cmp/mapping.lua +++ b/lua/cmp/mapping.lua @@ -1,82 +1,70 @@ -local types = require('cmp.types') +local misc = require('cmp.utils.misc') -local mapping = {} - -mapping.mode = function(modes, action_or_invoke) - local invoke = action_or_invoke - if type(action_or_invoke) == 'table' then - if type(action_or_invoke.invoke) == 'function' then - invoke = action_or_invoke.invoke - else - error('`invoke` must be function or result of `cmp.mapping.mode`.') +local mapping = setmetatable({}, { + __call = function(_, invoke, modes) + return { + invoke = function(...) + invoke(...) + end, + modes = modes or { 'i' }, + } + end, +}) +---Invoke completion +mapping.complete = function() + return function(fallback) + if not require('cmp').complete() then + fallback() end end - return { - modes = modes, - invoke = invoke, - } -end - -mapping.complete = function() - return mapping.mode({ 'i' }, function(core) - core.complete(core.get_context({ reason = types.cmp.ContextReason.Manual })) - end) end +---Close current completion menu if it displayed. mapping.close = function() - return mapping.mode({ 'i' }, function(core, fallback) - if vim.fn.pumvisible() == 1 then - core.reset() - else + return function(fallback) + if not require('cmp').close() then fallback() end - end) + end end -mapping.scroll = function(delta) - return mapping.mode({ 'i' }, function(core, fallback) - if core.menu.float:is_visible() then - core.menu.float:scroll(delta) - else +---Scroll documentation window. +mapping.scroll_docs = function(delta) + return function(fallback) + if not require('cmp').scroll_docs(delta) then fallback() end - end) + end end +mapping.scroll = misc.deprecated(mapping.scroll_docs, '`cmp.mapping.scroll` is deprecated. Please change it to `cmp.mapping.scroll_docs` instead.') -mapping.next_item = function() - return mapping.mode({ 'i' }, function(_, fallback) - if vim.fn.pumvisible() == 1 then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'n') - else +---Select next completion item. +mapping.select_next_item = function() + return function(fallback) + if not require('cmp').select_next_item() then fallback() end - end) + end end +mapping.next_item = misc.deprecated(mapping.select_next_item, '`cmp.mapping.next_item` is deprecated. Please change it to `cmp.mapping.select_next_item` instead.') -mapping.prev_item = function() - return mapping.mode({ 'i' }, function(_, fallback) - if vim.fn.pumvisible() == 1 then - vim.fn.feedkeys(vim.api.nvim_replace_termcodes('', true, true, true), 'n') - else +---Select prev completion item. +mapping.select_prev_item = function() + return function(fallback) + if not require('cmp').select_prev_item() then fallback() end - end) + end end +mapping.prev_item = misc.deprecated(mapping.select_prev_item, '`cmp.mapping.prev_item` is deprecated. Please change it to `cmp.mapping.select_prev_item` instead.') +---Confirm selection mapping.confirm = function(option) - option = option or {} - return mapping.mode({ 'i' }, function(core, fallback) - local e = core.menu:get_selected_entry() or (option.select and core.menu:get_first_entry() or nil) - if e then - core.confirm(e, { - behavior = option.behavior, - }, function() - core.complete(core.get_context({ reason = types.cmp.ContextReason.TriggerOnly })) - end) - else + return function(fallback) + if not require('cmp').confirm(option) then fallback() end - end) + end end return mapping diff --git a/lua/cmp/utils/misc.lua b/lua/cmp/utils/misc.lua index 147d588..a0f2773 100644 --- a/lua/cmp/utils/misc.lua +++ b/lua/cmp/utils/misc.lua @@ -136,4 +136,16 @@ misc.to_vimindex = function(text, utfindex) return utfindex + 1 end +---Mark the function as deprecated +misc.deprecated = function(fn, msg) + local printed = false + return function(...) + if not printed then + print(msg) + printed = true + end + return fn(...) + end +end + return misc