RFC: cmdline completion (#362)

* manual support dot-repeat

* cmdwin and terminal

* cmdline only

* Fix

* fix

* Improve

* Fix test

* Support macro

* disable cmdline for now

* Simplify

* fmt

* consume once

* Ignore = type

* cmdline

* fmt

* Improve

* update

* fmt

* Support incsearch

* fix

* Add api

* Avoid cmdline completion if the native_menu enabled

* fix for macro

* Improve

* fmt

* Insert-mode only by default

* Update

* avoid conflict

* Improve default mapping

* Fix

* fix

* similar to native

* Update

* Fix README.md

* Improve

* Use <afile>
This commit is contained in:
hrsh7th
2021-10-27 12:38:46 +09:00
committed by GitHub
parent b5899f05c5
commit cae2e8f48b
13 changed files with 263 additions and 109 deletions

View File

@@ -44,6 +44,8 @@ call plug#begin(s:plug_dir)
Plug 'neovim/nvim-lspconfig' Plug 'neovim/nvim-lspconfig'
Plug 'hrsh7th/cmp-nvim-lsp' Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer' Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/cmp-path'
Plug 'hrsh7th/cmp-cmdline'
Plug 'hrsh7th/nvim-cmp' Plug 'hrsh7th/nvim-cmp'
" For vsnip users. " For vsnip users.
@@ -80,11 +82,14 @@ lua <<EOF
end, end,
}, },
mapping = { mapping = {
['<C-d>'] = cmp.mapping.scroll_docs(-4), ['<C-d>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), { 'i', 'c' }),
['<C-f>'] = cmp.mapping.scroll_docs(4), ['<C-f>'] = cmp.mapping(cmp.mapping.scroll_docs(4), { 'i', 'c' }),
['<C-Space>'] = cmp.mapping.complete(), ['<C-Space>'] = cmp.mapping(cmp.mapping.complete(), { 'i', 'c' }),
['<C-e>'] = cmp.mapping.close(),
['<C-y>'] = cmp.config.disable, -- If you want to remove the default `<C-y>` mapping, You can specify `cmp.config.disable` value. ['<C-y>'] = cmp.config.disable, -- If you want to remove the default `<C-y>` mapping, You can specify `cmp.config.disable` value.
['<C-e>'] = cmp.mapping({
i = cmp.mapping.abort(),
c = cmp.mapping.close(),
}),
['<CR>'] = cmp.mapping.confirm({ select = true }), ['<CR>'] = cmp.mapping.confirm({ select = true }),
}, },
sources = cmp.config.sources({ sources = cmp.config.sources({
@@ -98,6 +103,22 @@ lua <<EOF
}) })
}) })
-- Use buffer source for `/`.
cmp.setup.cmdline('/', {
sources = {
{ name = 'buffer' }
}
})
-- Use cmdline & path source for ':'.
cmp.setup.cmdline(':', {
sources = cmp.config.sources({
{ name = 'path' }
}, {
{ name = 'cmdline' }
})
})
-- Setup lspconfig. -- Setup lspconfig.
local capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities()) local capabilities = require('cmp_nvim_lsp').update_capabilities(vim.lsp.protocol.make_client_capabilities())
require('lspconfig')[%YOUR_LSP_SERVER%].setup { require('lspconfig')[%YOUR_LSP_SERVER%].setup {
@@ -125,6 +146,7 @@ If you want to remove an option, you can set it to `false` instead.
Built in helper `cmd.mappings` are: Built in helper `cmd.mappings` are:
- *cmp.mapping(...)*
- *cmp.mapping.select_prev_item({ cmp.SelectBehavior.{Insert,Select} } })* - *cmp.mapping.select_prev_item({ cmp.SelectBehavior.{Insert,Select} } })*
- *cmp.mapping.select_next_item({ cmp.SelectBehavior.{Insert,Select} })* - *cmp.mapping.select_next_item({ cmp.SelectBehavior.{Insert,Select} })*
- *cmp.mapping.scroll_docs(number)* - *cmp.mapping.scroll_docs(number)*
@@ -133,7 +155,7 @@ Built in helper `cmd.mappings` are:
- *cmp.mapping.abort()* - *cmp.mapping.abort()*
- *cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })* - *cmp.mapping.confirm({ select = bool, behavior = cmp.ConfirmBehavior.{Insert,Replace} })*
You can configure `nvim-cmp` to use these `cmd.mappings` like this: You can configure `nvim-cmp` to use these `cmd.mapping` like this:
```lua ```lua
mapping = { mapping = {
@@ -162,6 +184,17 @@ mapping = {
} }
``` ```
One more addition, the mapping mode can be specified by table key. It's useful to specify different function for each modes.
```lua
mapping = {
['<CR>'] = cmp.mapping({
i = cmp.mapping.confirm({ select = true }),
c = cmp.mapping.confirm({ select = false }),
})
}
```
You can specify your own custom mapping function. You can specify your own custom mapping function.
```lua ```lua

View File

@@ -15,6 +15,9 @@ config.global = require('cmp.config.default')()
---@type table<number, cmp.ConfigSchema> ---@type table<number, cmp.ConfigSchema>
config.buffers = {} config.buffers = {}
---@type table<string, cmp.ConfigSchema>
config.cmdline = {}
---Set configuration for global. ---Set configuration for global.
---@param c cmp.ConfigSchema ---@param c cmp.ConfigSchema
config.set_global = function(c) config.set_global = function(c)
@@ -32,14 +35,29 @@ config.set_buffer = function(c, bufnr)
config.buffers[bufnr].revision = revision + 1 config.buffers[bufnr].revision = revision + 1
end end
---Set configuration for cmdline
config.set_cmdline = function(c, type)
local revision = (config.cmdline[type] or {}).revision or 1
config.cmdline[type] = c
config.cmdline[type].revision = revision + 1
end
---@return cmp.ConfigSchema ---@return cmp.ConfigSchema
config.get = function() config.get = function()
local global = config.global local global = config.global
local bufnr = vim.api.nvim_get_current_buf() if api.is_cmdline_mode() then
local buffer = config.buffers[bufnr] or { revision = 1 } local type = vim.fn.getcmdtype()
return config.cache:ensure({ 'get_buffer', bufnr, global.revision or 0, buffer.revision or 0 }, function() local cmdline = config.cmdline[type] or { revision = 1, sources = {} }
return misc.merge(buffer, global) return config.cache:ensure({ 'get_cmdline', type, global.revision or 0, cmdline.revision or 0 }, function()
end) return misc.merge(cmdline, global)
end)
else
local bufnr = vim.api.nvim_get_current_buf()
local buffer = config.buffers[bufnr] or { revision = 1 }
return config.cache:ensure({ 'get_buffer', bufnr, global.revision or 0, buffer.revision or 0 }, function()
return misc.merge(buffer, global)
end)
end
end end
---Return cmp is enabled or not. ---Return cmp is enabled or not.

View File

@@ -81,12 +81,28 @@ return function()
event = {}, event = {},
mapping = { mapping = {
['<Down>'] = mapping(mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }), { 'i' }), ['<Down>'] = mapping({
['<Up>'] = mapping(mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Select }), { 'i' }), i = mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Select }),
['<C-n>'] = mapping(mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i' }), c = function(fallback)
['<C-p>'] = mapping(mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i' }), local cmp = require('cmp')
['<C-y>'] = mapping(mapping.confirm({ select = false }), { 'i' }), cmp.close()
['<C-e>'] = mapping(mapping.abort(), { 'i' }), vim.schedule(cmp.suspend())
fallback()
end,
}),
['<Up>'] = mapping({
i = mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Select }),
c = function(fallback)
local cmp = require('cmp')
cmp.close()
vim.schedule(cmp.suspend())
fallback()
end,
}),
['<C-n>'] = mapping(mapping.select_next_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
['<C-p>'] = mapping(mapping.select_prev_item({ behavior = types.cmp.SelectBehavior.Insert }), { 'i', 'c' }),
['<C-y>'] = mapping.confirm({ select = false }),
['<C-e>'] = mapping.abort(),
}, },
formatting = { formatting = {

View File

@@ -1,3 +1,5 @@
local api = require('cmp.utils.api')
local mapping local mapping
mapping = setmetatable({}, { mapping = setmetatable({}, {
__call = function(_, invoke, modes) __call = function(_, invoke, modes)
@@ -7,9 +9,25 @@ mapping = setmetatable({}, {
invoke(...) invoke(...)
end, end,
modes = modes or { 'i' }, modes = modes or { 'i' },
__type = 'mapping',
} }
elseif type(invoke) == 'table' then
if invoke.__type == 'mapping' then
return invoke
else
return mapping(function(fallback)
if api.is_insert_mode() and invoke.i then
return invoke.i(fallback)
elseif api.is_cmdline_mode() and invoke.c then
return invoke.c(fallback)
elseif api.is_select_mode() and invoke.s then
return invoke.s(fallback)
else
fallback()
end
end, vim.tbl_keys(invoke))
end
end end
return invoke
end, end,
}) })
@@ -53,7 +71,9 @@ end
mapping.select_next_item = function(option) mapping.select_next_item = function(option)
return function(fallback) return function(fallback)
if not require('cmp').select_next_item(option) then if not require('cmp').select_next_item(option) then
local release = require('cmp').core:suspend()
fallback() fallback()
vim.schedule(release)
end end
end end
end end
@@ -62,7 +82,9 @@ end
mapping.select_prev_item = function(option) mapping.select_prev_item = function(option)
return function(fallback) return function(fallback)
if not require('cmp').select_prev_item(option) then if not require('cmp').select_prev_item(option) then
local release = require('cmp').core:suspend()
fallback() fallback()
vim.schedule(release)
end end
end end
end end

View File

@@ -70,10 +70,6 @@ cmp.close = function()
cmp.core:reset() cmp.core:reset()
vim.schedule(release) vim.schedule(release)
return true return true
elseif vim.fn.pumvisible() == 1 then
vim.fn.complete(1, {})
cmp.core:reset()
return true
else else
return false return false
end end
@@ -86,14 +82,16 @@ cmp.abort = function()
cmp.core.view:abort() cmp.core.view:abort()
vim.schedule(release) vim.schedule(release)
return true return true
elseif vim.fn.pumvisible() == 1 then
vim.api.nvim_select_popupmenu_item(-1, true, true, {})
return true
else else
return false return false
end end
end end
---Suspend completion.
cmp.suspend = function()
return cmp.core:suspend()
end
---Select next item if possible ---Select next item if possible
cmp.select_next_item = function(option) cmp.select_next_item = function(option)
option = option or {} option = option or {}
@@ -252,6 +250,9 @@ cmp.setup = setmetatable({
buffer = function(c) buffer = function(c)
config.set_buffer(c, vim.api.nvim_get_current_buf()) config.set_buffer(c, vim.api.nvim_get_current_buf())
end, end,
cmdline = function(type, c)
config.set_cmdline(c, type)
end,
}, { }, {
__call = function(self, c) __call = function(self, c)
self.global(c) self.global(c)

View File

@@ -267,7 +267,7 @@ source.complete = function(self, ctx, callback)
completion_context = { completion_context = {
triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions, triggerKind = types.lsp.CompletionTriggerKind.TriggerForIncompleteCompletions,
} }
elseif self.request_offset ~= offset then elseif not vim.tbl_contains({ self.request_offset, self.offset }, offset) then
completion_context = { completion_context = {
triggerKind = types.lsp.CompletionTriggerKind.Invoked, triggerKind = types.lsp.CompletionTriggerKind.Invoked,
} }
@@ -303,40 +303,37 @@ source.complete = function(self, ctx, callback)
option = self:get_config().opts, option = self:get_config().opts,
completion_context = completion_context, completion_context = completion_context,
}, },
async.timeout( self.complete_dedup(vim.schedule_wrap(misc.once(function(response)
self.complete_dedup(vim.schedule_wrap(function(response) if #((response or {}).items or response or {}) > 0 then
if #((response or {}).items or response or {}) > 0 then debug.log(self:get_debug_name(), 'retrieve', #(response.items or response))
debug.log(self:get_debug_name(), 'retrieve', #(response.items or response)) local old_offset = self.offset
local old_offset = self.offset local old_entries = self.entries
local old_entries = self.entries
self.status = source.SourceStatus.COMPLETED self.status = source.SourceStatus.COMPLETED
self.incomplete = response.isIncomplete or false self.incomplete = response.isIncomplete or false
self.entries = {} self.entries = {}
for i, item in ipairs(response.items or response) do for i, item in ipairs(response.items or response) do
if (misc.safe(item) or {}).label then if (misc.safe(item) or {}).label then
local e = entry.new(ctx, self, item) local e = entry.new(ctx, self, item)
self.entries[i] = e self.entries[i] = e
self.offset = math.min(self.offset, e:get_offset()) self.offset = math.min(self.offset, e:get_offset())
end
end end
self.revision = self.revision + 1
if #self:get_entries(ctx) == 0 then
self.offset = old_offset
self.entries = old_entries
self.revision = self.revision + 1
end
else
debug.log(self:get_debug_name(), 'continue', 'nil')
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
self:reset()
end
self.status = prev_status
end end
callback() self.revision = self.revision + 1
end)), if #self:get_entries(ctx) == 0 then
2000 self.offset = old_offset
) self.entries = old_entries
self.revision = self.revision + 1
end
else
debug.log(self:get_debug_name(), 'continue', 'nil')
if completion_context.triggerKind == types.lsp.CompletionTriggerKind.TriggerCharacter then
self:reset()
end
self.status = prev_status
end
callback()
end)))
) )
return true return true
end end

View File

@@ -9,10 +9,11 @@ api.is_insert_mode = function()
end end
api.is_cmdline_mode = function() api.is_cmdline_mode = function()
return vim.tbl_contains({ local is_cmdline_mode = vim.tbl_contains({
'c', 'c',
'cv', 'cv',
}, vim.api.nvim_get_mode().mode) }, vim.api.nvim_get_mode().mode)
return is_cmdline_mode and vim.fn.getcmdtype() ~= '='
end end
api.is_select_mode = function() api.is_select_mode = function()
@@ -41,16 +42,17 @@ api.get_cursor = function()
end end
api.get_screen_cursor = function() api.get_screen_cursor = function()
local cursor = api.get_cursor()
if api.is_cmdline_mode() then if api.is_cmdline_mode() then
return cursor return api.get_cursor()
end end
local cursor = api.get_cursor()
local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1) local pos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1)
return { pos.row, pos.col - 1 } return { pos.row, pos.col - 1 }
end end
api.get_cursor_before_line = function() api.get_cursor_before_line = function()
return string.sub(api.get_current_line(), 1, api.get_cursor()[2] + 1) local cursor = api.get_cursor()
return string.sub(api.get_current_line(), 1, cursor[2] + 1)
end end
return api return api

View File

@@ -64,7 +64,7 @@ keymap.to_keymap = setmetatable({
---Mode safe break undo ---Mode safe break undo
keymap.undobreak = function() keymap.undobreak = function()
if api.is_cmdline_mode() then if not api.is_insert_mode() then
return '' return ''
end end
return keymap.t('<C-g>u') return keymap.t('<C-g>u')
@@ -72,7 +72,7 @@ end
---Mode safe join undo ---Mode safe join undo
keymap.undojoin = function() keymap.undojoin = function()
if api.is_cmdline_mode() then if not api.is_insert_mode() then
return '' return ''
end end
return keymap.t('<C-g>U') return keymap.t('<C-g>U')
@@ -244,12 +244,12 @@ keymap.listen = setmetatable({
misc.set(_G, { 'cmp', 'utils', 'keymap', 'listen', 'run' }, function(id) misc.set(_G, { 'cmp', 'utils', 'keymap', 'listen', 'run' }, function(id)
local definition = keymap.listen.cache:get({ 'definition', id }) local definition = keymap.listen.cache:get({ 'definition', id })
if definition.mode == 'c' and vim.fn.getcmdtype() == '=' then if definition.mode == 'c' and vim.fn.getcmdtype() == '=' then
return vim.api.nvim_feedkeys(keymap.t(definition.fallback), 'it', true) return vim.api.nvim_feedkeys(keymap.t(definition.fallback.keys), definition.fallback.mode, true)
end end
definition.callback( definition.callback(
definition.keys, definition.keys,
misc.once(function() misc.once(function()
vim.api.nvim_feedkeys(keymap.t(definition.fallback), 'it', true) vim.api.nvim_feedkeys(keymap.t(definition.fallback.keys), definition.fallback.mode, true)
end) end)
) )
return keymap.t('<Ignore>') return keymap.t('<Ignore>')
@@ -258,18 +258,27 @@ end)
---Evacuate existing key mapping ---Evacuate existing key mapping
---@param mode string ---@param mode string
---@param lhs string ---@param lhs string
---@return string ---@return { keys: string, mode: string }
keymap.evacuate = function(mode, lhs) keymap.evacuate = function(mode, lhs)
local map = keymap.find_map_by_lhs(mode, lhs) local map = keymap.find_map_by_lhs(mode, lhs)
-- Keep existing mapping as <Plug> mapping. We escape fisrt recursive key sequence. See `:help recursive_mapping`) -- Keep existing mapping as <Plug> mapping. We escape fisrt recursive key sequence. See `:help recursive_mapping`)
local rhs = map.rhs local rhs = map.rhs
if map.noremap == 0 then if map.noremap == 0 and map.expr == 1 then
if map.expr == 1 then -- remap & expr mapping should evacuate as <Plug> mapping with solving recursive mapping.
rhs = string.format('v:lua.cmp.utils.keymap.evacuate.expr("%s", "%s", "%s")', mode, str.escape(keymap.escape(lhs), { '"' }), str.escape(keymap.escape(rhs), { '"' })) rhs = string.format('v:lua.cmp.utils.keymap.evacuate.expr("%s", "%s", "%s")', mode, str.escape(keymap.escape(lhs), { '"' }), str.escape(keymap.escape(rhs), { '"' }))
else elseif map.noremap ~= 0 and map.expr == 1 then
rhs = keymap.recursive(mode, lhs, rhs) -- noremap & expr mapping should always evacuate as <Plug> mapping.
rhs = rhs
elseif map.noremap == 0 then
-- remap & non-expr mapping should be checked if recursive or not.
rhs = keymap.recursive(mode, lhs, rhs)
if rhs == map.rhs or map.noremap ~= 0 then
return { keys = rhs, mode = 'it' .. (map.noremap == 1 and 'n' or '') }
end end
else
-- noremap & non-expr mapping doesn't need to evacuate.
return { keys = rhs, mode = 'it' .. (map.noremap == 1 and 'n' or '') }
end end
local fallback = ('<Plug>(cmp-utils-keymap-evacuate-rhs:%s)'):format(map.lhs) local fallback = ('<Plug>(cmp-utils-keymap-evacuate-rhs:%s)'):format(map.lhs)
@@ -280,7 +289,7 @@ keymap.evacuate = function(mode, lhs)
silent = true, silent = true,
nowait = true, nowait = true,
}) })
return fallback return { keys = fallback, mode = 'it' }
end end
misc.set(_G, { 'cmp', 'utils', 'keymap', 'evacuate', 'expr' }, function(mode, lhs, rhs) misc.set(_G, { 'cmp', 'utils', 'keymap', 'evacuate', 'expr' }, function(mode, lhs, rhs)
return keymap.t(keymap.recursive(mode, lhs, vim.api.nvim_eval(rhs))) return keymap.t(keymap.recursive(mode, lhs, vim.api.nvim_eval(rhs)))

View File

@@ -43,7 +43,7 @@ describe('keymap', function()
noremap = false, noremap = false,
}) })
local fallback = keymap.evacuate('i', '(') local fallback = keymap.evacuate('i', '(')
vim.api.nvim_feedkeys('i' .. keymap.t(fallback), 'x', true) vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
assert.are.same({ '(' }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) assert.are.same({ '(' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
end) end)
@@ -57,7 +57,7 @@ describe('keymap', function()
noremap = false, noremap = false,
}) })
local fallback = keymap.evacuate('i', '(') local fallback = keymap.evacuate('i', '(')
vim.api.nvim_feedkeys('i' .. keymap.t(fallback), 'x', true) vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
assert.are.same({ '()' }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) assert.are.same({ '()' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
end) end)
@@ -70,7 +70,7 @@ describe('keymap', function()
noremap = false, noremap = false,
}) })
local fallback = keymap.evacuate('i', '<Tab>') local fallback = keymap.evacuate('i', '<Tab>')
vim.api.nvim_feedkeys('i' .. keymap.t(fallback), 'x', true) vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
assert.are.same({ 'foobar' }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) assert.are.same({ 'foobar' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
end) end)
it('false', function() it('false', function()
@@ -79,7 +79,7 @@ describe('keymap', function()
noremap = false, noremap = false,
}) })
local fallback = keymap.evacuate('i', '<Tab>') local fallback = keymap.evacuate('i', '<Tab>')
vim.api.nvim_feedkeys('i' .. keymap.t(fallback), 'x', true) vim.api.nvim_feedkeys('i' .. keymap.t(fallback.keys), fallback.mode .. 'x', true)
assert.are.same({ '\taiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true)) assert.are.same({ '\taiueo' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
end) end)
end) end)

View File

@@ -5,12 +5,12 @@ local misc = {}
---@return function ---@return function
misc.once = function(callback) misc.once = function(callback)
local done = false local done = false
return function() return function(...)
if done then if done then
return return
end end
done = true done = true
callback() callback(...)
end end
end end

View File

@@ -1,6 +1,7 @@
local cache = require('cmp.utils.cache') local cache = require('cmp.utils.cache')
local misc = require('cmp.utils.misc') local misc = require('cmp.utils.misc')
local buffer = require('cmp.utils.buffer') local buffer = require('cmp.utils.buffer')
local api = require('cmp.utils.api')
---@class cmp.WindowStyle ---@class cmp.WindowStyle
---@field public relative string ---@field public relative string
@@ -31,7 +32,6 @@ window.new = function()
self.style = {} self.style = {}
self.cache = cache.new() self.cache = cache.new()
self.opt = {} self.opt = {}
self.id = 0
return self return self
end end
@@ -76,8 +76,6 @@ end
---Open window ---Open window
---@param style cmp.WindowStyle ---@param style cmp.WindowStyle
window.open = function(self, style) window.open = function(self, style)
self.id = self.id + 1
if style then if style then
self:set_style(style) self:set_style(style)
end end
@@ -146,29 +144,31 @@ window.update = function(self)
self.swin2 = nil self.swin2 = nil
end end
end end
-- In cmdline, vim does not redraw automatically.
if api.is_cmdline_mode() then
vim.api.nvim_win_call(self.win, function()
vim.cmd([[redraw]])
end)
end
end end
---Close window ---Close window
window.close = function(self) window.close = function(self)
local id = self.id if self.win and vim.api.nvim_win_is_valid(self.win) then
vim.schedule(function() if self.win and vim.api.nvim_win_is_valid(self.win) then
if id == self.id then vim.api.nvim_win_hide(self.win)
if self.win and vim.api.nvim_win_is_valid(self.win) then self.win = nil
if self.win and vim.api.nvim_win_is_valid(self.win) then
vim.api.nvim_win_hide(self.win)
self.win = nil
end
if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
vim.api.nvim_win_hide(self.swin1)
self.swin1 = nil
end
if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
vim.api.nvim_win_hide(self.swin2)
self.swin2 = nil
end
end
end end
end) if self.swin1 and vim.api.nvim_win_is_valid(self.swin1) then
vim.api.nvim_win_hide(self.swin1)
self.swin1 = nil
end
if self.swin2 and vim.api.nvim_win_is_valid(self.swin2) then
vim.api.nvim_win_hide(self.swin2)
self.swin2 = nil
end
end
end end
---Return the window is visible or not. ---Return the window is visible or not.

View File

@@ -6,6 +6,8 @@ local types = require('cmp.types')
local keymap = require('cmp.utils.keymap') local keymap = require('cmp.utils.keymap')
local api = require('cmp.utils.api') local api = require('cmp.utils.api')
local SIDE_PADDING = 1
---@class cmp.CustomEntriesView ---@class cmp.CustomEntriesView
---@field private entries_win cmp.Window ---@field private entries_win cmp.Window
---@field private offset number ---@field private offset number
@@ -50,7 +52,7 @@ custom_entries_view.new = function()
local e = self.entries[i + 1] local e = self.entries[i + 1]
if e then if e then
local v = e:get_view(self.offset) local v = e:get_view(self.offset)
local o = 1 local o = SIDE_PADDING
local a = 0 local a = 0
for _, field in ipairs(fields) do for _, field in ipairs(fields) do
if field == types.cmp.ItemField.Abbr then if field == types.cmp.ItemField.Abbr then
@@ -133,6 +135,7 @@ custom_entries_view.open = function(self, offset, entries)
local height = vim.api.nvim_get_option('pumheight') local height = vim.api.nvim_get_option('pumheight')
height = height == 0 and #self.entries or height height = height == 0 and #self.entries or height
height = math.min(height, #self.entries) height = math.min(height, #self.entries)
if (vim.o.lines - pos[1]) <= 8 and pos[1] - 8 > 0 then if (vim.o.lines - pos[1]) <= 8 and pos[1] - 8 > 0 then
height = math.min(height, pos[1] - 1) height = math.min(height, pos[1] - 1)
pos[1] = pos[1] - height - 1 pos[1] = pos[1] - height - 1
@@ -151,8 +154,8 @@ custom_entries_view.open = function(self, offset, entries)
self.entries_win:open({ self.entries_win:open({
relative = 'editor', relative = 'editor',
style = 'minimal', style = 'minimal',
row = row, row = math.max(0, row),
col = col, col = math.max(0, col),
width = width, width = width,
height = height, height = height,
zindex = 1001, zindex = 1001,
@@ -196,16 +199,22 @@ custom_entries_view.draw = function(self)
if e then if e then
local view = e:get_view(self.offset) local view = e:get_view(self.offset)
local text = {} local text = {}
table.insert(text, ' ') table.insert(text, string.rep(' ', SIDE_PADDING))
for _, field in ipairs(fields) do for _, field in ipairs(fields) do
table.insert(text, view[field].text) table.insert(text, view[field].text)
table.insert(text, string.rep(' ', 1 + self.column_width[field] - view[field].width)) table.insert(text, string.rep(' ', 1 + self.column_width[field] - view[field].width))
end end
table.insert(text, ' ') table.insert(text, string.rep(' ', SIDE_PADDING))
table.insert(texts, table.concat(text, '')) table.insert(texts, table.concat(text, ''))
end end
end end
vim.api.nvim_buf_set_lines(self.entries_win:get_buffer(), topline, botline, false, texts) vim.api.nvim_buf_set_lines(self.entries_win:get_buffer(), topline, botline, false, texts)
if api.is_cmdline_mode() then
vim.api.nvim_win_call(self.entries_win.win, function()
vim.cmd([[redraw]])
end)
end
end end
custom_entries_view.visible = function(self) custom_entries_view.visible = function(self)
@@ -282,20 +291,33 @@ custom_entries_view._select = function(self, cursor, option)
vim.api.nvim_win_set_cursor(self.entries_win.win, { math.max(cursor, 1), is_insert and 0 or 1 }) vim.api.nvim_win_set_cursor(self.entries_win.win, { math.max(cursor, 1), is_insert and 0 or 1 })
if is_insert then if is_insert then
self:_insert(self.entries[cursor] and self.entries[cursor]:get_vim_item(self.offset).word or self.prefix) self:_insert(self.entries[cursor] and self.entries[cursor]:get_vim_item(self.offset).word or self.prefix or '')
end end
self.entries_win:update() self.entries_win:update()
self:draw() self:draw()
self.event:emit('change') self.event:emit('change')
end end
custom_entries_view._insert = function(self, word) custom_entries_view._insert = function(self, word)
keymap.feedkeys('', 'n', function() if api.is_cmdline_mode() then
local release = require('cmp').core:suspend()
local cursor = api.get_cursor() local cursor = api.get_cursor()
local length = vim.str_utfindex(string.sub(api.get_current_line(), self.offset, cursor[2])) local length = vim.str_utfindex(string.sub(api.get_current_line(), self.offset, cursor[2]))
keymap.feedkeys(keymap.backspace(length) .. word, 'int', vim.schedule_wrap(release)) vim.api.nvim_feedkeys(keymap.backspace(length) .. word, 'int', true)
end) else
local release = require('cmp').core:suspend()
keymap.feedkeys('', 'n', function()
local cursor = api.get_cursor()
local length = vim.str_utfindex(string.sub(api.get_current_line(), self.offset, cursor[2]))
keymap.feedkeys(
keymap.backspace(length) .. word,
'int',
vim.schedule_wrap(function()
release()
end)
)
end)
end
end end
return custom_entries_view return custom_entries_view

View File

@@ -5,11 +5,12 @@ vim.g.loaded_cmp = true
local api = require "cmp.utils.api" local api = require "cmp.utils.api"
local misc = require('cmp.utils.misc') local misc = require('cmp.utils.misc')
local config = require('cmp.config')
local highlight = require('cmp.utils.highlight') local highlight = require('cmp.utils.highlight')
-- TODO: https://github.com/neovim/neovim/pull/14661 -- TODO: https://github.com/neovim/neovim/pull/14661
vim.cmd [[ vim.cmd [[
augroup cmp augroup ___cmp___
autocmd! autocmd!
autocmd InsertEnter * lua require'cmp.utils.autocmd'.emit('InsertEnter') autocmd InsertEnter * lua require'cmp.utils.autocmd'.emit('InsertEnter')
autocmd InsertLeave * lua require'cmp.utils.autocmd'.emit('InsertLeave') autocmd InsertLeave * lua require'cmp.utils.autocmd'.emit('InsertLeave')
@@ -18,9 +19,42 @@ vim.cmd [[
autocmd CompleteChanged * lua require'cmp.utils.autocmd'.emit('CompleteChanged') autocmd CompleteChanged * lua require'cmp.utils.autocmd'.emit('CompleteChanged')
autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone') autocmd CompleteDone * lua require'cmp.utils.autocmd'.emit('CompleteDone')
autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme() autocmd ColorScheme * call v:lua.cmp.plugin.colorscheme()
autocmd CmdlineEnter * call v:lua.cmp.plugin.cmdline.enter()
autocmd CmdlineLeave * call v:lua.cmp.plugin.cmdline.leave()
augroup END augroup END
]] ]]
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'enter' }, function()
if config.get().experimental.native_menu then
return
end
local cmdtype = vim.fn.expand('<afile>')
if cmdtype ~= '=' then
vim.cmd [[
augroup cmp-cmdline
autocmd!
autocmd CmdlineChanged * lua require'cmp.utils.autocmd'.emit('TextChanged')
augroup END
]]
require('cmp.utils.autocmd').emit('InsertEnter')
end
end)
misc.set(_G, { 'cmp', 'plugin', 'cmdline', 'leave' }, function()
if config.get().experimental.native_menu then
return
end
local cmdtype = vim.fn.expand('<afile>')
if cmdtype ~= '=' then
vim.cmd [[
augroup cmp-cmdline
autocmd!
augroup END
]]
require('cmp.utils.autocmd').emit('InsertLeave')
end
end)
misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function() misc.set(_G, { 'cmp', 'plugin', 'colorscheme' }, function()
highlight.inherit('CmpItemAbbrDefault', 'Pmenu', { highlight.inherit('CmpItemAbbrDefault', 'Pmenu', {
guibg = 'NONE', guibg = 'NONE',