mirror of
https://github.com/hrsh7th/nvim-cmp
synced 2024-09-16 20:54:03 +02:00
start organize
This commit is contained in:
parent
9efea38255
commit
de53527063
19 changed files with 484 additions and 963 deletions
|
@ -102,7 +102,7 @@ lua <<EOF
|
|||
})
|
||||
})
|
||||
|
||||
-- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
|
||||
-- Use buffer source for `/` and `?`.
|
||||
cmp.setup.cmdline({ '/', '?' }, {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = {
|
||||
|
@ -110,7 +110,7 @@ lua <<EOF
|
|||
}
|
||||
})
|
||||
|
||||
-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
|
||||
-- Use cmdline & path source for ':'.
|
||||
cmp.setup.cmdline(':', {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = cmp.config.sources({
|
||||
|
|
10
doc/cmp.txt
10
doc/cmp.txt
|
@ -42,8 +42,7 @@ Usage *cmp-usage*
|
|||
A recommended configuration can be found below.
|
||||
NOTE:
|
||||
1. You must provide a `snippet.expand` function.
|
||||
2. `cmp.setup.cmdline` won't work if you use the `native` completion menu.
|
||||
3. You can disable the `default` options by specifying `cmp.config.disable` value.
|
||||
2. You can disable the `default` options by specifying `cmp.config.disable` value.
|
||||
>vim
|
||||
call plug#begin(s:plug_dir)
|
||||
Plug 'neovim/nvim-lspconfig'
|
||||
|
@ -596,15 +595,10 @@ sources[n].entry_filter~
|
|||
Using the `ctx` parameter, you can further customize the behaviour of the
|
||||
source.
|
||||
|
||||
*cmp-config.view*
|
||||
view~
|
||||
`{ entries: cmp.CustomEntriesConfig|NativeEntriesConfig|cmp.WildmenuEntriesConfig|string }`
|
||||
The view class used to customize nvim-cmp's appearance.
|
||||
|
||||
*cmp-config.window.{completion,documentation}.border*
|
||||
window.{completion,documentation}.border~
|
||||
`string | string[] | nil`
|
||||
Border characters used for the completion popup menu when |experimental.native_menu| is disabled.
|
||||
Border characters used for the completion popup menu.
|
||||
See |nvim_open_win|.
|
||||
|
||||
*cmp-config.window.{completion,documentation}.winhighlight*
|
||||
|
|
|
@ -158,16 +158,7 @@ config.get_source_config = function(name)
|
|||
return s
|
||||
end
|
||||
end
|
||||
error('Specified source is not found: ' .. name)
|
||||
end
|
||||
|
||||
---Return the current menu is native or not.
|
||||
config.is_native_menu = function()
|
||||
local c = config.get()
|
||||
if c.view and c.view.entries then
|
||||
return c.view.entries == 'native' or c.view.entries.name == 'native'
|
||||
end
|
||||
return false
|
||||
return {}
|
||||
end
|
||||
|
||||
---Normalize mapping key
|
||||
|
@ -187,21 +178,6 @@ config.normalize = function(c)
|
|||
c.mapping = normalized
|
||||
end
|
||||
|
||||
-- Notice experimental.native_menu.
|
||||
if c.experimental and c.experimental.native_menu then
|
||||
echo({
|
||||
'[nvim-cmp] ',
|
||||
{ 'experimental.native_menu', 'WarningMsg' },
|
||||
' is deprecated.\n',
|
||||
'[nvim-cmp] Please use ',
|
||||
{ 'view.entries = "native"', 'WarningMsg' },
|
||||
' instead.',
|
||||
})
|
||||
|
||||
c.view = c.view or {}
|
||||
c.view.entries = c.view.entries or 'native'
|
||||
end
|
||||
|
||||
-- Notice documentation.
|
||||
if c.documentation ~= nil then
|
||||
echo({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local types = require('cmp.types')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
|
||||
local function merge_keymaps(base, override)
|
||||
|
@ -66,7 +66,7 @@ mapping.preset.cmdline = function(override)
|
|||
if cmp.visible() then
|
||||
cmp.select_next_item()
|
||||
else
|
||||
feedkeys.call(keymap.t('<C-z>'), 'n')
|
||||
Keymap.send(keymap.t('<C-z>'), 'n')
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ mapping.preset.cmdline = function(override)
|
|||
if cmp.visible() then
|
||||
cmp.select_prev_item()
|
||||
else
|
||||
feedkeys.call(keymap.t('<C-z>'), 'n')
|
||||
Keymap.send(keymap.t('<C-z>'), 'n')
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
@ -154,22 +154,24 @@ end
|
|||
|
||||
---Select next completion item.
|
||||
mapping.select_next_item = function(option)
|
||||
local cmp = require('cmp')
|
||||
return function(fallback)
|
||||
if not require('cmp').select_next_item(option) then
|
||||
local release = require('cmp').core:suspend()
|
||||
if cmp.visible() then
|
||||
cmp.select_next_item(option):await()
|
||||
else
|
||||
fallback()
|
||||
vim.schedule(release)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Select prev completion item.
|
||||
mapping.select_prev_item = function(option)
|
||||
local cmp = require('cmp')
|
||||
return function(fallback)
|
||||
if not require('cmp').select_prev_item(option) then
|
||||
local release = require('cmp').core:suspend()
|
||||
if cmp.visible() then
|
||||
cmp.select_prev_item(option):await()
|
||||
else
|
||||
fallback()
|
||||
vim.schedule(release)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -177,7 +179,7 @@ end
|
|||
---Confirm selection
|
||||
mapping.confirm = function(option)
|
||||
return function(fallback)
|
||||
if not require('cmp').confirm(option) then
|
||||
if not require('cmp').confirm(option):await() then
|
||||
fallback()
|
||||
end
|
||||
end
|
||||
|
|
309
lua/cmp/core.lua
309
lua/cmp/core.lua
|
@ -2,7 +2,6 @@ local debug = require('cmp.utils.debug')
|
|||
local str = require('cmp.utils.str')
|
||||
local char = require('cmp.utils.char')
|
||||
local pattern = require('cmp.utils.pattern')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local async = require('cmp.utils.async')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local context = require('cmp.context')
|
||||
|
@ -13,6 +12,8 @@ local config = require('cmp.config')
|
|||
local types = require('cmp.types')
|
||||
local api = require('cmp.utils.api')
|
||||
local event = require('cmp.utils.event')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
||||
|
||||
---@class cmp.Core
|
||||
---@field public suspending boolean
|
||||
|
@ -73,10 +74,12 @@ end
|
|||
---Suspend completion
|
||||
core.suspend = function(self)
|
||||
self.suspending = true
|
||||
-- It's needed to avoid conflicting with autocmd debouncing.
|
||||
return vim.schedule_wrap(function()
|
||||
-- Basically used to skip unexpected TextChanged/CmdlineChanged autocmd.
|
||||
-- 1. The autocmd fires after script processing, so schedule is necessary
|
||||
-- 2. The CmdlineChanged uses schedule for debouncing, so schedule is needed here additionally.
|
||||
return vim.schedule_wrap(vim.schedule_wrap(function()
|
||||
self.suspending = false
|
||||
end)
|
||||
end))
|
||||
end
|
||||
|
||||
---Get sources that sorted by priority
|
||||
|
@ -125,7 +128,7 @@ core.on_keymap = function(self, keys, fallback)
|
|||
}, function()
|
||||
local ctx = self:get_context()
|
||||
local word = e:get_word()
|
||||
if string.sub(ctx.cursor_before_line, -#word, ctx.cursor.col - 1) == word and is_printable then
|
||||
if string.sub(ctx.cursor_before_line, - #word, ctx.cursor.col - 1) == word and is_printable then
|
||||
fallback()
|
||||
else
|
||||
self:reset()
|
||||
|
@ -220,9 +223,10 @@ core.autoindent = function(self, trigger_event, callback)
|
|||
end
|
||||
|
||||
---Complete common string for current completed entries.
|
||||
---@return cmp.kit.Async.AsyncTask boolean
|
||||
core.complete_common_string = function(self)
|
||||
if not self.view:visible() or self.view:get_selected_entry() then
|
||||
return false
|
||||
return AsyncTask.resolve(false)
|
||||
end
|
||||
|
||||
config.set_onetime({
|
||||
|
@ -252,11 +256,14 @@ core.complete_common_string = function(self)
|
|||
end
|
||||
local cursor_before_line = api.get_cursor_before_line()
|
||||
local pretext = cursor_before_line:sub(offset)
|
||||
if common_string and #common_string > #pretext then
|
||||
feedkeys.call(keymap.backspace(pretext) .. common_string, 'n')
|
||||
return true
|
||||
end
|
||||
return false
|
||||
return AsyncTask.resolve():next(function()
|
||||
if common_string and #common_string > #pretext then
|
||||
return Keymap.send(keymap.backspace(pretext) .. common_string, 'ni'):next(function()
|
||||
return true
|
||||
end)
|
||||
end
|
||||
return false
|
||||
end)
|
||||
end
|
||||
|
||||
---Invoke completion
|
||||
|
@ -341,13 +348,10 @@ end, config.get().performance.throttle)
|
|||
---Confirm completion.
|
||||
---@param e cmp.Entry
|
||||
---@param option cmp.ConfirmOption
|
||||
---@param callback function
|
||||
core.confirm = function(self, e, option, callback)
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
core.confirm = function(self, e, option)
|
||||
if not (e and not e.confirmed) then
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
return
|
||||
return AsyncTask.resolve()
|
||||
end
|
||||
e.confirmed = true
|
||||
|
||||
|
@ -358,143 +362,146 @@ core.confirm = function(self, e, option, callback)
|
|||
-- Close menus.
|
||||
self.view:close()
|
||||
|
||||
feedkeys.call(keymap.indentkeys(), 'n')
|
||||
feedkeys.call('', 'n', function()
|
||||
-- Emulate `<C-y>` behavior to save `.` register.
|
||||
local ctx = context.new()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
||||
table.insert(keys, e:get_word())
|
||||
table.insert(keys, keymap.undobreak())
|
||||
feedkeys.call(table.concat(keys, ''), 'in')
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
-- Restore the line at the time of request.
|
||||
local ctx = context.new()
|
||||
if api.is_cmdline_mode() then
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
||||
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
|
||||
feedkeys.call(table.concat(keys, ''), 'in')
|
||||
else
|
||||
vim.cmd([[silent! undojoin]])
|
||||
-- This logic must be used nvim_buf_set_text.
|
||||
-- If not used, the snippet engine's placeholder wil be broken.
|
||||
vim.api.nvim_buf_set_text(0, e.context.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
|
||||
e.context.cursor_before_line:sub(e:get_offset()),
|
||||
})
|
||||
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
|
||||
end
|
||||
end)
|
||||
feedkeys.call('', 'n', function()
|
||||
-- Apply additionalTextEdits.
|
||||
local ctx = context.new()
|
||||
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
||||
e:resolve(function()
|
||||
local new = context.new()
|
||||
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
||||
if #text_edits == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local has_cursor_line_text_edit = (function()
|
||||
local minrow = math.min(ctx.cursor.row, new.cursor.row)
|
||||
local maxrow = math.max(ctx.cursor.row, new.cursor.row)
|
||||
for _, te in ipairs(text_edits) do
|
||||
local srow = te.range.start.line + 1
|
||||
local erow = te.range['end'].line + 1
|
||||
if srow <= minrow and maxrow <= erow then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)()
|
||||
if has_cursor_line_text_edit then
|
||||
return
|
||||
end
|
||||
vim.cmd([[silent! undojoin]])
|
||||
vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind())
|
||||
local indentkeys = vim.bo.indentkeys
|
||||
return AsyncTask.resolve()
|
||||
:next(function()
|
||||
return Keymap.send(keymap.indentkeys(), 'ni')
|
||||
end)
|
||||
else
|
||||
vim.cmd([[silent! undojoin]])
|
||||
vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind())
|
||||
end
|
||||
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
|
||||
completion_item.textEdit = {}
|
||||
completion_item.textEdit.newText = misc.safe(completion_item.insertText) or completion_item.word or completion_item.label
|
||||
end
|
||||
local behavior = option.behavior or config.get().confirmation.default_behavior
|
||||
if behavior == types.cmp.ConfirmBehavior.Replace then
|
||||
completion_item.textEdit.range = e:get_replace_range()
|
||||
else
|
||||
completion_item.textEdit.range = e:get_insert_range()
|
||||
end
|
||||
:next(function()
|
||||
-- Emulate `<C-y>` behavior to save `.` register.
|
||||
local ctx = context.new()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
||||
table.insert(keys, e:get_word())
|
||||
table.insert(keys, keymap.undobreak())
|
||||
return Keymap.send(table.concat(keys, ''), 'ni')
|
||||
end)
|
||||
:next(function()
|
||||
-- Restore the line at the time of request.
|
||||
local ctx = context.new()
|
||||
if api.is_cmdline_mode() then
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
||||
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
|
||||
return Keymap.send(table.concat(keys, ''), 'ni')
|
||||
else
|
||||
vim.cmd([[silent! undojoin]])
|
||||
-- This logic must be used nvim_buf_set_text.
|
||||
-- If not used, the snippet engine's placeholder wil be broken.
|
||||
vim.api.nvim_buf_set_text(0, e.context.cursor.row - 1, e:get_offset() - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
|
||||
e.context.cursor_before_line:sub(e:get_offset()),
|
||||
})
|
||||
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
|
||||
end
|
||||
end)
|
||||
:next(function()
|
||||
-- Apply additionalTextEdits.
|
||||
local ctx = context.new()
|
||||
if #(misc.safe(e:get_completion_item().additionalTextEdits) or {}) == 0 then
|
||||
e:resolve(function()
|
||||
local new = context.new()
|
||||
local text_edits = misc.safe(e:get_completion_item().additionalTextEdits) or {}
|
||||
if #text_edits == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
|
||||
local diff_after = math.max(0, (completion_item.textEdit.range['end'].character + 1) - e.context.cursor.col)
|
||||
local new_text = completion_item.textEdit.newText
|
||||
completion_item.textEdit.range.start.line = ctx.cursor.line
|
||||
completion_item.textEdit.range.start.character = (ctx.cursor.col - 1) - diff_before
|
||||
completion_item.textEdit.range['end'].line = ctx.cursor.line
|
||||
completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after
|
||||
if api.is_insert_mode() then
|
||||
if false then
|
||||
--To use complex expansion debug.
|
||||
vim.pretty_print({ -- luacheck: ignore
|
||||
diff_before = diff_before,
|
||||
diff_after = diff_after,
|
||||
new_text = new_text,
|
||||
text_edit_new_text = completion_item.textEdit.newText,
|
||||
range_start = completion_item.textEdit.range.start.character,
|
||||
range_end = completion_item.textEdit.range['end'].character,
|
||||
original_range_start = e:get_completion_item().textEdit.range.start.character,
|
||||
original_range_end = e:get_completion_item().textEdit.range['end'].character,
|
||||
cursor_line = ctx.cursor_line,
|
||||
cursor_col0 = ctx.cursor.col - 1,
|
||||
})
|
||||
end
|
||||
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
|
||||
if is_snippet then
|
||||
completion_item.textEdit.newText = ''
|
||||
end
|
||||
vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8')
|
||||
local has_cursor_line_text_edit = (function()
|
||||
local minrow = math.min(ctx.cursor.row, new.cursor.row)
|
||||
local maxrow = math.max(ctx.cursor.row, new.cursor.row)
|
||||
for _, te in ipairs(text_edits) do
|
||||
local srow = te.range.start.line + 1
|
||||
local erow = te.range['end'].line + 1
|
||||
if srow <= minrow and maxrow <= erow then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)()
|
||||
if has_cursor_line_text_edit then
|
||||
return
|
||||
end
|
||||
vim.cmd([[silent! undojoin]])
|
||||
vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, e.source:get_position_encoding_kind())
|
||||
end)
|
||||
else
|
||||
vim.cmd([[silent! undojoin]])
|
||||
vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind())
|
||||
end
|
||||
end)
|
||||
:next(function()
|
||||
local ctx = context.new()
|
||||
local completion_item = misc.copy(e:get_completion_item())
|
||||
if not misc.safe(completion_item.textEdit) then
|
||||
completion_item.textEdit = {}
|
||||
completion_item.textEdit.newText = misc.safe(completion_item.insertText) or completion_item.word or completion_item.label
|
||||
end
|
||||
local behavior = option.behavior or config.get().confirmation.default_behavior
|
||||
if behavior == types.cmp.ConfirmBehavior.Replace then
|
||||
completion_item.textEdit.range = e:get_replace_range()
|
||||
else
|
||||
completion_item.textEdit.range = e:get_insert_range()
|
||||
end
|
||||
|
||||
local texts = vim.split(completion_item.textEdit.newText, '\n')
|
||||
vim.api.nvim_win_set_cursor(0, {
|
||||
completion_item.textEdit.range.start.line + #texts,
|
||||
(#texts == 1 and (completion_item.textEdit.range.start.character + #texts[1]) or #texts[#texts]),
|
||||
})
|
||||
if is_snippet then
|
||||
config.get().snippet.expand({
|
||||
body = new_text,
|
||||
insert_text_mode = completion_item.insertTextMode,
|
||||
})
|
||||
end
|
||||
else
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1)))
|
||||
table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character)))
|
||||
table.insert(keys, new_text)
|
||||
feedkeys.call(table.concat(keys, ''), 'in')
|
||||
end
|
||||
end)
|
||||
feedkeys.call(keymap.indentkeys(vim.bo.indentkeys), 'n')
|
||||
feedkeys.call('', 'n', function()
|
||||
e:execute(vim.schedule_wrap(function()
|
||||
release()
|
||||
self.event:emit('confirm_done', {
|
||||
entry = e,
|
||||
commit_character = option.commit_character,
|
||||
})
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end))
|
||||
end)
|
||||
local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
|
||||
local diff_after = math.max(0, (completion_item.textEdit.range['end'].character + 1) - e.context.cursor.col)
|
||||
local new_text = completion_item.textEdit.newText
|
||||
completion_item.textEdit.range.start.line = ctx.cursor.line
|
||||
completion_item.textEdit.range.start.character = (ctx.cursor.col - 1) - diff_before
|
||||
completion_item.textEdit.range['end'].line = ctx.cursor.line
|
||||
completion_item.textEdit.range['end'].character = (ctx.cursor.col - 1) + diff_after
|
||||
if api.is_insert_mode() then
|
||||
if false then
|
||||
--To use complex expansion debug.
|
||||
vim.pretty_print({ -- luacheck: ignore
|
||||
diff_before = diff_before,
|
||||
diff_after = diff_after,
|
||||
new_text = new_text,
|
||||
text_edit_new_text = completion_item.textEdit.newText,
|
||||
range_start = completion_item.textEdit.range.start.character,
|
||||
range_end = completion_item.textEdit.range['end'].character,
|
||||
original_range_start = e:get_completion_item().textEdit.range.start.character,
|
||||
original_range_end = e:get_completion_item().textEdit.range['end'].character,
|
||||
cursor_line = ctx.cursor_line,
|
||||
cursor_col0 = ctx.cursor.col - 1,
|
||||
})
|
||||
end
|
||||
local is_snippet = completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet
|
||||
if is_snippet then
|
||||
completion_item.textEdit.newText = ''
|
||||
end
|
||||
vim.lsp.util.apply_text_edits({ completion_item.textEdit }, ctx.bufnr, 'utf-8')
|
||||
|
||||
local texts = vim.split(completion_item.textEdit.newText, '\n')
|
||||
vim.api.nvim_win_set_cursor(0, {
|
||||
completion_item.textEdit.range.start.line + #texts,
|
||||
(#texts == 1 and (completion_item.textEdit.range.start.character + #texts[1]) or #texts[#texts]),
|
||||
})
|
||||
if is_snippet then
|
||||
config.get().snippet.expand({
|
||||
body = new_text,
|
||||
insert_text_mode = completion_item.insertTextMode,
|
||||
})
|
||||
end
|
||||
else
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.backspace(ctx.cursor_line:sub(completion_item.textEdit.range.start.character + 1, ctx.cursor.col - 1)))
|
||||
table.insert(keys, keymap.delete(ctx.cursor_line:sub(ctx.cursor.col, completion_item.textEdit.range['end'].character)))
|
||||
table.insert(keys, new_text)
|
||||
return Keymap.send(table.concat(keys, ''), 'ni')
|
||||
end
|
||||
end)
|
||||
:next(function()
|
||||
return Keymap.send(keymap.indentkeys(indentkeys), 'ni')
|
||||
end)
|
||||
:next(function()
|
||||
e:execute(vim.schedule_wrap(function()
|
||||
release()
|
||||
self.event:emit('confirm_done', {
|
||||
entry = e,
|
||||
commit_character = option.commit_character,
|
||||
})
|
||||
end))
|
||||
end)
|
||||
end
|
||||
|
||||
---Reset current completion state
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
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')
|
||||
local api = require('cmp.utils.api')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local Async = require('cmp.kit.Async')
|
||||
|
||||
describe('cmp.core', function()
|
||||
describe('confirm', function()
|
||||
|
@ -18,7 +19,7 @@ describe('cmp.core', function()
|
|||
|
||||
local c = core.new()
|
||||
local s = source.new('spec', {
|
||||
get_position_encoding_kind = function()
|
||||
get_position_encoding_kind = function(_)
|
||||
return option.position_encoding_kind or types.lsp.PositionEncodingKind.UTF16
|
||||
end,
|
||||
complete = function(_, _, callback)
|
||||
|
@ -26,26 +27,22 @@ describe('cmp.core', function()
|
|||
end,
|
||||
})
|
||||
c:register_source(s)
|
||||
feedkeys.call(request, 'n', function()
|
||||
local state = {}
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(request, 'n'):await()
|
||||
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], {}, function() end)
|
||||
end)
|
||||
local state = {}
|
||||
feedkeys.call('', 'x', function()
|
||||
feedkeys.call('', 'n', 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()
|
||||
end)
|
||||
end)
|
||||
Keymap.send(filter, 'n'):await()
|
||||
c:confirm(c.sources[s.id].entries[1], {}, function() end):await()
|
||||
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()
|
||||
end))
|
||||
return state
|
||||
end
|
||||
|
||||
|
|
167
lua/cmp/init.lua
167
lua/cmp/init.lua
|
@ -1,11 +1,12 @@
|
|||
local core = require('cmp.core')
|
||||
local source = require('cmp.source')
|
||||
local config = require('cmp.config')
|
||||
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 async = require('cmp.utils.async')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
||||
|
||||
local cmp = {}
|
||||
|
||||
|
@ -32,7 +33,10 @@ cmp.config.sources = require('cmp.config.sources')
|
|||
cmp.config.mapping = require('cmp.config.mapping')
|
||||
cmp.config.window = require('cmp.config.window')
|
||||
|
||||
---Sync asynchronous process.
|
||||
---Sync waiting filter process.
|
||||
---@generic T: fun(...: any[]): any
|
||||
---@param callback T
|
||||
---@return T
|
||||
cmp.sync = function(callback)
|
||||
return function(...)
|
||||
cmp.core.filter:sync(1000)
|
||||
|
@ -43,13 +47,14 @@ cmp.sync = function(callback)
|
|||
end
|
||||
|
||||
---Suspend completion.
|
||||
---@return fun()
|
||||
cmp.suspend = function()
|
||||
return cmp.core:suspend()
|
||||
end
|
||||
|
||||
---Register completion sources
|
||||
---@param name string
|
||||
---@param s cmp.Source
|
||||
---@param s cmp.CustomSource
|
||||
---@return integer
|
||||
cmp.register_source = function(name, s)
|
||||
local src = source.new(name, s)
|
||||
|
@ -75,144 +80,139 @@ cmp.complete = cmp.sync(function(option)
|
|||
option = option or {}
|
||||
config.set_onetime(option.config)
|
||||
cmp.core:complete(cmp.core:get_context({ reason = option.reason or cmp.ContextReason.Manual }))
|
||||
return true
|
||||
end)
|
||||
|
||||
---Complete common string in current entries.
|
||||
---@return cmp.kit.Async.AsyncTask boolean
|
||||
cmp.complete_common_string = cmp.sync(function()
|
||||
return cmp.core:complete_common_string()
|
||||
end)
|
||||
|
||||
---Return view is visible or not.
|
||||
---@return boolean
|
||||
cmp.visible = cmp.sync(function()
|
||||
return cmp.core.view:visible() or vim.fn.pumvisible() == 1
|
||||
end)
|
||||
|
||||
---Get current selected entry or nil
|
||||
---@return cmp.Entry?
|
||||
cmp.get_selected_entry = cmp.sync(function()
|
||||
return cmp.core.view:get_selected_entry()
|
||||
end)
|
||||
|
||||
---Get current active entry or nil
|
||||
---@return cmp.Entry?
|
||||
cmp.get_active_entry = cmp.sync(function()
|
||||
return cmp.core.view:get_active_entry()
|
||||
end)
|
||||
|
||||
---Get current all entries
|
||||
---@return cmp.Entry[]
|
||||
cmp.get_entries = cmp.sync(function()
|
||||
return cmp.core.view:get_entries()
|
||||
end)
|
||||
|
||||
---Close current completion
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
cmp.close = cmp.sync(function()
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:close()
|
||||
vim.schedule(release)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
return AsyncTask.resolve():next(function()
|
||||
if cmp.core.view:visible() then
|
||||
return cmp.core.view:close()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
---Abort current completion
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
cmp.abort = cmp.sync(function()
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:abort()
|
||||
vim.schedule(release)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
return AsyncTask.resolve():next(function()
|
||||
if cmp.core.view:visible() then
|
||||
return cmp.core.view:abort():next(cmp.core:suspend())
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
---Select next item if possible
|
||||
---@param option? cmp.SelectOption
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
cmp.select_next_item = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
option.behavior = option.behavior or cmp.SelectBehavior.Insert
|
||||
option.count = option.count or 1
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_next_item(option)
|
||||
vim.schedule(release)
|
||||
return true
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if option.behavior == cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t(string.rep('<C-n>', option.count)), 'in')
|
||||
else
|
||||
feedkeys.call(keymap.t(string.rep('<Down>', option.count)), 'in')
|
||||
return AsyncTask.resolve():next(function()
|
||||
if cmp.core.view:visible() then
|
||||
return cmp.core.view:select_next_item(option):next(cmp.core:suspend())
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if option.behavior == cmp.SelectBehavior.Insert then
|
||||
return Keymap.send(keymap.t(string.rep('<C-n>', option.count)), 'in')
|
||||
else
|
||||
return Keymap.send(keymap.t(string.rep('<Down>', option.count)), 'in')
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
end)
|
||||
|
||||
---Select prev item if possible
|
||||
---@param option? cmp.SelectOption
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
cmp.select_prev_item = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
option.behavior = option.behavior or cmp.SelectBehavior.Insert
|
||||
option.count = option.count or 1
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local release = cmp.core:suspend()
|
||||
cmp.core.view:select_prev_item(option)
|
||||
vim.schedule(release)
|
||||
return true
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if option.behavior == cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t(string.rep('<C-p>', option.count)), 'in')
|
||||
else
|
||||
feedkeys.call(keymap.t(string.rep('<Up>', option.count)), 'in')
|
||||
return AsyncTask.resolve():next(function()
|
||||
if cmp.core.view:visible() then
|
||||
return cmp.core.view:select_prev_item(option):next(cmp.core:suspend())
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
if option.behavior == cmp.SelectBehavior.Insert then
|
||||
return Keymap.send(keymap.t(string.rep('<C-p>', option.count)), 'in')
|
||||
else
|
||||
return Keymap.send(keymap.t(string.rep('<Up>', option.count)), 'in')
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
end)
|
||||
|
||||
---Scrolling documentation window if possible
|
||||
---@param delta integer
|
||||
cmp.scroll_docs = cmp.sync(function(delta)
|
||||
if cmp.core.view.docs_view:visible() then
|
||||
cmp.core.view:scroll_docs(delta)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
---Confirm completion
|
||||
cmp.confirm = cmp.sync(function(option, callback)
|
||||
---@param option? cmp.ConfirmOption
|
||||
---@return cmp.kit.Async.AsyncTask boolean
|
||||
cmp.confirm = cmp.sync(function(option)
|
||||
option = option or {}
|
||||
option.select = option.select or false
|
||||
option.behavior = option.behavior or cmp.get_config().confirmation.default_behavior or cmp.ConfirmBehavior.Insert
|
||||
callback = callback or function() end
|
||||
|
||||
if cmp.core.view:visible() then
|
||||
local e = cmp.core.view:get_selected_entry()
|
||||
if not e and option.select then
|
||||
e = cmp.core.view:get_first_entry()
|
||||
return AsyncTask.resolve():next(function()
|
||||
if cmp.core.view:visible() then
|
||||
local e = cmp.core.view:get_selected_entry()
|
||||
if not e and option.select then
|
||||
e = cmp.core.view:get_first_entry()
|
||||
end
|
||||
if e then
|
||||
return cmp.core:confirm(e, {
|
||||
behavior = option.behavior,
|
||||
}):next(function()
|
||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly }))
|
||||
end)
|
||||
end
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
local index = vim.fn.complete_info({ 'selected' }).selected
|
||||
if index == -1 and option.select then
|
||||
index = 0
|
||||
end
|
||||
if index ~= -1 then
|
||||
vim.api.nvim_select_popupmenu_item(index, true, true, {})
|
||||
end
|
||||
end
|
||||
if e then
|
||||
cmp.core:confirm(e, {
|
||||
behavior = option.behavior,
|
||||
}, function()
|
||||
callback()
|
||||
cmp.core:complete(cmp.core:get_context({ reason = cmp.ContextReason.TriggerOnly }))
|
||||
end)
|
||||
return true
|
||||
end
|
||||
elseif vim.fn.pumvisible() == 1 then
|
||||
local index = vim.fn.complete_info({ 'selected' }).selected
|
||||
if index == -1 and option.select then
|
||||
index = 0
|
||||
end
|
||||
if index ~= -1 then
|
||||
vim.api.nvim_select_popupmenu_item(index, true, true, {})
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)
|
||||
end)
|
||||
|
||||
---Show status
|
||||
|
@ -277,25 +277,32 @@ end
|
|||
|
||||
---@type cmp.Setup
|
||||
cmp.setup = setmetatable({
|
||||
---@param c cmp.ConfigSchema
|
||||
global = function(c)
|
||||
config.set_global(c)
|
||||
end,
|
||||
---@param filetype string
|
||||
---@param c cmp.ConfigSchema
|
||||
filetype = function(filetype, c)
|
||||
config.set_filetype(c, filetype)
|
||||
end,
|
||||
---@param c cmp.ConfigSchema
|
||||
buffer = function(c)
|
||||
config.set_buffer(c, vim.api.nvim_get_current_buf())
|
||||
end,
|
||||
---@param type ':'|'/'|'?'
|
||||
---@param c cmp.ConfigSchema
|
||||
cmdline = function(type, c)
|
||||
config.set_cmdline(c, type)
|
||||
end,
|
||||
}, {
|
||||
---@param self unknown
|
||||
---@param c cmp.ConfigSchema
|
||||
__call = function(self, c)
|
||||
self.global(c)
|
||||
end,
|
||||
})
|
||||
|
||||
-- In InsertEnter autocmd, vim will detects mode=normal unexpectedly.
|
||||
local on_insert_enter = function()
|
||||
if config.enabled() then
|
||||
cmp.config.compare.scopes:update()
|
||||
|
@ -304,16 +311,17 @@ local on_insert_enter = function()
|
|||
cmp.core:on_change('InsertEnter')
|
||||
end
|
||||
end
|
||||
autocmd.subscribe({ 'CmdlineEnter' }, async.debounce_next_tick(on_insert_enter))
|
||||
autocmd.subscribe({ 'InsertEnter' }, async.debounce_next_tick_by_keymap(on_insert_enter))
|
||||
autocmd.subscribe({ 'InsertEnter' }, async.debounce_next_tick_by_keymap(on_insert_enter)) -- Debouncing is needed to solve InsertEnter's mode problem.
|
||||
autocmd.subscribe({ 'CmdlineEnter' }, on_insert_enter)
|
||||
|
||||
-- async.throttle is needed for performance. The mapping `:<C-u>...<CR>` will fire `CmdlineChanged` for each character.
|
||||
local on_text_changed = function()
|
||||
if config.enabled() then
|
||||
cmp.core:on_change('TextChanged')
|
||||
end
|
||||
end
|
||||
autocmd.subscribe({ 'TextChangedI', 'TextChangedP' }, on_text_changed)
|
||||
-- async.debounce_next_tick is needed for performance. The mapping `:<C-u>...<CR>` will fire `CmdlineChanged` for each character.
|
||||
-- I don't know why but 'We can't use `async.debounce_next_tick_by_keymap` here.
|
||||
autocmd.subscribe('CmdlineChanged', async.debounce_next_tick(on_text_changed))
|
||||
|
||||
autocmd.subscribe('CursorMovedI', function()
|
||||
|
@ -325,6 +333,7 @@ autocmd.subscribe('CursorMovedI', function()
|
|||
end
|
||||
end)
|
||||
|
||||
-- The follwoing autocmds must not be debounced.
|
||||
-- If make this asynchronous, the completion menu will not close when the command output is displayed.
|
||||
autocmd.subscribe({ 'InsertLeave', 'CmdlineLeave' }, function()
|
||||
cmp.core:reset()
|
||||
|
|
|
@ -174,6 +174,15 @@ function AsyncTask:await(schedule)
|
|||
return res
|
||||
end
|
||||
|
||||
---Return current state of task.
|
||||
---@return { status: cmp.kit.Async.AsyncTask.Status, value: any }
|
||||
function AsyncTask:state()
|
||||
return {
|
||||
status = self.status,
|
||||
value = self.value,
|
||||
}
|
||||
end
|
||||
|
||||
---Register next step.
|
||||
---@param on_fulfilled fun(value: any): any
|
||||
function AsyncTask:next(on_fulfilled)
|
||||
|
|
|
@ -2,7 +2,7 @@ local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
|||
|
||||
local Async = {}
|
||||
|
||||
---@type string<thread, integer>
|
||||
---@type table<thread, integer>
|
||||
Async.___threads___ = {}
|
||||
|
||||
---Run async function immediately.
|
||||
|
@ -14,6 +14,12 @@ function Async.run(runner, ...)
|
|||
return Async.async(runner)(...)
|
||||
end
|
||||
|
||||
---Return current context is async coroutine or not.
|
||||
---@return boolean
|
||||
function Async.in_context()
|
||||
return Async.___threads___[coroutine.running()] ~= nil
|
||||
end
|
||||
|
||||
---Create async function.
|
||||
---@generic T: fun(...): cmp.kit.Async.AsyncTask
|
||||
---@param runner T
|
||||
|
|
|
@ -103,7 +103,6 @@ cmp.ItemField = {
|
|||
---@field public snippet cmp.SnippetConfig
|
||||
---@field public mapping table<string, cmp.Mapping>
|
||||
---@field public sources cmp.SourceConfig[]
|
||||
---@field public view cmp.ViewConfig
|
||||
---@field public experimental cmp.ExperimentalConfig
|
||||
|
||||
---@class cmp.PerformanceConfig
|
||||
|
@ -182,18 +181,4 @@ cmp.ItemField = {
|
|||
---@field public resolve fun(completion_item: lsp.CompletionItem, callback: fun(completion_item: lsp.CompletionItem), resolve: fun(completion_item: lsp.CompletionItem, callback: fun(completion_item: lsp.CompletionItem)))
|
||||
---@field public execute fun(completion_item: lsp.CompletionItem, callback: fun(), execute: fun(completion_item: lsp.CompletionItem, callback: fun()))
|
||||
|
||||
---@class cmp.ViewConfig
|
||||
---@field public entries cmp.CustomEntriesConfig|cmp.NativeEntriesConfig|cmp.WildmenuEntriesConfig|string
|
||||
|
||||
---@class cmp.CustomEntriesConfig
|
||||
---@field name 'custom'
|
||||
---@field selection_order 'top_down'|'near_cursor'
|
||||
|
||||
---@class cmp.NativeEntriesConfig
|
||||
---@field name 'native'
|
||||
|
||||
---@class cmp.WildmenuEntriesConfig
|
||||
---@field name 'wildmenu'
|
||||
---@field separator string|nil
|
||||
|
||||
return cmp
|
||||
|
|
|
@ -1,64 +1,53 @@
|
|||
local spec = require('cmp.utils.spec')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local Async = require('cmp.kit.Async')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
describe('api', function()
|
||||
before_each(spec.before)
|
||||
describe('get_cursor', function()
|
||||
it('insert-mode', function()
|
||||
local cursor
|
||||
feedkeys.call(keymap.t('i\t1234567890'), 'nx', function()
|
||||
cursor = api.get_cursor()
|
||||
end)
|
||||
assert.are.equal(cursor[2], 11)
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t('i\t1234567890'), 'n'):await()
|
||||
assert.are.equal(api.get_cursor()[2], 11)
|
||||
end))
|
||||
end)
|
||||
it('cmdline-mode', function()
|
||||
local cursor
|
||||
keymap.set_map(0, 'c', '<Plug>(cmp-spec-spy)', function()
|
||||
cursor = api.get_cursor()
|
||||
end, { expr = true, noremap = true })
|
||||
feedkeys.call(keymap.t(':\t1234567890'), 'n')
|
||||
feedkeys.call(keymap.t('<Plug>(cmp-spec-spy)'), 'x')
|
||||
assert.are.equal(cursor[2], 11)
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t(':\t1234567890'), 'n'):await()
|
||||
assert.are.equal(api.get_cursor()[2], 11)
|
||||
end))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_screen_cursor', function()
|
||||
it('insert-mode', function()
|
||||
local screen_cursor
|
||||
feedkeys.call(keymap.t('iあいうえお'), 'nx', function()
|
||||
screen_cursor = api.get_screen_cursor()
|
||||
end)
|
||||
assert.are.equal(10, screen_cursor[2])
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t('iあいうえお'), 'n'):await()
|
||||
assert.are.equal(api.get_screen_cursor()[2], 10)
|
||||
end))
|
||||
end)
|
||||
it('cmdline-mode', function()
|
||||
local screen_cursor
|
||||
keymap.set_map(0, 'c', '<Plug>(cmp-spec-spy)', function()
|
||||
screen_cursor = api.get_screen_cursor()
|
||||
end, { expr = true, noremap = true })
|
||||
feedkeys.call(keymap.t(':あいうえお'), 'n')
|
||||
feedkeys.call(keymap.t('<Plug>(cmp-spec-spy)'), 'x')
|
||||
assert.are.equal(10, screen_cursor[2])
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t(':あいうえお'), 'n'):await()
|
||||
assert.are.equal(10, api.get_screen_cursor()[2])
|
||||
end))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_cursor_before_line', function()
|
||||
it('insert-mode', function()
|
||||
local cursor_before_line
|
||||
feedkeys.call(keymap.t('i\t1234567890<Left><Left>'), 'nx', function()
|
||||
cursor_before_line = api.get_cursor_before_line()
|
||||
end)
|
||||
assert.are.same(cursor_before_line, '\t12345678')
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t('i\t1234567890<Left><Left>'), 'n'):await()
|
||||
assert.are.same(api.get_cursor_before_line(), '\t12345678')
|
||||
end))
|
||||
end)
|
||||
it('cmdline-mode', function()
|
||||
local cursor_before_line
|
||||
keymap.set_map(0, 'c', '<Plug>(cmp-spec-spy)', function()
|
||||
cursor_before_line = api.get_cursor_before_line()
|
||||
end, { expr = true, noremap = true })
|
||||
feedkeys.call(keymap.t(':\t1234567890<Left><Left>'), 'n')
|
||||
feedkeys.call(keymap.t('<Plug>(cmp-spec-spy)'), 'x')
|
||||
assert.are.same(cursor_before_line, '\t12345678')
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keymap.t(':\t1234567890<Left><Left>'), 'n'):await()
|
||||
assert.are.same(api.get_cursor_before_line(), '\t12345678')
|
||||
end))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
||||
|
||||
local async = {}
|
||||
|
||||
|
@ -142,8 +143,14 @@ end
|
|||
|
||||
---Wait and callback for consuming next keymap.
|
||||
async.debounce_next_tick_by_keymap = function(callback)
|
||||
local queue = nil
|
||||
return function()
|
||||
feedkeys.call('', '', callback)
|
||||
if queue then
|
||||
return
|
||||
end
|
||||
queue = Keymap.send('', 'n'):next(callback):next(function()
|
||||
queue = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
local misc = require('cmp.utils.misc')
|
||||
local buffer = require('cmp.utils.buffer')
|
||||
local api = require('cmp.utils.api')
|
||||
local Async = require('cmp.kit.Async')
|
||||
|
||||
local keymap = {}
|
||||
|
||||
|
@ -31,8 +32,6 @@ keymap.normalize = function(keys)
|
|||
end
|
||||
|
||||
---Return vim notation keymapping (simple conversion).
|
||||
---@param s string
|
||||
---@return string
|
||||
keymap.to_keymap = setmetatable({
|
||||
['<CR>'] = { '\n', '\r', '\r\n' },
|
||||
['<Tab>'] = { '\t' },
|
||||
|
@ -40,15 +39,17 @@ keymap.to_keymap = setmetatable({
|
|||
['<Bar>'] = { '|' },
|
||||
['<Space>'] = { ' ' },
|
||||
}, {
|
||||
---@param s string
|
||||
---@return string
|
||||
__call = function(self, s)
|
||||
return string.gsub(s, '.', function(c)
|
||||
return (string.gsub(s, '.', function(c)
|
||||
for key, chars in pairs(self) do
|
||||
if vim.tbl_contains(chars, c) then
|
||||
return key
|
||||
end
|
||||
end
|
||||
return c
|
||||
end)
|
||||
end))
|
||||
end,
|
||||
})
|
||||
|
||||
|
@ -124,15 +125,18 @@ keymap.listen = function(mode, lhs, callback)
|
|||
|
||||
local bufnr = existing.buffer and vim.api.nvim_get_current_buf() or -1
|
||||
local fallback = keymap.fallback(bufnr, mode, existing)
|
||||
keymap.set_map(bufnr, mode, lhs, function()
|
||||
keymap.set_map(bufnr, mode, lhs, Async.async(function()
|
||||
vim.pretty_print('mapping', lhs)
|
||||
local ignore = false
|
||||
ignore = ignore or (mode == 'c' and vim.fn.getcmdtype() == '=')
|
||||
if ignore then
|
||||
vim.pretty_print('ignore', lhs)
|
||||
fallback()
|
||||
else
|
||||
vim.pretty_print('call callback', lhs)
|
||||
callback(lhs, misc.once(fallback))
|
||||
end
|
||||
end, {
|
||||
end), {
|
||||
expr = false,
|
||||
noremap = true,
|
||||
silent = true,
|
||||
|
@ -207,6 +211,7 @@ keymap.get_map = function(mode, lhs)
|
|||
lhs = map.lhs,
|
||||
rhs = map.rhs or '',
|
||||
expr = map.expr == 1,
|
||||
desc = map.desc,
|
||||
callback = map.callback,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
|
@ -224,6 +229,7 @@ keymap.get_map = function(mode, lhs)
|
|||
lhs = map.lhs,
|
||||
rhs = map.rhs or '',
|
||||
expr = map.expr == 1,
|
||||
desc = map.desc,
|
||||
callback = map.callback,
|
||||
noremap = map.noremap == 1,
|
||||
script = map.script == 1,
|
||||
|
@ -239,6 +245,7 @@ keymap.get_map = function(mode, lhs)
|
|||
lhs = lhs,
|
||||
rhs = lhs,
|
||||
expr = false,
|
||||
desc = nil,
|
||||
callback = nil,
|
||||
noremap = true,
|
||||
script = false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
local spec = require('cmp.utils.spec')
|
||||
local api = require('cmp.utils.api')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local Async = require('cmp.kit.Async')
|
||||
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
|
||||
|
@ -39,18 +40,17 @@ describe('keymap', function()
|
|||
|
||||
local run_fallback = function(keys, fallback)
|
||||
local state = {}
|
||||
feedkeys.call(keys, '', function()
|
||||
Keymap.spec(Async.async(function()
|
||||
Keymap.send(keys, ''):await()
|
||||
fallback()
|
||||
end)
|
||||
feedkeys.call('', '', function()
|
||||
Keymap.send('', ''):await()
|
||||
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()
|
||||
end)
|
||||
feedkeys.call('', 'x')
|
||||
end))
|
||||
return state
|
||||
end
|
||||
|
||||
|
@ -180,7 +180,7 @@ describe('keymap', function()
|
|||
keymap.listen('i', '<CR>', function(_, fallback)
|
||||
fallback()
|
||||
end)
|
||||
feedkeys.call(keymap.t('i<CR>'), 'tx')
|
||||
Keymap.send(keymap.t('i<CR>'), 'x')
|
||||
assert.are.same({ '', 'recursive' }, vim.api.nvim_buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -99,7 +99,11 @@ window.get_buffer = function(self)
|
|||
local buf, created_new = buffer.ensure(self.name)
|
||||
if created_new then
|
||||
for k, v in pairs(self.buffer_opt) do
|
||||
vim.api.nvim_buf_set_option(buf, k, v)
|
||||
pcall(function()
|
||||
vim.api.nvim_set_option_value(k, v, {
|
||||
buf = buf
|
||||
})
|
||||
end)
|
||||
end
|
||||
end
|
||||
return buf
|
||||
|
|
134
lua/cmp/view.lua
134
lua/cmp/view.lua
|
@ -3,18 +3,15 @@ local async = require('cmp.utils.async')
|
|||
local event = require('cmp.utils.event')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local docs_view = require('cmp.view.docs_view')
|
||||
local custom_entries_view = require('cmp.view.custom_entries_view')
|
||||
local wildmenu_entries_view = require('cmp.view.wildmenu_entries_view')
|
||||
local native_entries_view = require('cmp.view.native_entries_view')
|
||||
local entries_view = require('cmp.view.entries_view')
|
||||
local ghost_text_view = require('cmp.view.ghost_text_view')
|
||||
local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
||||
|
||||
---@class cmp.View
|
||||
---@field public event cmp.Event
|
||||
---@field private resolve_dedup cmp.AsyncDedup
|
||||
---@field private native_entries_view cmp.NativeEntriesView
|
||||
---@field private custom_entries_view cmp.CustomEntriesView
|
||||
---@field private wildmenu_entries_view cmp.CustomEntriesView
|
||||
---@field private change_dedup cmp.AsyncDedup
|
||||
---@field private entries_view cmp.EntriesView
|
||||
---@field private docs_view cmp.DocsView
|
||||
---@field private ghost_text_view cmp.GhostTextView
|
||||
local view = {}
|
||||
|
@ -23,25 +20,22 @@ local view = {}
|
|||
view.new = function()
|
||||
local self = setmetatable({}, { __index = view })
|
||||
self.resolve_dedup = async.dedup()
|
||||
self.custom_entries_view = custom_entries_view.new()
|
||||
self.native_entries_view = native_entries_view.new()
|
||||
self.wildmenu_entries_view = wildmenu_entries_view.new()
|
||||
self.entries_view = entries_view.new()
|
||||
self.docs_view = docs_view.new()
|
||||
self.ghost_text_view = ghost_text_view.new()
|
||||
self.event = event.new()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---Return the view components are available or not.
|
||||
---@return boolean
|
||||
view.ready = function(self)
|
||||
return self:_get_entries_view():ready()
|
||||
return self.entries_view:ready()
|
||||
end
|
||||
|
||||
---OnChange handler.
|
||||
view.on_change = function(self)
|
||||
self:_get_entries_view():on_change()
|
||||
self.entries_view:on_change()
|
||||
end
|
||||
|
||||
---Open menu
|
||||
|
@ -107,9 +101,9 @@ view.open = function(self, ctx, sources)
|
|||
|
||||
-- open
|
||||
if #entries > 0 then
|
||||
self:_get_entries_view():open(offset, entries)
|
||||
self.entries_view:open(offset, entries)
|
||||
self.event:emit('menu_opened', {
|
||||
window = self:_get_entries_view(),
|
||||
window = self.entries_view,
|
||||
})
|
||||
break
|
||||
end
|
||||
|
@ -121,102 +115,88 @@ view.open = function(self, ctx, sources)
|
|||
end
|
||||
end
|
||||
|
||||
---Close menu
|
||||
view.close = function(self)
|
||||
if self:visible() then
|
||||
self.event:emit('complete_done', {
|
||||
entry = self:_get_entries_view():get_selected_entry(),
|
||||
})
|
||||
end
|
||||
self:_get_entries_view():close()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
self.event:emit('menu_closed', {
|
||||
window = self:_get_entries_view(),
|
||||
})
|
||||
end
|
||||
|
||||
---Abort menu
|
||||
view.abort = function(self)
|
||||
self:_get_entries_view():abort()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
self.event:emit('menu_closed', {
|
||||
window = self:_get_entries_view(),
|
||||
})
|
||||
end
|
||||
|
||||
---Return the view is visible or not.
|
||||
---@return boolean
|
||||
view.visible = function(self)
|
||||
return self:_get_entries_view():visible()
|
||||
end
|
||||
|
||||
---Scroll documentation window if possible.
|
||||
---@param delta integer
|
||||
view.scroll_docs = function(self, delta)
|
||||
self.docs_view:scroll(delta)
|
||||
end
|
||||
|
||||
---Select prev menu item.
|
||||
---@param option cmp.SelectOption
|
||||
view.select_next_item = function(self, option)
|
||||
self:_get_entries_view():select_next_item(option)
|
||||
---Return the view is visible or not.
|
||||
---@return boolean
|
||||
view.visible = function(self)
|
||||
return not not self.entries_view:visible()
|
||||
end
|
||||
|
||||
---Close menu
|
||||
view.close = function(self)
|
||||
if self:visible() then
|
||||
self.event:emit('complete_done', {
|
||||
entry = self.entries_view:get_selected_entry(),
|
||||
})
|
||||
end
|
||||
self.entries_view:close()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
self.event:emit('menu_closed', {
|
||||
window = self.entries_view,
|
||||
})
|
||||
end
|
||||
|
||||
---Abort menu
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
view.abort = function(self)
|
||||
return AsyncTask.resolve():next(function()
|
||||
return self.entries_view:abort()
|
||||
end):next(function()
|
||||
self.docs_view:close()
|
||||
self.ghost_text_view:hide()
|
||||
self.event:emit('menu_closed', {
|
||||
window = self.entries_view,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
---Select prev menu item.
|
||||
---@param option cmp.SelectOption
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
view.select_next_item = function(self, option)
|
||||
return self.entries_view:select_next_item(option)
|
||||
end
|
||||
|
||||
---Select prev menu item.
|
||||
---@param option cmp.SelectOption
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
view.select_prev_item = function(self, option)
|
||||
self:_get_entries_view():select_prev_item(option)
|
||||
return self.entries_view:select_prev_item(option)
|
||||
end
|
||||
|
||||
---Get offset.
|
||||
view.get_offset = function(self)
|
||||
return self:_get_entries_view():get_offset()
|
||||
return self.entries_view:get_offset()
|
||||
end
|
||||
|
||||
---Get entries.
|
||||
---@return cmp.Entry[]
|
||||
view.get_entries = function(self)
|
||||
return self:_get_entries_view():get_entries()
|
||||
return self.entries_view:get_entries()
|
||||
end
|
||||
|
||||
---Get first entry
|
||||
---@param self cmp.Entry|nil
|
||||
---@return cmp.Entry|nil
|
||||
view.get_first_entry = function(self)
|
||||
return self:_get_entries_view():get_first_entry()
|
||||
return self.entries_view:get_first_entry()
|
||||
end
|
||||
|
||||
---Get current selected entry
|
||||
---@return cmp.Entry|nil
|
||||
view.get_selected_entry = function(self)
|
||||
return self:_get_entries_view():get_selected_entry()
|
||||
return self.entries_view:get_selected_entry()
|
||||
end
|
||||
|
||||
---Get current active entry
|
||||
---@return cmp.Entry|nil
|
||||
view.get_active_entry = function(self)
|
||||
return self:_get_entries_view():get_active_entry()
|
||||
end
|
||||
|
||||
---Return current configured entries_view
|
||||
---@return cmp.CustomEntriesView|cmp.NativeEntriesView
|
||||
view._get_entries_view = function(self)
|
||||
self.native_entries_view.event:clear()
|
||||
self.custom_entries_view.event:clear()
|
||||
self.wildmenu_entries_view.event:clear()
|
||||
|
||||
local c = config.get()
|
||||
local v = self.custom_entries_view
|
||||
if (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'wildmenu' then
|
||||
v = self.wildmenu_entries_view
|
||||
elseif (c.view and c.view.entries and (c.view.entries.name or c.view.entries)) == 'native' then
|
||||
v = self.native_entries_view
|
||||
end
|
||||
v.event:on('change', function()
|
||||
self:on_entry_change()
|
||||
end)
|
||||
return v
|
||||
return self.entries_view:get_active_entry()
|
||||
end
|
||||
|
||||
---On entry change
|
||||
|
@ -235,7 +215,7 @@ view.on_entry_change = async.throttle(function(self)
|
|||
if not self:visible() then
|
||||
return
|
||||
end
|
||||
self.docs_view:open(e, self:_get_entries_view():info())
|
||||
self.docs_view:open(e, self.entries_view:info())
|
||||
end)))
|
||||
else
|
||||
self.docs_view:close()
|
||||
|
|
|
@ -1,28 +1,30 @@
|
|||
local event = require('cmp.utils.event')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local window = require('cmp.utils.window')
|
||||
local config = require('cmp.config')
|
||||
local types = require('cmp.types')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local api = require('cmp.utils.api')
|
||||
local Keymap = require('cmp.kit.Vim.Keymap')
|
||||
local AsyncTask = require('cmp.kit.Async.AsyncTask')
|
||||
|
||||
local DEFAULT_HEIGHT = 10 -- @see https://github.com/vim/vim/blob/master/src/popupmenu.c#L45
|
||||
|
||||
---@class cmp.CustomEntriesView
|
||||
---@class cmp.EntriesView
|
||||
---@field private entries_win cmp.Window
|
||||
---@field private prefix? string
|
||||
---@field private offset integer
|
||||
---@field private active boolean
|
||||
---@field private entries cmp.Entry[]
|
||||
---@field private column_width any
|
||||
---@field public event cmp.Event
|
||||
local custom_entries_view = {}
|
||||
local entries_view = {}
|
||||
|
||||
custom_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.custom_entries_view')
|
||||
entries_view.ns = vim.api.nvim_create_namespace('cmp.view.entries_view')
|
||||
|
||||
custom_entries_view.new = function()
|
||||
local self = setmetatable({}, { __index = custom_entries_view })
|
||||
entries_view.new = function()
|
||||
local self = setmetatable({}, { __index = entries_view })
|
||||
|
||||
self.entries_win = window.new()
|
||||
self.entries_win:option('conceallevel', 2)
|
||||
|
@ -31,7 +33,7 @@ custom_entries_view.new = function()
|
|||
self.entries_win:option('foldenable', false)
|
||||
self.entries_win:option('wrap', false)
|
||||
-- This is done so that strdisplaywidth calculations for lines in the
|
||||
-- custom_entries_view window exactly match with what is really displayed,
|
||||
-- entries_view window exactly match with what is really displayed,
|
||||
-- see comment in cmp.Entry.get_view. Setting tabstop to 1 makes all tabs be
|
||||
-- always rendered one column wide, which removes the unpredictability coming
|
||||
-- from variable width of the tab character.
|
||||
|
@ -52,7 +54,7 @@ custom_entries_view.new = function()
|
|||
end)
|
||||
)
|
||||
|
||||
vim.api.nvim_set_decoration_provider(custom_entries_view.ns, {
|
||||
vim.api.nvim_set_decoration_provider(entries_view.ns, {
|
||||
on_win = function(_, win, buf, top, bot)
|
||||
if win ~= self.entries_win.win or buf ~= self.entries_win:get_buffer() then
|
||||
return
|
||||
|
@ -69,7 +71,7 @@ custom_entries_view.new = function()
|
|||
if field == types.cmp.ItemField.Abbr then
|
||||
a = o
|
||||
end
|
||||
vim.api.nvim_buf_set_extmark(buf, custom_entries_view.ns, i, o, {
|
||||
vim.api.nvim_buf_set_extmark(buf, entries_view.ns, i, o, {
|
||||
end_line = i,
|
||||
end_col = o + v[field].bytes,
|
||||
hl_group = v[field].hl_group,
|
||||
|
@ -80,7 +82,7 @@ custom_entries_view.new = function()
|
|||
end
|
||||
|
||||
for _, m in ipairs(e.matches or {}) do
|
||||
vim.api.nvim_buf_set_extmark(buf, custom_entries_view.ns, i, a + m.word_match_start - 1, {
|
||||
vim.api.nvim_buf_set_extmark(buf, entries_view.ns, i, a + m.word_match_start - 1, {
|
||||
end_line = i,
|
||||
end_col = a + m.word_match_end,
|
||||
hl_group = m.fuzzy and 'CmpItemAbbrMatchFuzzy' or 'CmpItemAbbrMatch',
|
||||
|
@ -96,15 +98,15 @@ custom_entries_view.new = function()
|
|||
return self
|
||||
end
|
||||
|
||||
custom_entries_view.ready = function()
|
||||
entries_view.ready = function()
|
||||
return vim.fn.pumvisible() == 0
|
||||
end
|
||||
|
||||
custom_entries_view.on_change = function(self)
|
||||
entries_view.on_change = function(self)
|
||||
self.active = false
|
||||
end
|
||||
|
||||
custom_entries_view.is_direction_top_down = function(self)
|
||||
entries_view.is_direction_top_down = function(self)
|
||||
local c = config.get()
|
||||
if (c.view and c.view.entries and c.view.entries.selection_order) == 'top_down' then
|
||||
return true
|
||||
|
@ -115,7 +117,7 @@ custom_entries_view.is_direction_top_down = function(self)
|
|||
end
|
||||
end
|
||||
|
||||
custom_entries_view.open = function(self, offset, entries)
|
||||
entries_view.open = function(self, offset, entries)
|
||||
local completion = config.get().window.completion
|
||||
self.offset = offset
|
||||
self.entries = {}
|
||||
|
@ -224,25 +226,29 @@ custom_entries_view.open = function(self, offset, entries)
|
|||
end
|
||||
end
|
||||
|
||||
custom_entries_view.close = function(self)
|
||||
entries_view.close = function(self)
|
||||
self.prefix = nil
|
||||
self.offset = -1
|
||||
self.active = false
|
||||
self.bottom_up = false
|
||||
self.entries = {}
|
||||
self.entries_win:close()
|
||||
self.bottom_up = false
|
||||
end
|
||||
|
||||
custom_entries_view.abort = function(self)
|
||||
if self.prefix then
|
||||
self:_insert(self.prefix)
|
||||
end
|
||||
feedkeys.call('', 'n', function()
|
||||
self:close()
|
||||
end)
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
entries_view.abort = function(self)
|
||||
return AsyncTask.resolve()
|
||||
:next(function()
|
||||
if self.prefix then
|
||||
return self:_insert(self.prefix)
|
||||
end
|
||||
end)
|
||||
:next(function()
|
||||
self:close()
|
||||
end)
|
||||
end
|
||||
|
||||
custom_entries_view.draw = function(self)
|
||||
entries_view.draw = function(self)
|
||||
local info = vim.fn.getwininfo(self.entries_win.win)[1]
|
||||
local topline = info.topline - 1
|
||||
local botline = info.topline + info.height - 1
|
||||
|
@ -273,15 +279,16 @@ custom_entries_view.draw = function(self)
|
|||
end
|
||||
end
|
||||
|
||||
custom_entries_view.visible = function(self)
|
||||
entries_view.visible = function(self)
|
||||
return self.entries_win:visible()
|
||||
end
|
||||
|
||||
custom_entries_view.info = function(self)
|
||||
entries_view.info = function(self)
|
||||
return self.entries_win:info()
|
||||
end
|
||||
|
||||
custom_entries_view.select_next_item = function(self, option)
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
entries_view.select_next_item = function(self, option)
|
||||
if self:visible() then
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1]
|
||||
local is_top_down = self:is_direction_top_down()
|
||||
|
@ -310,12 +317,13 @@ custom_entries_view.select_next_item = function(self, option)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:_select(cursor, option)
|
||||
return self:_select(cursor, option)
|
||||
end
|
||||
return AsyncTask.resolve()
|
||||
end
|
||||
|
||||
custom_entries_view.select_prev_item = function(self, option)
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
entries_view.select_prev_item = function(self, option)
|
||||
if self:visible() then
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1]
|
||||
local is_top_down = self:is_direction_top_down()
|
||||
|
@ -344,44 +352,45 @@ custom_entries_view.select_prev_item = function(self, option)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:_select(cursor, option)
|
||||
return self:_select(cursor, option)
|
||||
end
|
||||
return AsyncTask.resolve()
|
||||
end
|
||||
|
||||
custom_entries_view.get_offset = function(self)
|
||||
entries_view.get_offset = function(self)
|
||||
if self:visible() then
|
||||
return self.offset
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
custom_entries_view.get_entries = function(self)
|
||||
entries_view.get_entries = function(self)
|
||||
if self:visible() then
|
||||
return self.entries
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
custom_entries_view.get_first_entry = function(self)
|
||||
entries_view.get_first_entry = function(self)
|
||||
if self:visible() then
|
||||
return (self:is_direction_top_down() and self.entries[1]) or self.entries[#self.entries]
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.get_selected_entry = function(self)
|
||||
entries_view.get_selected_entry = function(self)
|
||||
if self:visible() and self.entries_win:option('cursorline') then
|
||||
return self.entries[vim.api.nvim_win_get_cursor(self.entries_win.win)[1]]
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.get_active_entry = function(self)
|
||||
entries_view.get_active_entry = function(self)
|
||||
if self:visible() and self.active then
|
||||
return self:get_selected_entry()
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view._select = function(self, cursor, option)
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
entries_view._select = function(self, cursor, option)
|
||||
local is_insert = (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert
|
||||
if is_insert and not self.active then
|
||||
self.prefix = string.sub(api.get_current_line(), self.offset, api.get_cursor()[2]) or ''
|
||||
|
@ -395,48 +404,32 @@ custom_entries_view._select = function(self, cursor, option)
|
|||
0,
|
||||
})
|
||||
|
||||
if is_insert then
|
||||
self:_insert(self.entries[cursor] and self.entries[cursor]:get_vim_item(self.offset).word or self.prefix)
|
||||
end
|
||||
|
||||
self.entries_win:update()
|
||||
self:draw()
|
||||
self.event:emit('change')
|
||||
return AsyncTask.resolve():next(function()
|
||||
if is_insert then
|
||||
return self:_insert(self.entries[cursor] and self.entries[cursor]:get_vim_item(self.offset).word or self.prefix)
|
||||
end
|
||||
end):next(function()
|
||||
self.entries_win:update()
|
||||
self:draw()
|
||||
self.event:emit('change')
|
||||
end)
|
||||
end
|
||||
|
||||
custom_entries_view._insert = setmetatable({
|
||||
pending = false,
|
||||
}, {
|
||||
__call = function(this, self, word)
|
||||
word = word or ''
|
||||
if api.is_cmdline_mode() then
|
||||
local cursor = api.get_cursor()
|
||||
vim.api.nvim_feedkeys(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'int', true)
|
||||
else
|
||||
if this.pending then
|
||||
return
|
||||
end
|
||||
this.pending = true
|
||||
---@return cmp.kit.Async.AsyncTask
|
||||
entries_view._insert = function(self, word)
|
||||
word = word or ''
|
||||
if api.is_cmdline_mode() then
|
||||
local cursor = api.get_cursor()
|
||||
return Keymap.send(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'ni')
|
||||
else
|
||||
local cursor = api.get_cursor()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.indentkeys())
|
||||
table.insert(keys, keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])))
|
||||
table.insert(keys, word)
|
||||
table.insert(keys, keymap.indentkeys(vim.bo.indentkeys))
|
||||
return Keymap.send(table.concat(keys, ''), 'ni')
|
||||
end
|
||||
end
|
||||
|
||||
local release = require('cmp').suspend()
|
||||
feedkeys.call('', '', function()
|
||||
local cursor = api.get_cursor()
|
||||
local keys = {}
|
||||
table.insert(keys, keymap.indentkeys())
|
||||
table.insert(keys, keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])))
|
||||
table.insert(keys, word)
|
||||
table.insert(keys, keymap.indentkeys(vim.bo.indentkeys))
|
||||
feedkeys.call(
|
||||
table.concat(keys, ''),
|
||||
'int',
|
||||
vim.schedule_wrap(function()
|
||||
this.pending = false
|
||||
release()
|
||||
end)
|
||||
)
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return custom_entries_view
|
||||
return entries_view
|
|
@ -1,180 +0,0 @@
|
|||
local event = require('cmp.utils.event')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local types = require('cmp.types')
|
||||
local config = require('cmp.config')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
---@class cmp.NativeEntriesView
|
||||
---@field private offset integer
|
||||
---@field private items vim.CompletedItem
|
||||
---@field private entries cmp.Entry[]
|
||||
---@field private preselect_index integer
|
||||
---@field public event cmp.Event
|
||||
local native_entries_view = {}
|
||||
|
||||
native_entries_view.new = function()
|
||||
local self = setmetatable({}, { __index = native_entries_view })
|
||||
self.event = event.new()
|
||||
self.offset = -1
|
||||
self.items = {}
|
||||
self.entries = {}
|
||||
self.preselect_index = 0
|
||||
autocmd.subscribe('CompleteChanged', function()
|
||||
self.event:emit('change')
|
||||
end)
|
||||
return self
|
||||
end
|
||||
|
||||
native_entries_view.ready = function(_)
|
||||
if vim.fn.pumvisible() == 0 then
|
||||
return true
|
||||
end
|
||||
return vim.fn.complete_info({ 'mode' }).mode == 'eval'
|
||||
end
|
||||
|
||||
native_entries_view.on_change = function(self)
|
||||
if #self.entries > 0 and self.offset <= vim.api.nvim_win_get_cursor(0)[2] + 1 then
|
||||
local preselect_enabled = config.get().preselect == types.cmp.PreselectMode.Item
|
||||
|
||||
local completeopt = vim.o.completeopt
|
||||
if self.preselect_index == 1 and preselect_enabled then
|
||||
vim.o.completeopt = 'menu,menuone,noinsert'
|
||||
else
|
||||
vim.o.completeopt = config.get().completion.completeopt
|
||||
end
|
||||
vim.fn.complete(self.offset, self.items)
|
||||
vim.o.completeopt = completeopt
|
||||
|
||||
if self.preselect_index > 1 and preselect_enabled then
|
||||
self:preselect(self.preselect_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.open = function(self, offset, entries)
|
||||
local dedup = {}
|
||||
local items = {}
|
||||
local dedup_entries = {}
|
||||
local preselect_index = 0
|
||||
for _, e in ipairs(entries) do
|
||||
local item = e:get_vim_item(offset)
|
||||
if item.dup == 1 or not dedup[item.abbr] then
|
||||
dedup[item.abbr] = true
|
||||
table.insert(items, item)
|
||||
table.insert(dedup_entries, e)
|
||||
if preselect_index == 0 and e.completion_item.preselect then
|
||||
preselect_index = #dedup_entries
|
||||
end
|
||||
end
|
||||
end
|
||||
self.offset = offset
|
||||
self.items = items
|
||||
self.entries = dedup_entries
|
||||
self.preselect_index = preselect_index
|
||||
self:on_change()
|
||||
end
|
||||
|
||||
native_entries_view.close = function(self)
|
||||
if api.is_insert_mode() and self:visible() then
|
||||
vim.api.nvim_select_popupmenu_item(-1, false, true, {})
|
||||
end
|
||||
self.offset = -1
|
||||
self.entries = {}
|
||||
self.items = {}
|
||||
self.preselect_index = 0
|
||||
end
|
||||
|
||||
native_entries_view.abort = function(_)
|
||||
if api.is_suitable_mode() then
|
||||
vim.api.nvim_select_popupmenu_item(-1, true, true, {})
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.visible = function(_)
|
||||
return vim.fn.pumvisible() == 1
|
||||
end
|
||||
|
||||
native_entries_view.info = function(self)
|
||||
if self:visible() then
|
||||
local info = vim.fn.pum_getpos()
|
||||
return {
|
||||
width = info.width + (info.scrollbar and 1 or 0) + (info.col == 0 and 0 or 1),
|
||||
height = info.height,
|
||||
row = info.row,
|
||||
col = info.col == 0 and 0 or info.col - 1,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.preselect = function(self, index)
|
||||
if self:visible() then
|
||||
if index <= #self.entries then
|
||||
vim.api.nvim_select_popupmenu_item(index - 1, false, false, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.select_next_item = function(self, option)
|
||||
local callback = function()
|
||||
self.event:emit('change')
|
||||
end
|
||||
if self:visible() then
|
||||
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t(string.rep('<C-n>', option.count)), 'n', callback)
|
||||
else
|
||||
feedkeys.call(keymap.t(string.rep('<Down>', option.count)), 'n', callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.select_prev_item = function(self, option)
|
||||
local callback = function()
|
||||
self.event:emit('change')
|
||||
end
|
||||
if self:visible() then
|
||||
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
|
||||
feedkeys.call(keymap.t(string.rep('<C-p>', option.count)), 'n', callback)
|
||||
else
|
||||
feedkeys.call(keymap.t(string.rep('<Up>', option.count)), 'n', callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.get_offset = function(self)
|
||||
if self:visible() then
|
||||
return self.offset
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
native_entries_view.get_entries = function(self)
|
||||
if self:visible() then
|
||||
return self.entries
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
native_entries_view.get_first_entry = function(self)
|
||||
if self:visible() then
|
||||
return self.entries[1]
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.get_selected_entry = function(self)
|
||||
if self:visible() then
|
||||
local idx = vim.fn.complete_info({ 'selected' }).selected
|
||||
if idx > -1 then
|
||||
return self.entries[math.max(0, idx) + 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.get_active_entry = function(self)
|
||||
if self:visible() and (vim.v.completed_item or {}).word then
|
||||
return self:get_selected_entry()
|
||||
end
|
||||
end
|
||||
|
||||
return native_entries_view
|
|
@ -1,264 +0,0 @@
|
|||
local event = require('cmp.utils.event')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
local feedkeys = require('cmp.utils.feedkeys')
|
||||
local config = require('cmp.config')
|
||||
local window = require('cmp.utils.window')
|
||||
local types = require('cmp.types')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local misc = require('cmp.utils.misc')
|
||||
local api = require('cmp.utils.api')
|
||||
|
||||
---@class cmp.CustomEntriesView
|
||||
---@field private offset integer
|
||||
---@field private entries_win cmp.Window
|
||||
---@field private active boolean
|
||||
---@field private entries cmp.Entry[]
|
||||
---@field public event cmp.Event
|
||||
local wildmenu_entries_view = {}
|
||||
|
||||
wildmenu_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.statusline_entries_view')
|
||||
|
||||
wildmenu_entries_view.new = function()
|
||||
local self = setmetatable({}, { __index = wildmenu_entries_view })
|
||||
self.event = event.new()
|
||||
self.offset = -1
|
||||
self.active = false
|
||||
self.entries = {}
|
||||
self.offsets = {}
|
||||
self.selected_index = 0
|
||||
self.entries_win = window.new()
|
||||
|
||||
self.entries_win:option('conceallevel', 2)
|
||||
self.entries_win:option('concealcursor', 'n')
|
||||
self.entries_win:option('cursorlineopt', 'line')
|
||||
self.entries_win:option('foldenable', false)
|
||||
self.entries_win:option('wrap', false)
|
||||
self.entries_win:option('scrolloff', 0)
|
||||
self.entries_win:option('sidescrolloff', 0)
|
||||
self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
|
||||
self.entries_win:buffer_option('tabstop', 1)
|
||||
|
||||
autocmd.subscribe(
|
||||
'CompleteChanged',
|
||||
vim.schedule_wrap(function()
|
||||
if self:visible() and vim.fn.pumvisible() == 1 then
|
||||
self:close()
|
||||
end
|
||||
end)
|
||||
)
|
||||
|
||||
vim.api.nvim_set_decoration_provider(wildmenu_entries_view.ns, {
|
||||
on_win = function(_, win, buf, _, _)
|
||||
if win ~= self.entries_win.win or buf ~= self.entries_win:get_buffer() then
|
||||
return
|
||||
end
|
||||
|
||||
for i, e in ipairs(self.entries) do
|
||||
if e then
|
||||
local view = e:get_view(self.offset, buf)
|
||||
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, self.offsets[i], {
|
||||
end_line = 0,
|
||||
end_col = self.offsets[i] + view.abbr.bytes,
|
||||
hl_group = view.abbr.hl_group,
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
|
||||
if i == self.selected_index then
|
||||
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, self.offsets[i], {
|
||||
end_line = 0,
|
||||
end_col = self.offsets[i] + view.abbr.bytes,
|
||||
hl_group = 'PmenuSel',
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
end
|
||||
|
||||
for _, m in ipairs(e.matches or {}) do
|
||||
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, self.offsets[i] + m.word_match_start - 1, {
|
||||
end_line = 0,
|
||||
end_col = self.offsets[i] + m.word_match_end,
|
||||
hl_group = m.fuzzy and 'CmpItemAbbrMatchFuzzy' or 'CmpItemAbbrMatch',
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
wildmenu_entries_view.close = function(self)
|
||||
self.entries_win:close()
|
||||
end
|
||||
|
||||
wildmenu_entries_view.ready = function()
|
||||
return vim.fn.pumvisible() == 0
|
||||
end
|
||||
|
||||
wildmenu_entries_view.on_change = function(self)
|
||||
self.active = false
|
||||
end
|
||||
|
||||
wildmenu_entries_view.open = function(self, offset, entries)
|
||||
self.offset = offset
|
||||
self.entries = {}
|
||||
|
||||
-- Apply window options (that might be changed) on the custom completion menu.
|
||||
self.entries_win:option('winblend', vim.o.pumblend)
|
||||
|
||||
local dedup = {}
|
||||
local preselect = 0
|
||||
local i = 1
|
||||
for _, e in ipairs(entries) do
|
||||
local view = e:get_view(offset, 0)
|
||||
if view.dup == 1 or not dedup[e.completion_item.label] then
|
||||
dedup[e.completion_item.label] = true
|
||||
table.insert(self.entries, e)
|
||||
if preselect == 0 and e.completion_item.preselect then
|
||||
preselect = i
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
self.entries_win:open({
|
||||
relative = 'editor',
|
||||
style = 'minimal',
|
||||
row = vim.o.lines - 2,
|
||||
col = 0,
|
||||
width = vim.o.columns,
|
||||
height = 1,
|
||||
zindex = 1001,
|
||||
})
|
||||
self:draw()
|
||||
|
||||
if preselect > 0 and config.get().preselect == types.cmp.PreselectMode.Item then
|
||||
self:_select(preselect, { behavior = types.cmp.SelectBehavior.Select })
|
||||
elseif not string.match(config.get().completion.completeopt, 'noselect') then
|
||||
self:_select(1, { behavior = types.cmp.SelectBehavior.Select })
|
||||
else
|
||||
self:_select(0, { behavior = types.cmp.SelectBehavior.Select })
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view.abort = function(self)
|
||||
feedkeys.call('', 'n', function()
|
||||
self:close()
|
||||
end)
|
||||
end
|
||||
|
||||
wildmenu_entries_view.draw = function(self)
|
||||
self.offsets = {}
|
||||
|
||||
local entries_buf = self.entries_win:get_buffer()
|
||||
local texts = {}
|
||||
local offset = 0
|
||||
for _, e in ipairs(self.entries) do
|
||||
local view = e:get_view(self.offset, entries_buf)
|
||||
table.insert(self.offsets, offset)
|
||||
table.insert(texts, view.abbr.text)
|
||||
offset = offset + view.abbr.bytes + #self:_get_separator()
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(entries_buf, 0, 1, false, { table.concat(texts, self:_get_separator()) })
|
||||
vim.api.nvim_buf_set_option(entries_buf, 'modified', false)
|
||||
|
||||
vim.api.nvim_win_call(0, function()
|
||||
misc.redraw()
|
||||
end)
|
||||
end
|
||||
|
||||
wildmenu_entries_view.visible = function(self)
|
||||
return self.entries_win:visible()
|
||||
end
|
||||
|
||||
wildmenu_entries_view.info = function(self)
|
||||
return self.entries_win:info()
|
||||
end
|
||||
|
||||
wildmenu_entries_view.select_next_item = function(self, option)
|
||||
if self:visible() then
|
||||
local cursor
|
||||
if self.selected_index == 0 or self.selected_index == #self.entries then
|
||||
cursor = option.count
|
||||
else
|
||||
cursor = self.selected_index + option.count
|
||||
end
|
||||
cursor = math.max(math.min(cursor, #self.entries), 0)
|
||||
self:_select(cursor, option)
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view.select_prev_item = function(self, option)
|
||||
if self:visible() then
|
||||
if self.selected_index == 0 or self.selected_index <= 1 then
|
||||
self:_select(#self.entries, option)
|
||||
else
|
||||
self:_select(math.max(self.selected_index - option.count, 0), option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view.get_offset = function(self)
|
||||
if self:visible() then
|
||||
return self.offset
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
wildmenu_entries_view.get_entries = function(self)
|
||||
if self:visible() then
|
||||
return self.entries
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
wildmenu_entries_view.get_first_entry = function(self)
|
||||
if self:visible() then
|
||||
return self.entries[1]
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view.get_selected_entry = function(self)
|
||||
if self:visible() and self.active then
|
||||
return self.entries[self.selected_index]
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view.get_active_entry = function(self)
|
||||
if self:visible() and self.active then
|
||||
return self:get_selected_entry()
|
||||
end
|
||||
end
|
||||
|
||||
wildmenu_entries_view._select = function(self, selected_index, option)
|
||||
local is_next = self.selected_index < selected_index
|
||||
self.selected_index = selected_index
|
||||
self.active = (selected_index ~= 0)
|
||||
|
||||
if self.active then
|
||||
local e = self:get_active_entry()
|
||||
if option.behavior == types.cmp.SelectBehavior.Insert then
|
||||
local cursor = api.get_cursor()
|
||||
local word = e:get_vim_item(self.offset).word
|
||||
vim.api.nvim_feedkeys(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'int', true)
|
||||
end
|
||||
vim.api.nvim_win_call(self.entries_win.win, function()
|
||||
local view = e:get_view(self.offset, self.entries_win:get_buffer())
|
||||
vim.api.nvim_win_set_cursor(0, { 1, self.offsets[selected_index] + (is_next and view.abbr.bytes or 0) })
|
||||
vim.cmd([[redraw!]]) -- Force refresh for vim.api.nvim_set_decoration_provider
|
||||
end)
|
||||
end
|
||||
|
||||
self.event:emit('change')
|
||||
end
|
||||
|
||||
wildmenu_entries_view._get_separator = function()
|
||||
local c = config.get()
|
||||
return (c and c.view and c.view.entries and c.view.entries.separator) or ' '
|
||||
end
|
||||
|
||||
return wildmenu_entries_view
|
Loading…
Reference in a new issue