Implement shell like common string completion.

Fix #785
This commit is contained in:
hrsh7th
2022-02-13 14:34:28 +09:00
parent 7e473b99d3
commit 070c5572ad
10 changed files with 154 additions and 30 deletions

View File

@@ -204,6 +204,23 @@ NOTE: You can call these functions in mapping via `<Cmd>lua require('cmp').compl
<
NOTE: The `config` means a temporary setting, but the `config.mapping` remains permanent.
*cmp.complete_common_string* ()
Complete common string as like as shell completion behavior.
>
cmp.setup {
mapping = {
['<C-n>'] = cmp.mapping(function(fallback)
if cmp.visible() then
if cmp.complete_common_string() then
return
end
return cmp.select_next_item()
end
fallback()
end, { 'i', 'c' }),
}
}
<
*cmp.confirm* (option: cmp.ConfirmOption, callback: function)
Accept current selected completion item.
If you didn't select any items and specified the `{ select = true }` for
@@ -279,6 +296,9 @@ You can also use built-in mapping helpers.
*cmp.mapping.complete* (option: cmp.CompleteParams)
Same as |cmp.complete|
*cmp.mapping.complete_common_string* ()
Same as |cmp.complete_common_string|
*cmp.mapping.confirm* (option: cmp.ConfirmOption)
Same as |cmp.confirm|

View File

@@ -22,6 +22,15 @@ mapping.complete = function(option)
end
end
---Complete common string.
mapping.complete_common_string = function()
return function(fallback)
if not require('cmp').complete_common_string() then
fallback()
end
end
end
---Close current completion menu if it displayed.
mapping.close = function()
return function(fallback)

View File

@@ -1,4 +1,5 @@
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')
@@ -217,6 +218,32 @@ core.autoindent = function(self, trigger_event, callback)
callback()
end
---Complete common string for current completed entries.
core.complete_common_string = function(self)
if not self.view:visible() then
return false
end
self.filter:sync(1000)
local cursor = api.get_cursor()
local offset = self.view:get_offset()
local common_string
for _, e in ipairs(self.view:get_entries()) do
local vim_item = e:get_vim_item(offset)
if not common_string then
common_string = vim_item.word
else
common_string = str.get_common_string(common_string, vim_item.word)
end
end
if common_string and #common_string > (1 + cursor[2] - offset) then
feedkeys.call(keymap.backspace(string.sub(api.get_current_line(), offset, cursor[2])) .. common_string, 'n')
return true
end
return false
end
---Invoke completion
---@param ctx cmp.Context
core.complete = function(self, ctx)

View File

@@ -75,6 +75,10 @@ cmp.complete = cmp.sync(function(option)
return true
end)
cmp.complete_common_string = cmp.sync(function()
return cmp.core:complete_common_string()
end)
---Return view is visible or not.
cmp.visible = cmp.sync(function()
return cmp.core.view:visible() or vim.fn.pumvisible() == 1

View File

@@ -67,6 +67,9 @@ end
---@param count number
---@return string
keymap.backspace = function(count)
if type(count) == 'string' then
count = vim.fn.strchars(count, true)
end
if count <= 0 then
return ''
end

View File

@@ -43,6 +43,17 @@ str.has_prefix = function(text, prefix)
return true
end
---get_common_string
str.get_common_string = function(text1, text2)
local min = math.min(#text1, #text2)
for i = 1, min do
if not char.match(string.byte(text1, i), string.byte(text2, i)) then
return string.sub(text1, 1, i - 1)
end
end
return string.sub(text1, 1, min)
end
---Remove suffix
---@param text string
---@param suffix string

View File

@@ -161,6 +161,17 @@ view.select_prev_item = function(self, option)
self:_get_entries_view():select_prev_item(option)
end
---Get offset.
view.get_offset = function(self)
return self:_get_entries_view():get_offset()
end
---Get entries.
---@return cmp.Entry[]
view.get_entries = function(self)
return self:_get_entries_view():get_entries()
end
---Get first entry
---@param self cmp.Entry|nil
view.get_first_entry = function(self)

View File

@@ -257,6 +257,20 @@ custom_entries_view.select_prev_item = function(self, option)
end
end
custom_entries_view.get_offset = function(self)
if self:visible() then
return self.offset
end
return nil
end
custom_entries_view.get_entries = function(self)
if self:visible() then
return self.entries
end
return {}
end
custom_entries_view.get_first_entry = function(self)
if self:visible() then
return self.entries[1]
@@ -301,8 +315,7 @@ custom_entries_view._insert = setmetatable({
word = word or ''
if api.is_cmdline_mode() then
local cursor = api.get_cursor()
local length = vim.fn.strchars(string.sub(api.get_current_line(), self.offset, cursor[2]), true)
vim.api.nvim_feedkeys(keymap.backspace(length) .. word, 'int', true)
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
@@ -312,10 +325,9 @@ custom_entries_view._insert = setmetatable({
local release = require('cmp').suspend()
feedkeys.call('', '', function()
local cursor = api.get_cursor()
local length = vim.fn.strchars(string.sub(api.get_current_line(), self.offset, cursor[2]), true)
local keys = {}
table.insert(keys, keymap.indentkeys())
table.insert(keys, keymap.backspace(length))
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(

View File

@@ -143,6 +143,20 @@ native_entries_view.select_prev_item = function(self, option)
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]

View File

@@ -14,16 +14,16 @@ local api = require('cmp.utils.api')
---@field private active boolean
---@field private entries cmp.Entry[]
---@field public event cmp.Event
local statusline_entries_view = {}
local wildmenu_entries_view = {}
local function get_separator()
local c = config.get()
return (c and c.view and c.view.entries and c.view.entries.separator) or ' '
end
statusline_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.statusline_entries_view')
wildmenu_entries_view.ns = vim.api.nvim_create_namespace('cmp.view.statusline_entries_view')
statusline_entries_view.init = function(self)
wildmenu_entries_view.init = function(self)
self.event = event.new()
self.offset = -1
self.active = false
@@ -33,8 +33,8 @@ statusline_entries_view.init = function(self)
self.moving_forwards = nil
end
statusline_entries_view.new = function()
local self = setmetatable({}, { __index = statusline_entries_view })
wildmenu_entries_view.new = function()
local self = setmetatable({}, { __index = wildmenu_entries_view })
self:init()
self.entries_win = window.new()
@@ -47,7 +47,7 @@ statusline_entries_view.new = function()
self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
self.entries_win:buffer_option('tabstop', 1)
vim.api.nvim_set_decoration_provider(statusline_entries_view.ns, {
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
@@ -58,7 +58,7 @@ statusline_entries_view.new = function()
local e = self.entries[i]
if e then
local view = e:get_view(self.offset, buf)
vim.api.nvim_buf_set_extmark(buf, statusline_entries_view.ns, 0, location, {
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, location, {
end_line = 0,
end_col = location + view['abbr'].bytes,
hl_group = view['abbr'].hl_group,
@@ -67,7 +67,7 @@ statusline_entries_view.new = function()
})
if i == self.selected_index then
vim.api.nvim_buf_set_extmark(buf, statusline_entries_view.ns, 0, location, {
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, location, {
end_line = 0,
end_col = location + view['abbr'].bytes,
hl_group = 'PmenuSel',
@@ -77,7 +77,7 @@ statusline_entries_view.new = function()
end
for _, m in ipairs(e.matches or {}) do
vim.api.nvim_buf_set_extmark(buf, statusline_entries_view.ns, 0, location + m.word_match_start - 1, {
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, location + m.word_match_start - 1, {
end_line = 0,
end_col = location + m.word_match_end,
hl_group = m.fuzzy and 'CmpItemAbbrMatchFuzzy' or 'CmpItemAbbrMatch',
@@ -103,20 +103,20 @@ statusline_entries_view.new = function()
return self
end
statusline_entries_view.close = function(self)
wildmenu_entries_view.close = function(self)
self.entries_win:close()
self:init()
end
statusline_entries_view.ready = function()
wildmenu_entries_view.ready = function()
return vim.fn.pumvisible() == 0
end
statusline_entries_view.on_change = function(self)
wildmenu_entries_view.on_change = function(self)
self.active = false
end
statusline_entries_view.open = function(self, offset, entries)
wildmenu_entries_view.open = function(self, offset, entries)
self.offset = offset
self.entries = {}
self.last_displayed_indices = {}
@@ -159,13 +159,13 @@ statusline_entries_view.open = function(self, offset, entries)
end
end
statusline_entries_view.abort = function(self)
wildmenu_entries_view.abort = function(self)
feedkeys.call('', 'n', function()
self:close()
end)
end
statusline_entries_view.draw = function(self)
wildmenu_entries_view.draw = function(self)
local entries_buf = self.entries_win:get_buffer()
local texts = {}
local lengths = {}
@@ -235,15 +235,15 @@ statusline_entries_view.draw = function(self)
end)
end
statusline_entries_view.visible = function(self)
wildmenu_entries_view.visible = function(self)
return self.entries_win:visible()
end
statusline_entries_view.info = function(self)
wildmenu_entries_view.info = function(self)
return self.entries_win:info()
end
statusline_entries_view.select_next_item = function(self, option)
wildmenu_entries_view.select_next_item = function(self, option)
if self:visible() then
self.moving_forwards = true
if self.selected_index == nil or self.selected_index == #self.entries then
@@ -254,7 +254,7 @@ statusline_entries_view.select_next_item = function(self, option)
end
end
statusline_entries_view.select_prev_item = function(self, option)
wildmenu_entries_view.select_prev_item = function(self, option)
if self:visible() then
self.moving_forwards = false
if self.selected_index == nil or self.selected_index <= 1 then
@@ -265,33 +265,46 @@ statusline_entries_view.select_prev_item = function(self, option)
end
end
statusline_entries_view.get_first_entry = function(self)
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
statusline_entries_view.get_selected_entry = function(self)
wildmenu_entries_view.get_selected_entry = function(self)
if self:visible() and self.active then
return self.entries[self.selected_index]
end
end
statusline_entries_view.get_active_entry = function(self)
wildmenu_entries_view.get_active_entry = function(self)
if self:visible() and self.active then
return self:get_selected_entry()
end
end
statusline_entries_view._select = function(self, selected_index, _)
wildmenu_entries_view._select = function(self, selected_index, _)
self.selected_index = selected_index
self.active = (selected_index ~= nil)
if self.active then
local cursor = api.get_cursor()
local word = self:get_active_entry():get_vim_item(self.offset).word
local length = vim.fn.strchars(string.sub(api.get_current_line(), self.offset, cursor[2]), true)
vim.api.nvim_feedkeys(keymap.backspace(length) .. word, 'int', true)
vim.api.nvim_feedkeys(keymap.backspace(string.sub(api.get_current_line(), self.offset, cursor[2])) .. word, 'int', true)
end
self.entries_win:update()
@@ -299,4 +312,4 @@ statusline_entries_view._select = function(self, selected_index, _)
self.event:emit('change')
end
return statusline_entries_view
return wildmenu_entries_view