Use floating window for completion menus (#224)
* WIP * WIP * Fix #226 * Insert text * Emulate vim native * テキトウ * Tekito * Move scrollbar impl * aaa * Ignore unexpected event * fix * fix scroll * Refactor (conflict...) * Fix bug * Positive integer * Refactor a bit * Fix for pumheight=0 * fx * Improve matching highlight * Improve colorscheme handling * fmt * Add cmp.visible * Fix pum pos * ABBR_MARGIN * Fix cel calculation * up * refactor * fix * a * a * compat * Remove current completion state * Fix ghost text * Add feature toggle * highlight customization * Update * Add breaking change announcement * Add README.md * Remove unused function * extmark ephemeral ghost text * Support native comp * Fix docs pos * a * Remove if native menu visible * theme async * Improvement idea: option to disables insert on select item (#240) * use ghost text instead of insertion on prev/next item * add disables_insert_on_selection option * move disable_insert_on_select option as argumet on * update README * use an enum behavior to disable insert on select * Adopt contribution * Preselect * Improve * Change configuration option * a * Improve * Improve * Implement proper <C-e> behavior to native/custom * Support <C-c> maybe * Improve docs view * Improve * Avoid syntax leak * TODO: refactor * Fix * Revert win pos * fmt * ghost text remaining * Don't use italic by default * bottom * dedup by label * Ignore events * up * Hacky native view partial support * up * perf * improve * more cache * fmt * Fix format option * fmt * recheck * Fix * Improve * Improve * compat * implement redraw * improve * up * fmt/lint * immediate ghost text * source timeout * up * Support multibyte * disable highlight * up * improve * fmt * fmt * fix * fix * up * up * Use screenpos * Add undojoin check * Fix height * matcher bug * Fix dot-repeat * Remove undojoin * macro * Support dot-repeat * MacroSafe * Default item count is 200 * fmt Co-authored-by: Eric Puentes <eric.puentes@mercadolibre.com.co>
This commit is contained in:
295
lua/cmp/view/custom_entries_view.lua
Normal file
295
lua/cmp/view/custom_entries_view.lua
Normal file
@@ -0,0 +1,295 @@
|
||||
local event = require('cmp.utils.event')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
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')
|
||||
|
||||
---@class cmp.CustomEntriesView
|
||||
---@field private entries_win cmp.Window
|
||||
---@field private offset number
|
||||
---@field private entries cmp.Entry[]
|
||||
---@field private column_bytes any
|
||||
---@field private column_width any
|
||||
---@field public event cmp.Event
|
||||
local custom_entries_view = {}
|
||||
|
||||
custom_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.custom_entries_view')
|
||||
|
||||
custom_entries_view.new = function()
|
||||
local self = setmetatable({}, { __index = custom_entries_view })
|
||||
self.entries_win = window.new()
|
||||
self.entries_win:option('conceallevel', 2)
|
||||
self.entries_win:option('concealcursor', 'n')
|
||||
self.entries_win:option('foldenable', false)
|
||||
self.entries_win:option('wrap', false)
|
||||
self.entries_win:option('scrolloff', 0)
|
||||
self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
|
||||
self.event = event.new()
|
||||
self.offset = -1
|
||||
self.entries = {}
|
||||
|
||||
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(custom_entries_view.ns, {
|
||||
on_win = function(_, win, buf, top, bot)
|
||||
if win ~= self.entries_win.win then
|
||||
return
|
||||
end
|
||||
|
||||
for i = top, bot do
|
||||
local e = self.entries[i + 1]
|
||||
if e then
|
||||
local v = e:get_view(self.offset)
|
||||
local o = 1
|
||||
for _, key in ipairs({ 'abbr', 'kind', 'menu' }) do
|
||||
if self.column_bytes[key] > 0 then
|
||||
vim.api.nvim_buf_set_extmark(buf, custom_entries_view.ns, i, o, {
|
||||
end_line = i,
|
||||
end_col = o + v[key].bytes,
|
||||
hl_group = v[key].hl_group,
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
o = o + self.column_bytes[key] + 1
|
||||
end
|
||||
end
|
||||
for _, m in ipairs(e.matches or {}) do
|
||||
vim.api.nvim_buf_set_extmark(buf, custom_entries_view.ns, i, m.word_match_start, {
|
||||
end_line = i,
|
||||
end_col = m.word_match_end + 1,
|
||||
hl_group = m.fuzzy and 'CmpItemAbbrMatchFuzzy' or 'CmpItemAbbrMatch',
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
custom_entries_view.ready = function()
|
||||
return vim.fn.pumvisible() == 0
|
||||
end
|
||||
|
||||
custom_entries_view.redraw = function()
|
||||
-- noop
|
||||
end
|
||||
|
||||
custom_entries_view.open = function(self, offset, entries)
|
||||
self.offset = offset
|
||||
self.entries = {}
|
||||
self.column_bytes = { abbr = 0, kind = 0, menu = 0 }
|
||||
self.column_width = { abbr = 0, kind = 0, menu = 0 }
|
||||
|
||||
local lines = {}
|
||||
local dedup = {}
|
||||
local preselect = 0
|
||||
local i = 1
|
||||
for _, e in ipairs(entries) do
|
||||
local view = e:get_view(offset)
|
||||
if view.dup == 1 or not dedup[e.completion_item.label] then
|
||||
dedup[e.completion_item.label] = true
|
||||
self.column_bytes.abbr = math.max(self.column_bytes.abbr, view.abbr.bytes)
|
||||
self.column_bytes.kind = math.max(self.column_bytes.kind, view.kind.bytes)
|
||||
self.column_bytes.menu = math.max(self.column_bytes.menu, view.menu.bytes)
|
||||
self.column_width.abbr = math.max(self.column_width.abbr, view.abbr.width)
|
||||
self.column_width.kind = math.max(self.column_width.kind, view.kind.width)
|
||||
self.column_width.menu = math.max(self.column_width.menu, view.menu.width)
|
||||
table.insert(self.entries, e)
|
||||
table.insert(lines, ' ')
|
||||
if preselect == 0 and e.completion_item.preselect then
|
||||
preselect = i
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(self.entries_win.buf, 0, -1, false, lines)
|
||||
|
||||
local width = 0
|
||||
width = width + 1
|
||||
width = width + self.column_width.abbr + (self.column_width.kind > 0 and 1 or 0)
|
||||
width = width + self.column_width.kind + (self.column_width.menu > 0 and 1 or 0)
|
||||
width = width + self.column_width.menu + 1
|
||||
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local row = vim.fn.screenpos('.', cursor[1], cursor[2] + 1).row
|
||||
local height = vim.api.nvim_get_option('pumheight')
|
||||
height = height == 0 and #self.entries or height
|
||||
height = math.min(height, #self.entries)
|
||||
if (vim.o.lines - row) <= 8 and row - 8 > 0 then
|
||||
height = math.min(height, row - 1)
|
||||
row = row - height - 1
|
||||
else
|
||||
height = math.min(height, vim.o.lines - row)
|
||||
end
|
||||
|
||||
if width < 1 or height < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local delta = cursor[2] + 1 - self.offset
|
||||
self.entries_win:option('cursorline', false)
|
||||
self.entries_win:open({
|
||||
relative = 'editor',
|
||||
style = 'minimal',
|
||||
row = row,
|
||||
col = vim.fn.screencol() - 1 - delta - 1,
|
||||
width = width,
|
||||
height = height,
|
||||
zindex = 1001,
|
||||
})
|
||||
vim.api.nvim_win_set_cursor(self.entries_win.win, { 1, 1 })
|
||||
|
||||
if preselect > 0 and config.get().preselect == types.cmp.PreselectMode.Item then
|
||||
self:preselect(preselect)
|
||||
elseif string.match(config.get().completion.completeopt, 'noinsert') then
|
||||
self:preselect(1)
|
||||
else
|
||||
self:draw()
|
||||
end
|
||||
self.event:emit('change')
|
||||
end
|
||||
|
||||
custom_entries_view.close = function(self)
|
||||
self.offset = -1
|
||||
self.entries = {}
|
||||
self.entries_win:close()
|
||||
end
|
||||
|
||||
custom_entries_view.abort = function(self)
|
||||
if self.prefix then
|
||||
self:_insert(self.prefix)
|
||||
end
|
||||
self:close()
|
||||
end
|
||||
|
||||
custom_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
|
||||
local texts = {}
|
||||
for i = topline, botline - 1 do
|
||||
local view = self.entries[i + 1]:get_view(self.offset)
|
||||
local text = {}
|
||||
table.insert(text, ' ')
|
||||
table.insert(text, view.abbr.text)
|
||||
table.insert(text, string.rep(' ', 1 + self.column_width.abbr - view.abbr.width))
|
||||
table.insert(text, view.kind.text)
|
||||
table.insert(text, string.rep(' ', 1 + self.column_width.kind - view.kind.width))
|
||||
table.insert(text, view.menu.text)
|
||||
table.insert(text, string.rep(' ', 1 + self.column_width.menu - view.menu.width))
|
||||
table.insert(text, ' ')
|
||||
table.insert(texts, table.concat(text, ''))
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(self.entries_win.buf, topline, botline, false, texts)
|
||||
end
|
||||
|
||||
custom_entries_view.visible = function(self)
|
||||
return self.entries_win:visible()
|
||||
end
|
||||
|
||||
custom_entries_view.info = function(self)
|
||||
return self.entries_win:info()
|
||||
end
|
||||
|
||||
custom_entries_view.preselect = function(self, index)
|
||||
if self:visible() then
|
||||
if index <= #self.entries then
|
||||
self.entries_win:option('cursorline', true)
|
||||
vim.api.nvim_win_set_cursor(self.entries_win.win, { index, 1 })
|
||||
self.entries_win:update()
|
||||
self:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.select_next_item = function(self, option)
|
||||
if self.entries_win:visible() then
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1] + 1
|
||||
if not self.entries_win:option('cursorline') then
|
||||
cursor = 1
|
||||
elseif #self.entries < cursor then
|
||||
cursor = 0
|
||||
end
|
||||
self:_select(cursor, option)
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.select_prev_item = function(self, option)
|
||||
if self.entries_win:visible() then
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)[1] - 1
|
||||
if not self.entries_win:option('cursorline') then
|
||||
cursor = #self.entries
|
||||
end
|
||||
self:_select(cursor, option)
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.get_first_entry = function(self)
|
||||
if self.entries_win:visible() then
|
||||
return self.entries[1]
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view.get_selected_entry = function(self)
|
||||
if self.entries_win: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)
|
||||
if self.entries_win:visible() and self.entries_win:option('cursorline') then
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.entries_win.win)
|
||||
if cursor[2] == 0 then
|
||||
return self:get_selected_entry()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
custom_entries_view._select = function(self, cursor, option)
|
||||
local is_insert = (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert
|
||||
if is_insert then
|
||||
if vim.api.nvim_win_get_cursor(self.entries_win.win)[2] == 1 then
|
||||
self.prefix = string.sub(vim.api.nvim_get_current_line(), self.offset, vim.api.nvim_win_get_cursor(0)[2]) or ''
|
||||
end
|
||||
end
|
||||
|
||||
self.entries_win:option('cursorline', cursor > 0)
|
||||
vim.api.nvim_win_set_cursor(self.entries_win.win, { math.max(cursor, 1), is_insert and 0 or 1 })
|
||||
|
||||
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')
|
||||
end
|
||||
|
||||
custom_entries_view._insert = function(self, word)
|
||||
vim.api.nvim_buf_set_keymap(0, 'i', '<Plug>(cmp.view.custom_entries_view._insert.remove)', ('v:lua.cmp.view.custom_entries_view._insert.remove(%s)'):format(self.offset), {
|
||||
expr = true,
|
||||
noremap = true,
|
||||
})
|
||||
keymap.feedkeys(keymap.t('<Plug>(cmp.view.custom_entries_view._insert.remove)'), 't')
|
||||
keymap.feedkeys(word, 'nt')
|
||||
end
|
||||
|
||||
misc.set(_G, { 'cmp', 'view', 'custom_entries_view', '_insert', 'remove' }, function(offset)
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local length = vim.str_utfindex(string.sub(vim.api.nvim_get_current_line(), offset, cursor[2]))
|
||||
return keymap.t(string.rep('<C-g>U<Left><Del>', length))
|
||||
end)
|
||||
|
||||
return custom_entries_view
|
||||
126
lua/cmp/view/docs_view.lua
Normal file
126
lua/cmp/view/docs_view.lua
Normal file
@@ -0,0 +1,126 @@
|
||||
local window = require('cmp.utils.window')
|
||||
local config = require('cmp.config')
|
||||
|
||||
---@class cmp.DocsView
|
||||
---@field public window cmp.Window
|
||||
local docs_view = {}
|
||||
|
||||
---Create new floating window module
|
||||
docs_view.new = function()
|
||||
local self = setmetatable({}, { __index = docs_view })
|
||||
self.entry = nil
|
||||
self.window = window.new()
|
||||
self.window:option('conceallevel', 2)
|
||||
self.window:option('concealcursor', 'n')
|
||||
self.window:option('foldenable', false)
|
||||
self.window:option('scrolloff', 0)
|
||||
self.window:option('wrap', true)
|
||||
return self
|
||||
end
|
||||
|
||||
---Open documentation window
|
||||
---@param e cmp.Entry
|
||||
---@param view cmp.WindowStyle
|
||||
docs_view.open = function(self, e, view)
|
||||
local documentation = config.get().documentation
|
||||
if not documentation then
|
||||
return
|
||||
end
|
||||
|
||||
if not e or not view then
|
||||
return self:close()
|
||||
end
|
||||
|
||||
local right_space = vim.o.columns - (view.col + view.width) - 2
|
||||
local left_space = view.col - 2
|
||||
local maxwidth = math.min(documentation.maxwidth, math.max(left_space, right_space))
|
||||
|
||||
-- update buffer content if needed.
|
||||
if not self.entry or e.id ~= self.entry.id then
|
||||
local documents = e:get_documentation()
|
||||
if #documents == 0 then
|
||||
return self:close()
|
||||
end
|
||||
|
||||
self.entry = e
|
||||
vim.api.nvim_buf_call(self.window.buf, function()
|
||||
vim.cmd([[syntax clear]])
|
||||
end)
|
||||
vim.lsp.util.stylize_markdown(self.window.buf, documents, {
|
||||
max_width = maxwidth,
|
||||
max_height = documentation.maxheight,
|
||||
})
|
||||
end
|
||||
|
||||
local width, height = vim.lsp.util._make_floating_popup_size(vim.api.nvim_buf_get_lines(self.window.buf, 0, -1, false), {
|
||||
max_width = maxwidth,
|
||||
max_height = documentation.maxheight,
|
||||
})
|
||||
if width <= 0 or height <= 0 then
|
||||
return self:close()
|
||||
end
|
||||
|
||||
local right_col = view.col + view.width
|
||||
local left_col = view.col - width - 2
|
||||
|
||||
local col, left
|
||||
if right_space >= width and left_space >= width then
|
||||
if right_space < left_space then
|
||||
col = left_col
|
||||
left = true
|
||||
else
|
||||
col = right_col
|
||||
end
|
||||
elseif right_space >= width then
|
||||
col = right_col
|
||||
elseif left_space >= width then
|
||||
col = left_col
|
||||
left = true
|
||||
else
|
||||
return self:close()
|
||||
end
|
||||
|
||||
self.window:option('winhighlight', documentation.winhighlight)
|
||||
self.window:set_style({
|
||||
relative = 'editor',
|
||||
style = 'minimal',
|
||||
width = width,
|
||||
height = height,
|
||||
row = view.row,
|
||||
col = col,
|
||||
border = documentation.border,
|
||||
})
|
||||
if left and self.window:has_scrollbar() then
|
||||
self.window.style.col = self.window.style.col - 1
|
||||
end
|
||||
self.window:open()
|
||||
end
|
||||
|
||||
---Close floating window
|
||||
docs_view.close = function(self)
|
||||
self.window:close()
|
||||
self.entry = nil
|
||||
end
|
||||
|
||||
docs_view.scroll = function(self, delta)
|
||||
if self:visible() then
|
||||
local info = vim.fn.getwininfo(self.window.win)[1] or {}
|
||||
local top = info.topline or 1
|
||||
top = top + delta
|
||||
top = math.max(top, 1)
|
||||
top = math.min(top, self.window:get_content_height() - info.height + 1)
|
||||
|
||||
vim.defer_fn(function()
|
||||
vim.api.nvim_buf_call(self.window.buf, function()
|
||||
vim.api.nvim_command('normal! ' .. top .. 'zt')
|
||||
self.window:update()
|
||||
end)
|
||||
end, 0)
|
||||
end
|
||||
end
|
||||
|
||||
docs_view.visible = function(self)
|
||||
return self.window:visible()
|
||||
end
|
||||
|
||||
return docs_view
|
||||
72
lua/cmp/view/ghost_text_view.lua
Normal file
72
lua/cmp/view/ghost_text_view.lua
Normal file
@@ -0,0 +1,72 @@
|
||||
local config = require('cmp.config')
|
||||
local str = require('cmp.utils.str')
|
||||
local types = require('cmp.types')
|
||||
|
||||
---@class cmp.GhostTextView
|
||||
local ghost_text_view = {}
|
||||
|
||||
ghost_text_view.ns = vim.api.nvim_create_namespace('cmp:GHOST_TEXT')
|
||||
|
||||
ghost_text_view.new = function()
|
||||
local self = setmetatable({}, { __index = ghost_text_view })
|
||||
self.win = nil
|
||||
self.entry = nil
|
||||
vim.api.nvim_set_decoration_provider(ghost_text_view.ns, {
|
||||
on_win = function(_, win)
|
||||
return win == self.win
|
||||
end,
|
||||
on_line = function(_)
|
||||
local c = config.get().experimental.ghost_text
|
||||
if not c then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.entry then
|
||||
return
|
||||
end
|
||||
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
if string.sub(vim.api.nvim_get_current_line(), cursor[2] + 1) ~= '' then
|
||||
return
|
||||
end
|
||||
|
||||
local diff = 1 + cursor[2] - self.entry:get_offset()
|
||||
local text = self.entry:get_insert_text()
|
||||
if self.entry.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||
text = vim.lsp.util.parse_snippet(text)
|
||||
end
|
||||
text = string.sub(str.oneline(text), diff + 1)
|
||||
if #text > 0 then
|
||||
vim.api.nvim_buf_set_extmark(0, ghost_text_view.ns, cursor[1] - 1, cursor[2], {
|
||||
right_gravity = false,
|
||||
virt_text = { { text, c.hl_group or 'Comment' } },
|
||||
virt_text_pos = 'overlay',
|
||||
hl_mode = 'combine',
|
||||
ephemeral = true,
|
||||
})
|
||||
end
|
||||
end,
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
---Show ghost text
|
||||
---@param e cmp.Entry
|
||||
ghost_text_view.show = function(self, e)
|
||||
local changed = e ~= self.entry
|
||||
self.win = vim.api.nvim_get_current_win()
|
||||
self.entry = e
|
||||
if changed then
|
||||
vim.cmd([[redraw!]]) -- force invoke decoration provider.
|
||||
end
|
||||
end
|
||||
|
||||
ghost_text_view.hide = function(self)
|
||||
if self.win and self.entry then
|
||||
self.win = nil
|
||||
self.entry = nil
|
||||
vim.cmd([[redraw!]]) -- force invoke decoration provider.
|
||||
end
|
||||
end
|
||||
|
||||
return ghost_text_view
|
||||
152
lua/cmp/view/native_entries_view.lua
Normal file
152
lua/cmp/view/native_entries_view.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
local event = require('cmp.utils.event')
|
||||
local autocmd = require('cmp.utils.autocmd')
|
||||
local keymap = require('cmp.utils.keymap')
|
||||
local types = require('cmp.types')
|
||||
local config = require('cmp.config')
|
||||
|
||||
---@class cmp.NativeEntriesView
|
||||
---@field private offset number
|
||||
---@field private items vim.CompletedItem
|
||||
---@field private entries cmp.Entry[]
|
||||
---@field private preselect number
|
||||
---@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 = 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.redraw = function(self)
|
||||
if #self.entries > 0 and self.offset <= vim.api.nvim_win_get_cursor(0)[2] then
|
||||
local completeopt = vim.o.completeopt
|
||||
vim.o.completeopt = self.preselect == 1 and 'menu,menuone,noinsert' or config.get().completion.completeopt
|
||||
vim.fn.complete(self.offset, self.items)
|
||||
vim.o.completeopt = completeopt
|
||||
|
||||
if self.preselect > 1 and config.get().preselect == types.cmp.PreselectMode.Item then
|
||||
self:preselect(self.preselect)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.open = function(self, offset, entries)
|
||||
local dedup = {}
|
||||
local items = {}
|
||||
local dedup_entries = {}
|
||||
local preselect = 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 == 0 and e.completion_item.preselect then
|
||||
preselect = #dedup_entries
|
||||
end
|
||||
end
|
||||
end
|
||||
self.offset = offset
|
||||
self.items = items
|
||||
self.entries = dedup_entries
|
||||
self.preselect = preselect
|
||||
self:redraw()
|
||||
end
|
||||
|
||||
native_entries_view.close = function(self)
|
||||
if string.sub(vim.api.nvim_get_mode().mode, 1, 1) == 'i' then
|
||||
vim.fn.complete(1, {})
|
||||
end
|
||||
self.offset = -1
|
||||
self.entries = {}
|
||||
self.items = {}
|
||||
self.preselect = 0
|
||||
end
|
||||
|
||||
native_entries_view.abort = function(_)
|
||||
if string.sub(vim.api.nvim_get_mode().mode, 1, 1) == 'i' 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),
|
||||
height = info.height,
|
||||
row = info.row,
|
||||
col = info.col,
|
||||
}
|
||||
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)
|
||||
if self:visible() then
|
||||
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
|
||||
keymap.feedkeys(keymap.t('<C-n>'), 'n')
|
||||
else
|
||||
keymap.feedkeys(keymap.t('<Down>'), 'n')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
native_entries_view.select_prev_item = function(self, option)
|
||||
if self:visible() then
|
||||
if (option.behavior or types.cmp.SelectBehavior.Insert) == types.cmp.SelectBehavior.Insert then
|
||||
keymap.feedkeys(keymap.t('<C-p>'), 'n')
|
||||
else
|
||||
keymap.feedkeys(keymap.t('<Up>'), 'n')
|
||||
end
|
||||
end
|
||||
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
|
||||
Reference in New Issue
Block a user