perf: improve for source providing huge list of items (#1980)
* perf: avoid creating closure in cache.ensure and drop some cached getters This mainly addresses the perf issue on large amount of calls to `entry.new`. Previously every `cache.ensure` calls in the code path of it creates an anonymous function, and it seems that luajit just could not inline it. Function creation is not expensive in luajit, but that overhead is noticeable if every `cache.ensure` call creates a function. The first improvemnt is to solidate the cache callback and attach it to the metatable of `entry`. This ensures that every created entry instance share the same cache callback and no new functions will be frequently created, reduces the ram usage and GC overhead. To improve it further, some frequently accessed fields of entry like `completion_item` and `offset` is refactored to use simple table access instead of getter pattern. The current cached getter is implemented using `cache.ensure`, which introduces two more levels of function calls on each access: `cache.key` and `cache.get`. The overhead is okay if but noticeable if entries amount is quite large: you need to call 4 functions on a simple `completion_item` field access for each item. All of the changes done in the commit is just constant time optimization. But the different is huge if tested with LS providing large amount of entries like tailwindcss. * perf: delay fuzzy match on displayed vim item `entry.get_vim_item` is a very heavy call, especially when user do complex stuff on item formatting. Delay its call to window displaying to let `performance.max_view_entries` applied to it. * remove unneeded fill_defaults * update gha --------- Co-authored-by: hrsh7th <629908+hrsh7th@users.noreply.github.com>
This commit is contained in:
4
.github/workflows/integration.yaml
vendored
4
.github/workflows/integration.yaml
vendored
@@ -23,9 +23,9 @@ jobs:
|
|||||||
neovim: true
|
neovim: true
|
||||||
|
|
||||||
- name: Setup lua
|
- name: Setup lua
|
||||||
uses: leafo/gh-actions-lua@v8
|
uses: leafo/gh-actions-lua@v10
|
||||||
with:
|
with:
|
||||||
luaVersion: "luajit-2.1.0-beta3"
|
luaVersion: "luajit-openresty"
|
||||||
|
|
||||||
- name: Setup luarocks
|
- name: Setup luarocks
|
||||||
uses: leafo/gh-actions-luarocks@v4
|
uses: leafo/gh-actions-luarocks@v4
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ local compare = {}
|
|||||||
---offset: Entries with smaller offset will be ranked higher.
|
---offset: Entries with smaller offset will be ranked higher.
|
||||||
---@type cmp.ComparatorFunction
|
---@type cmp.ComparatorFunction
|
||||||
compare.offset = function(entry1, entry2)
|
compare.offset = function(entry1, entry2)
|
||||||
local diff = entry1:get_offset() - entry2:get_offset()
|
local diff = entry1.offset - entry2.offset
|
||||||
if diff < 0 then
|
if diff < 0 then
|
||||||
return true
|
return true
|
||||||
elseif diff > 0 then
|
elseif diff > 0 then
|
||||||
@@ -180,8 +180,8 @@ compare.locality = setmetatable({
|
|||||||
}, {
|
}, {
|
||||||
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
||||||
__call = function(self, entry1, entry2)
|
__call = function(self, entry1, entry2)
|
||||||
local local1 = self.locality_map[entry1:get_word()]
|
local local1 = self.locality_map[entry1.word]
|
||||||
local local2 = self.locality_map[entry2:get_word()]
|
local local2 = self.locality_map[entry2.word]
|
||||||
if local1 ~= local2 then
|
if local1 ~= local2 then
|
||||||
if local1 == nil then
|
if local1 == nil then
|
||||||
return false
|
return false
|
||||||
@@ -255,8 +255,8 @@ compare.scopes = setmetatable({
|
|||||||
}, {
|
}, {
|
||||||
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
||||||
__call = function(self, entry1, entry2)
|
__call = function(self, entry1, entry2)
|
||||||
local local1 = self.scopes_map[entry1:get_word()]
|
local local1 = self.scopes_map[entry1.word]
|
||||||
local local2 = self.scopes_map[entry2:get_word()]
|
local local2 = self.scopes_map[entry2.word]
|
||||||
if local1 ~= local2 then
|
if local1 ~= local2 then
|
||||||
if local1 == nil then
|
if local1 == nil then
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ core.on_keymap = function(self, keys, fallback)
|
|||||||
commit_character = chars,
|
commit_character = chars,
|
||||||
}, function()
|
}, function()
|
||||||
local ctx = self:get_context()
|
local ctx = self:get_context()
|
||||||
local word = e:get_word()
|
local word = e.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()
|
fallback()
|
||||||
else
|
else
|
||||||
@@ -358,7 +358,7 @@ core.confirm = function(self, e, option, callback)
|
|||||||
end
|
end
|
||||||
e.confirmed = true
|
e.confirmed = true
|
||||||
|
|
||||||
debug.log('entry.confirm', e:get_completion_item())
|
debug.log('entry.confirm', e.completion_item)
|
||||||
|
|
||||||
async.sync(function(done)
|
async.sync(function(done)
|
||||||
e:resolve(done)
|
e:resolve(done)
|
||||||
@@ -374,8 +374,8 @@ core.confirm = function(self, e, option, callback)
|
|||||||
-- Emulate `<C-y>` behavior to save `.` register.
|
-- Emulate `<C-y>` behavior to save `.` register.
|
||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
local keys = {}
|
local keys = {}
|
||||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e.offset)))
|
||||||
table.insert(keys, e:get_word())
|
table.insert(keys, e.word)
|
||||||
table.insert(keys, keymap.undobreak())
|
table.insert(keys, keymap.undobreak())
|
||||||
feedkeys.call(table.concat(keys, ''), 'in')
|
feedkeys.call(table.concat(keys, ''), 'in')
|
||||||
end)
|
end)
|
||||||
@@ -384,15 +384,15 @@ core.confirm = function(self, e, option, callback)
|
|||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
if api.is_cmdline_mode() then
|
if api.is_cmdline_mode() then
|
||||||
local keys = {}
|
local keys = {}
|
||||||
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e:get_offset())))
|
table.insert(keys, keymap.backspace(ctx.cursor_before_line:sub(e.offset)))
|
||||||
table.insert(keys, string.sub(e.context.cursor_before_line, e:get_offset()))
|
table.insert(keys, string.sub(e.context.cursor_before_line, e.offset))
|
||||||
feedkeys.call(table.concat(keys, ''), 'in')
|
feedkeys.call(table.concat(keys, ''), 'in')
|
||||||
else
|
else
|
||||||
vim.cmd([[silent! undojoin]])
|
vim.cmd([[silent! undojoin]])
|
||||||
-- This logic must be used nvim_buf_set_text.
|
-- This logic must be used nvim_buf_set_text.
|
||||||
-- If not used, the snippet engine's placeholder wil be broken.
|
-- 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, {
|
vim.api.nvim_buf_set_text(0, e.context.cursor.row - 1, e.offset - 1, ctx.cursor.row - 1, ctx.cursor.col - 1, {
|
||||||
e.context.cursor_before_line:sub(e:get_offset()),
|
e.context.cursor_before_line:sub(e.offset),
|
||||||
})
|
})
|
||||||
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
|
vim.api.nvim_win_set_cursor(0, { e.context.cursor.row, e.context.cursor.col - 1 })
|
||||||
end
|
end
|
||||||
@@ -400,10 +400,10 @@ core.confirm = function(self, e, option, callback)
|
|||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
-- Apply additionalTextEdits.
|
-- Apply additionalTextEdits.
|
||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
if #(e:get_completion_item().additionalTextEdits or {}) == 0 then
|
if #(e.completion_item.additionalTextEdits or {}) == 0 then
|
||||||
e:resolve(function()
|
e:resolve(function()
|
||||||
local new = context.new()
|
local new = context.new()
|
||||||
local text_edits = e:get_completion_item().additionalTextEdits or {}
|
local text_edits = e.completion_item.additionalTextEdits or {}
|
||||||
if #text_edits == 0 then
|
if #text_edits == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -428,12 +428,12 @@ core.confirm = function(self, e, option, callback)
|
|||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
vim.cmd([[silent! undojoin]])
|
vim.cmd([[silent! undojoin]])
|
||||||
vim.lsp.util.apply_text_edits(e:get_completion_item().additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind())
|
vim.lsp.util.apply_text_edits(e.completion_item.additionalTextEdits, ctx.bufnr, e.source:get_position_encoding_kind())
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
feedkeys.call('', 'n', function()
|
feedkeys.call('', 'n', function()
|
||||||
local ctx = context.new()
|
local ctx = context.new()
|
||||||
local completion_item = misc.copy(e:get_completion_item())
|
local completion_item = misc.copy(e.completion_item)
|
||||||
if not completion_item.textEdit then
|
if not completion_item.textEdit then
|
||||||
completion_item.textEdit = {}
|
completion_item.textEdit = {}
|
||||||
local insertText = completion_item.insertText
|
local insertText = completion_item.insertText
|
||||||
@@ -444,9 +444,9 @@ core.confirm = function(self, e, option, callback)
|
|||||||
end
|
end
|
||||||
local behavior = option.behavior or config.get().confirmation.default_behavior
|
local behavior = option.behavior or config.get().confirmation.default_behavior
|
||||||
if behavior == types.cmp.ConfirmBehavior.Replace then
|
if behavior == types.cmp.ConfirmBehavior.Replace then
|
||||||
completion_item.textEdit.range = e:get_replace_range()
|
completion_item.textEdit.range = e.replace_range
|
||||||
else
|
else
|
||||||
completion_item.textEdit.range = e:get_insert_range()
|
completion_item.textEdit.range = e.insert_range
|
||||||
end
|
end
|
||||||
|
|
||||||
local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
|
local diff_before = math.max(0, e.context.cursor.col - (completion_item.textEdit.range.start.character + 1))
|
||||||
@@ -460,15 +460,15 @@ core.confirm = function(self, e, option, callback)
|
|||||||
if false then
|
if false then
|
||||||
--To use complex expansion debug.
|
--To use complex expansion debug.
|
||||||
vim.print({ -- luacheck: ignore
|
vim.print({ -- luacheck: ignore
|
||||||
item = e:get_completion_item(),
|
item = e.completion_item,
|
||||||
diff_before = diff_before,
|
diff_before = diff_before,
|
||||||
diff_after = diff_after,
|
diff_after = diff_after,
|
||||||
new_text = new_text,
|
new_text = new_text,
|
||||||
text_edit_new_text = completion_item.textEdit.newText,
|
text_edit_new_text = completion_item.textEdit.newText,
|
||||||
range_start = completion_item.textEdit.range.start.character,
|
range_start = completion_item.textEdit.range.start.character,
|
||||||
range_end = completion_item.textEdit.range['end'].character,
|
range_end = completion_item.textEdit.range['end'].character,
|
||||||
original_range_start = e:get_completion_item().textEdit.range.start.character,
|
original_range_start = e.completion_item.textEdit.range.start.character,
|
||||||
original_range_end = e:get_completion_item().textEdit.range['end'].character,
|
original_range_end = e.completion_item.textEdit.range['end'].character,
|
||||||
cursor_line = ctx.cursor_line,
|
cursor_line = ctx.cursor_line,
|
||||||
cursor_col0 = ctx.cursor.col - 1,
|
cursor_col0 = ctx.cursor.col - 1,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,7 +25,14 @@ local matcher = require('cmp.matcher')
|
|||||||
---@field public resolved_callbacks fun()[]
|
---@field public resolved_callbacks fun()[]
|
||||||
---@field public resolving boolean
|
---@field public resolving boolean
|
||||||
---@field public confirmed boolean
|
---@field public confirmed boolean
|
||||||
|
---@field public insert_range lsp.Range
|
||||||
|
---@field public replace_range lsp.Range
|
||||||
|
---@field public offset integer
|
||||||
|
---@field public word string
|
||||||
|
---@field public filter_text string
|
||||||
|
---@field private match_view_args_ret {input:string, word:string, option:cmp.MatchingConfig, matches:table[]}
|
||||||
local entry = {}
|
local entry = {}
|
||||||
|
entry.__index = entry
|
||||||
|
|
||||||
---Create new entry
|
---Create new entry
|
||||||
---@param ctx cmp.Context
|
---@param ctx cmp.Context
|
||||||
@@ -34,7 +41,7 @@ local entry = {}
|
|||||||
---@param item_defaults? lsp.internal.CompletionItemDefaults
|
---@param item_defaults? lsp.internal.CompletionItemDefaults
|
||||||
---@return cmp.Entry
|
---@return cmp.Entry
|
||||||
entry.new = function(ctx, source, completion_item, item_defaults)
|
entry.new = function(ctx, source, completion_item, item_defaults)
|
||||||
local self = setmetatable({}, { __index = entry })
|
local self = setmetatable({}, entry)
|
||||||
self.id = misc.id('entry.new')
|
self.id = misc.id('entry.new')
|
||||||
self.cache = cache.new()
|
self.cache = cache.new()
|
||||||
self.match_cache = cache.new()
|
self.match_cache = cache.new()
|
||||||
@@ -43,27 +50,80 @@ entry.new = function(ctx, source, completion_item, item_defaults)
|
|||||||
self.matches = {}
|
self.matches = {}
|
||||||
self.context = ctx
|
self.context = ctx
|
||||||
self.source = source
|
self.source = source
|
||||||
|
self.offset = source.request_offset
|
||||||
self.source_offset = source.request_offset
|
self.source_offset = source.request_offset
|
||||||
self.source_insert_range = source:get_default_insert_range()
|
self.source_insert_range = source.default_insert_range
|
||||||
self.source_replace_range = source:get_default_replace_range()
|
self.source_replace_range = source.default_replace_range
|
||||||
self.completion_item = self:fill_defaults(completion_item, item_defaults)
|
|
||||||
self.item_defaults = item_defaults
|
self.item_defaults = item_defaults
|
||||||
self.resolved_completion_item = nil
|
self.resolved_completion_item = nil
|
||||||
self.resolved_callbacks = {}
|
self.resolved_callbacks = {}
|
||||||
self.resolving = false
|
self.resolving = false
|
||||||
self.confirmed = false
|
self.confirmed = false
|
||||||
|
self:_set_completion_item(completion_item)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---Make offset value
|
---@package
|
||||||
---@return integer
|
entry._set_completion_item = function(self, completion_item)
|
||||||
|
if not self.completion_item then
|
||||||
|
self.completion_item = self:fill_defaults(completion_item, self.item_defaults)
|
||||||
|
else
|
||||||
|
-- @see https://github.com/microsoft/vscode/blob/85eea4a9b2ccc99615e970bf2181edbc1781d0f9/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts#L588
|
||||||
|
-- @see https://github.com/microsoft/vscode/blob/85eea4a9b2ccc99615e970bf2181edbc1781d0f9/src/vs/base/common/objects.ts#L89
|
||||||
|
-- @see https://github.com/microsoft/vscode/blob/a00f2e64f4fa9a1f774875562e1e9697d7138ed3/src/vs/editor/contrib/suggest/browser/suggest.ts#L147
|
||||||
|
for k, v in pairs(completion_item) do
|
||||||
|
self.completion_item[k] = v or self.completion_item[k]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local item = self.completion_item
|
||||||
|
|
||||||
|
---Create filter text
|
||||||
|
self.filter_text = item.filterText or str.trim(item.label)
|
||||||
|
|
||||||
|
-- TODO: the order below is important
|
||||||
|
if item.textEdit then
|
||||||
|
self.insert_range = self:convert_range_encoding(item.textEdit.insert or item.textEdit.range)
|
||||||
|
self.replace_range = self:convert_range_encoding(item.textEdit.replace or item.textEdit.range)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.word = self:_get_word()
|
||||||
|
self.offset = self:_get_offset()
|
||||||
|
|
||||||
|
if not self.insert_range then
|
||||||
|
self.insert_range = {
|
||||||
|
start = {
|
||||||
|
line = self.context.cursor.row - 1,
|
||||||
|
character = self.offset - 1,
|
||||||
|
},
|
||||||
|
['end'] = self.source_insert_range['end'],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.replace_range or ((self.context.cursor.col - 1) == self.replace_range['end'].character) then
|
||||||
|
self.replace_range = {
|
||||||
|
start = {
|
||||||
|
line = self.source_replace_range.start.line,
|
||||||
|
character = self.offset - 1,
|
||||||
|
},
|
||||||
|
['end'] = self.source_replace_range['end'],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@deprecated use entry.offset instead
|
||||||
entry.get_offset = function(self)
|
entry.get_offset = function(self)
|
||||||
return self.cache:ensure('get_offset', function()
|
return self.offset
|
||||||
|
end
|
||||||
|
|
||||||
|
---Make offset value
|
||||||
|
---@package
|
||||||
|
---@return integer
|
||||||
|
entry._get_offset = function(self)
|
||||||
local offset = self.source_offset
|
local offset = self.source_offset
|
||||||
if self:get_completion_item().textEdit then
|
if self.completion_item.textEdit then
|
||||||
local range = self:get_insert_range()
|
local range = self.insert_range
|
||||||
if range then
|
if range then
|
||||||
offset = self.context.cache:ensure('entry:' .. 'get_offset:' .. tostring(range.start.character), function()
|
|
||||||
local start = math.min(range.start.character + 1, offset)
|
local start = math.min(range.start.character + 1, offset)
|
||||||
for idx = start, self.source_offset do
|
for idx = start, self.source_offset do
|
||||||
local byte = string.byte(self.context.cursor_line, idx)
|
local byte = string.byte(self.context.cursor_line, idx)
|
||||||
@@ -72,13 +132,12 @@ entry.get_offset = function(self)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
return offset
|
return offset
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- NOTE
|
-- NOTE
|
||||||
-- The VSCode does not implement this but it's useful if the server does not care about word patterns.
|
-- The VSCode does not implement this but it's useful if the server does not care about word patterns.
|
||||||
-- We should care about this performance.
|
-- We should care about this performance.
|
||||||
local word = self:get_word()
|
local word = self.word
|
||||||
for idx = self.source_offset - 1, self.source_offset - #word, -1 do
|
for idx = self.source_offset - 1, self.source_offset - #word, -1 do
|
||||||
if char.is_semantic_index(self.context.cursor_line, idx) then
|
if char.is_semantic_index(self.context.cursor_line, idx) then
|
||||||
local c = string.byte(self.context.cursor_line, idx)
|
local c = string.byte(self.context.cursor_line, idx)
|
||||||
@@ -101,101 +160,101 @@ entry.get_offset = function(self)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
return offset
|
return offset
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
---@deprecated use entry.word instead
|
||||||
|
entry.get_word = function(self)
|
||||||
|
return self.word
|
||||||
end
|
end
|
||||||
|
|
||||||
---Create word for vim.CompletedItem
|
---Create word for vim.CompletedItem
|
||||||
---NOTE: This method doesn't clear the cache after completionItem/resolve.
|
---NOTE: This method doesn't clear the cache after completionItem/resolve.
|
||||||
|
---@package
|
||||||
---@return string
|
---@return string
|
||||||
entry.get_word = function(self)
|
entry._get_word = function(self)
|
||||||
return self.cache:ensure('get_word', function()
|
|
||||||
--NOTE: This is nvim-cmp specific implementation.
|
--NOTE: This is nvim-cmp specific implementation.
|
||||||
if self:get_completion_item().word then
|
local completion_item = self.completion_item
|
||||||
return self:get_completion_item().word
|
if completion_item.word then
|
||||||
|
return completion_item.word
|
||||||
end
|
end
|
||||||
|
|
||||||
local word
|
local word
|
||||||
if self:get_completion_item().textEdit and not misc.empty(self:get_completion_item().textEdit.newText) then
|
if completion_item.textEdit and not misc.empty(completion_item.textEdit.newText) then
|
||||||
word = str.trim(self:get_completion_item().textEdit.newText)
|
word = str.trim(completion_item.textEdit.newText)
|
||||||
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = tostring(snippet.parse(word))
|
word = tostring(snippet.parse(word))
|
||||||
end
|
end
|
||||||
local overwrite = self:get_overwrite()
|
local overwrite = self:get_overwrite()
|
||||||
if 0 < overwrite[2] or self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if 0 < overwrite[2] or completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1), overwrite[1] or 0)
|
word = str.get_word(word, string.byte(self.context.cursor_after_line, 1), overwrite[1] or 0)
|
||||||
end
|
end
|
||||||
elseif not misc.empty(self:get_completion_item().insertText) then
|
elseif not misc.empty(completion_item.insertText) then
|
||||||
word = str.trim(self:get_completion_item().insertText)
|
word = str.trim(completion_item.insertText)
|
||||||
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.get_word(tostring(snippet.parse(word)))
|
word = str.get_word(tostring(snippet.parse(word)))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
word = str.trim(self:get_completion_item().label)
|
word = str.trim(completion_item.label)
|
||||||
end
|
end
|
||||||
return str.oneline(word)
|
return str.oneline(word)
|
||||||
end) --[[@as string]]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get overwrite information
|
---Get overwrite information
|
||||||
---@return integer[]
|
---@return integer[]
|
||||||
entry.get_overwrite = function(self)
|
entry.get_overwrite = function(self)
|
||||||
return self.cache:ensure('get_overwrite', function()
|
return self.cache:ensure('get_overwrite', entry._get_overwrite, self)
|
||||||
if self:get_completion_item().textEdit then
|
end
|
||||||
local range = self:get_insert_range()
|
|
||||||
|
---@package
|
||||||
|
entry._get_overwrite = function(self)
|
||||||
|
if self.completion_item.textEdit then
|
||||||
|
local range = self.insert_range
|
||||||
if range then
|
if range then
|
||||||
return self.context.cache:ensure('entry:' .. 'get_overwrite:' .. tostring(range.start.character) .. ':' .. tostring(range['end'].character), function()
|
|
||||||
local vim_start = range.start.character + 1
|
local vim_start = range.start.character + 1
|
||||||
local vim_end = range['end'].character + 1
|
local vim_end = range['end'].character + 1
|
||||||
local before = self.context.cursor.col - vim_start
|
local before = self.context.cursor.col - vim_start
|
||||||
local after = vim_end - self.context.cursor.col
|
local after = vim_end - self.context.cursor.col
|
||||||
return { before, after }
|
return { before, after }
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return { 0, 0 }
|
return { 0, 0 }
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Create filter text
|
---@package
|
||||||
---@return string
|
|
||||||
entry.get_filter_text = function(self)
|
entry.get_filter_text = function(self)
|
||||||
return self.cache:ensure('get_filter_text', function()
|
return self.filter_text
|
||||||
local word
|
|
||||||
if self:get_completion_item().filterText then
|
|
||||||
word = self:get_completion_item().filterText
|
|
||||||
else
|
|
||||||
word = str.trim(self:get_completion_item().label)
|
|
||||||
end
|
|
||||||
return word
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get LSP's insert text
|
---Get LSP's insert text
|
||||||
---@return string
|
---@return string
|
||||||
entry.get_insert_text = function(self)
|
entry.get_insert_text = function(self)
|
||||||
return self.cache:ensure('get_insert_text', function()
|
return self.cache:ensure('get_insert_text', entry._get_insert_text, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@package
|
||||||
|
entry._get_insert_text = function(self)
|
||||||
|
local completion_item = self.completion_item
|
||||||
local word
|
local word
|
||||||
if self:get_completion_item().textEdit then
|
if completion_item.textEdit then
|
||||||
word = str.trim(self:get_completion_item().textEdit.newText)
|
word = str.trim(completion_item.textEdit.newText)
|
||||||
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||||
end
|
end
|
||||||
elseif self:get_completion_item().insertText then
|
elseif completion_item.insertText then
|
||||||
word = str.trim(self:get_completion_item().insertText)
|
word = str.trim(completion_item.insertText)
|
||||||
if self:get_completion_item().insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
if completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
|
||||||
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
word = str.remove_suffix(str.remove_suffix(word, '$0'), '${0}')
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
word = str.trim(self:get_completion_item().label)
|
word = str.trim(completion_item.label)
|
||||||
end
|
end
|
||||||
return word
|
return word
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return the item is deprecated or not.
|
---Return the item is deprecated or not.
|
||||||
---@return boolean
|
---@return boolean
|
||||||
entry.is_deprecated = function(self)
|
entry.is_deprecated = function(self)
|
||||||
return self:get_completion_item().deprecated or vim.tbl_contains(self:get_completion_item().tags or {}, types.lsp.CompletionItemTag.Deprecated)
|
return self.completion_item.deprecated or vim.tbl_contains(self.completion_item.tags or {}, types.lsp.CompletionItemTag.Deprecated)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return view information.
|
---Return view information.
|
||||||
@@ -204,7 +263,11 @@ end
|
|||||||
---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } }
|
---@return { abbr: { text: string, bytes: integer, width: integer, hl_group: string }, kind: { text: string, bytes: integer, width: integer, hl_group: string }, menu: { text: string, bytes: integer, width: integer, hl_group: string } }
|
||||||
entry.get_view = function(self, suggest_offset, entries_buf)
|
entry.get_view = function(self, suggest_offset, entries_buf)
|
||||||
local item = self:get_vim_item(suggest_offset)
|
local item = self:get_vim_item(suggest_offset)
|
||||||
return self.cache:ensure('get_view:' .. tostring(entries_buf), function()
|
return self.cache:ensure('get_view:' .. tostring(entries_buf), entry._get_view, self, item, entries_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@package
|
||||||
|
entry._get_view = function(self, item, entries_buf)
|
||||||
local view = {}
|
local view = {}
|
||||||
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
|
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
|
||||||
-- called in because it reads the values of the option 'tabstop' when
|
-- called in because it reads the values of the option 'tabstop' when
|
||||||
@@ -228,16 +291,19 @@ entry.get_view = function(self, suggest_offset, entries_buf)
|
|||||||
view.dup = item.dup
|
view.dup = item.dup
|
||||||
end)
|
end)
|
||||||
return view
|
return view
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Make vim.CompletedItem
|
---Make vim.CompletedItem
|
||||||
---@param suggest_offset integer
|
---@param suggest_offset integer
|
||||||
---@return vim.CompletedItem
|
---@return vim.CompletedItem
|
||||||
entry.get_vim_item = function(self, suggest_offset)
|
entry.get_vim_item = function(self, suggest_offset)
|
||||||
return self.cache:ensure('get_vim_item:' .. tostring(suggest_offset), function()
|
return self.cache:ensure('get_vim_item:' .. tostring(suggest_offset), entry._get_vim_item, self, suggest_offset)
|
||||||
local completion_item = self:get_completion_item()
|
end
|
||||||
local word = self:get_word()
|
|
||||||
|
---@package
|
||||||
|
entry._get_vim_item = function(self, suggest_offset)
|
||||||
|
local completion_item = self.completion_item
|
||||||
|
local word = self.word
|
||||||
local abbr = str.oneline(completion_item.label)
|
local abbr = str.oneline(completion_item.label)
|
||||||
|
|
||||||
-- ~ indicator
|
-- ~ indicator
|
||||||
@@ -255,8 +321,8 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- append delta text
|
-- append delta text
|
||||||
if suggest_offset < self:get_offset() then
|
if suggest_offset < self.offset then
|
||||||
word = string.sub(self.context.cursor_before_line, suggest_offset, self:get_offset() - 1) .. word
|
word = string.sub(self.context.cursor_before_line, suggest_offset, self.offset - 1) .. word
|
||||||
end
|
end
|
||||||
|
|
||||||
-- labelDetails.
|
-- labelDetails.
|
||||||
@@ -272,7 +338,7 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- remove duplicated string.
|
-- remove duplicated string.
|
||||||
if self:get_offset() ~= self.context.cursor.col then
|
if self.offset ~= self.context.cursor.col then
|
||||||
for i = 1, #word do
|
for i = 1, #word do
|
||||||
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
if str.has_prefix(self.context.cursor_after_line, string.sub(word, i, #word)) then
|
||||||
word = string.sub(word, 1, i - 1)
|
word = string.sub(word, 1, i - 1)
|
||||||
@@ -281,7 +347,7 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmp_opts = self:get_completion_item().cmp or {}
|
local cmp_opts = completion_item.cmp or {}
|
||||||
|
|
||||||
local vim_item = {
|
local vim_item = {
|
||||||
word = word,
|
word = word,
|
||||||
@@ -289,7 +355,7 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
kind = cmp_opts.kind_text or types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
kind = cmp_opts.kind_text or types.lsp.CompletionItemKind[self:get_kind()] or types.lsp.CompletionItemKind[1],
|
||||||
kind_hl_group = cmp_opts.kind_hl_group,
|
kind_hl_group = cmp_opts.kind_hl_group,
|
||||||
menu = menu,
|
menu = menu,
|
||||||
dup = self:get_completion_item().dup or 1,
|
dup = completion_item.dup or 1,
|
||||||
}
|
}
|
||||||
if config.get().formatting.format then
|
if config.get().formatting.format then
|
||||||
vim_item = config.get().formatting.format(self, vim_item)
|
vim_item = config.get().formatting.format(self, vim_item)
|
||||||
@@ -302,63 +368,22 @@ entry.get_vim_item = function(self, suggest_offset)
|
|||||||
vim_item.empty = 1
|
vim_item.empty = 1
|
||||||
|
|
||||||
return vim_item
|
return vim_item
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get commit characters
|
---Get commit characters
|
||||||
---@return string[]
|
---@return string[]
|
||||||
entry.get_commit_characters = function(self)
|
entry.get_commit_characters = function(self)
|
||||||
return self:get_completion_item().commitCharacters or {}
|
return self.completion_item.commitCharacters or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return insert range
|
---@deprecated use entry.insert_range instead
|
||||||
---@return lsp.Range|nil
|
|
||||||
entry.get_insert_range = function(self)
|
entry.get_insert_range = function(self)
|
||||||
local insert_range
|
return self.insert_range
|
||||||
if self:get_completion_item().textEdit then
|
|
||||||
if self:get_completion_item().textEdit.insert then
|
|
||||||
insert_range = self:get_completion_item().textEdit.insert
|
|
||||||
else
|
|
||||||
insert_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
|
|
||||||
end
|
|
||||||
insert_range = self:convert_range_encoding(insert_range)
|
|
||||||
else
|
|
||||||
insert_range = {
|
|
||||||
start = {
|
|
||||||
line = self.context.cursor.row - 1,
|
|
||||||
character = self:get_offset() - 1,
|
|
||||||
},
|
|
||||||
['end'] = self.source_insert_range['end'],
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return insert_range
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return replace range
|
---@deprecated use entry.replace_range instead
|
||||||
---@return lsp.Range|nil
|
|
||||||
entry.get_replace_range = function(self)
|
entry.get_replace_range = function(self)
|
||||||
return self.cache:ensure('get_replace_range', function()
|
return self.replace_range
|
||||||
local replace_range
|
|
||||||
if self:get_completion_item().textEdit then
|
|
||||||
if self:get_completion_item().textEdit.replace then
|
|
||||||
replace_range = self:get_completion_item().textEdit.replace
|
|
||||||
else
|
|
||||||
replace_range = self:get_completion_item().textEdit.range --[[@as lsp.Range]]
|
|
||||||
end
|
|
||||||
replace_range = self:convert_range_encoding(replace_range)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not replace_range or ((self.context.cursor.col - 1) == replace_range['end'].character) then
|
|
||||||
replace_range = {
|
|
||||||
start = {
|
|
||||||
line = self.source_replace_range.start.line,
|
|
||||||
character = self:get_offset() - 1,
|
|
||||||
},
|
|
||||||
['end'] = self.source_replace_range['end'],
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return replace_range
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Match line.
|
---Match line.
|
||||||
@@ -366,7 +391,26 @@ end
|
|||||||
---@param matching_config cmp.MatchingConfig
|
---@param matching_config cmp.MatchingConfig
|
||||||
---@return { score: integer, matches: table[] }
|
---@return { score: integer, matches: table[] }
|
||||||
entry.match = function(self, input, matching_config)
|
entry.match = function(self, input, matching_config)
|
||||||
return self.match_cache:ensure(input .. ':' .. (self.resolved_completion_item and '1' or '0' .. ':') .. (matching_config.disallow_fuzzy_matching and '1' or '0') .. ':' .. (matching_config.disallow_partial_fuzzy_matching and '1' or '0') .. ':' .. (matching_config.disallow_partial_matching and '1' or '0') .. ':' .. (matching_config.disallow_prefix_unmatching and '1' or '0') .. ':' .. (matching_config.disallow_symbol_nonprefix_matching and '1' or '0'), function()
|
-- https://www.lua.org/pil/11.6.html
|
||||||
|
-- do not use '..' to allocate multiple strings
|
||||||
|
local cache_key = string.format('%s:%d:%d:%d:%d:%d:%d', input, self.resolved_completion_item and 1 or 0, matching_config.disallow_fuzzy_matching and 1 or 0, matching_config.disallow_partial_matching and 1 or 0, matching_config.disallow_prefix_unmatching and 1 or 0, matching_config.disallow_partial_fuzzy_matching and 1 or 0, matching_config.disallow_symbol_nonprefix_matching and 1 or 0)
|
||||||
|
local matched = self.match_cache:get(cache_key)
|
||||||
|
if matched then
|
||||||
|
if self.match_view_args_ret and self.match_view_args_ret.input ~= input then
|
||||||
|
self.match_view_args_ret.input = input
|
||||||
|
self.match_view_args_ret.word = matched._word
|
||||||
|
self.match_view_args_ret.matches = matched.matches
|
||||||
|
end
|
||||||
|
return matched
|
||||||
|
end
|
||||||
|
matched = self:_match(input, matching_config)
|
||||||
|
self.match_cache:set(cache_key, matched)
|
||||||
|
return matched
|
||||||
|
end
|
||||||
|
|
||||||
|
---@package
|
||||||
|
entry._match = function(self, input, matching_config)
|
||||||
|
local completion_item = self.completion_item
|
||||||
local option = {
|
local option = {
|
||||||
disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching,
|
disallow_fuzzy_matching = matching_config.disallow_fuzzy_matching,
|
||||||
disallow_partial_fuzzy_matching = matching_config.disallow_partial_fuzzy_matching,
|
disallow_partial_fuzzy_matching = matching_config.disallow_partial_fuzzy_matching,
|
||||||
@@ -374,29 +418,29 @@ entry.match = function(self, input, matching_config)
|
|||||||
disallow_prefix_unmatching = matching_config.disallow_prefix_unmatching,
|
disallow_prefix_unmatching = matching_config.disallow_prefix_unmatching,
|
||||||
disallow_symbol_nonprefix_matching = matching_config.disallow_symbol_nonprefix_matching,
|
disallow_symbol_nonprefix_matching = matching_config.disallow_symbol_nonprefix_matching,
|
||||||
synonyms = {
|
synonyms = {
|
||||||
self:get_word(),
|
self.word,
|
||||||
self:get_completion_item().label,
|
self.completion_item.label,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local score, matches, filter_text, _
|
local score, matches, filter_text
|
||||||
local checked = {} ---@type table<string, boolean>
|
local checked = {} ---@type table<string, boolean>
|
||||||
|
|
||||||
filter_text = self:get_filter_text()
|
filter_text = self.filter_text
|
||||||
checked[filter_text] = true
|
checked[filter_text] = true
|
||||||
score, matches = matcher.match(input, filter_text, option)
|
score, matches = matcher.match(input, filter_text, option)
|
||||||
|
|
||||||
-- Support the language server that doesn't respect VSCode's behaviors.
|
-- Support the language server that doesn't respect VSCode's behaviors.
|
||||||
if score == 0 then
|
if score == 0 then
|
||||||
if self:get_completion_item().textEdit and not misc.empty(self:get_completion_item().textEdit.newText) then
|
if completion_item.textEdit and not misc.empty(completion_item.textEdit.newText) then
|
||||||
local diff = self.source_offset - self:get_offset()
|
local diff = self.source_offset - self.offset
|
||||||
if diff > 0 then
|
if diff > 0 then
|
||||||
local prefix = string.sub(self.context.cursor_line, self:get_offset(), self:get_offset() + diff)
|
local prefix = string.sub(self.context.cursor_line, self.offset, self.offset + diff)
|
||||||
local accept = nil
|
local accept = nil
|
||||||
accept = accept or string.match(prefix, '^[^%a]+$')
|
accept = accept or string.match(prefix, '^[^%a]+$')
|
||||||
accept = accept or string.find(self:get_completion_item().textEdit.newText, prefix, 1, true)
|
accept = accept or string.find(completion_item.textEdit.newText, prefix, 1, true)
|
||||||
if accept then
|
if accept then
|
||||||
filter_text = prefix .. self:get_filter_text()
|
filter_text = prefix .. filter_text
|
||||||
if not checked[filter_text] then
|
if not checked[filter_text] then
|
||||||
checked[filter_text] = true
|
checked[filter_text] = true
|
||||||
score, matches = matcher.match(input, filter_text, option)
|
score, matches = matcher.match(input, filter_text, option)
|
||||||
@@ -408,40 +452,44 @@ entry.match = function(self, input, matching_config)
|
|||||||
|
|
||||||
-- Fix highlight if filterText is not the same to vim_item.abbr.
|
-- Fix highlight if filterText is not the same to vim_item.abbr.
|
||||||
if score > 0 then
|
if score > 0 then
|
||||||
local vim_item = self:get_vim_item(self.source_offset)
|
self.match_view_args_ret = {
|
||||||
filter_text = vim_item.abbr or vim_item.word
|
input = input,
|
||||||
if not checked[filter_text] then
|
word = filter_text,
|
||||||
local diff = self.source_offset - self:get_offset()
|
option = option,
|
||||||
_, matches = matcher.match(input:sub(1 + diff), filter_text, option)
|
matches = matches,
|
||||||
end
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return { score = score, matches = matches }
|
return { score = score, matches = matches, _word = filter_text }
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get resolved completion item if possible.
|
---@param view string
|
||||||
---@return lsp.CompletionItem
|
entry.get_view_matches = function(self, view)
|
||||||
|
if self.match_view_args_ret then
|
||||||
|
if self.match_view_args_ret.word == view then
|
||||||
|
return self.match_view_args_ret.matches
|
||||||
|
end
|
||||||
|
self.match_view_args_ret.word = view
|
||||||
|
local input = self.match_view_args_ret.input
|
||||||
|
local diff = self.source_offset - self.offset
|
||||||
|
if diff > 0 then
|
||||||
|
input = input:sub(1 + diff)
|
||||||
|
end
|
||||||
|
local _, matches = matcher.match(input, view, self.match_view_args_ret.option)
|
||||||
|
self.match_view_args_ret.matches = matches
|
||||||
|
return matches
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@deprecated use entry.completion_item instead
|
||||||
entry.get_completion_item = function(self)
|
entry.get_completion_item = function(self)
|
||||||
return self.cache:ensure('get_completion_item', function()
|
|
||||||
if self.resolved_completion_item then
|
|
||||||
-- @see https://github.com/microsoft/vscode/blob/85eea4a9b2ccc99615e970bf2181edbc1781d0f9/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts#L588
|
|
||||||
-- @see https://github.com/microsoft/vscode/blob/85eea4a9b2ccc99615e970bf2181edbc1781d0f9/src/vs/base/common/objects.ts#L89
|
|
||||||
-- @see https://github.com/microsoft/vscode/blob/a00f2e64f4fa9a1f774875562e1e9697d7138ed3/src/vs/editor/contrib/suggest/browser/suggest.ts#L147
|
|
||||||
local completion_item = misc.copy(self.completion_item)
|
|
||||||
for k, v in pairs(self.resolved_completion_item) do
|
|
||||||
completion_item[k] = v or completion_item[k]
|
|
||||||
end
|
|
||||||
return completion_item
|
|
||||||
end
|
|
||||||
return self.completion_item
|
return self.completion_item
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Create documentation
|
---Create documentation
|
||||||
---@return string[]
|
---@return string[]
|
||||||
entry.get_documentation = function(self)
|
entry.get_documentation = function(self)
|
||||||
local item = self:get_completion_item()
|
local item = self.completion_item
|
||||||
|
|
||||||
local documents = {}
|
local documents = {}
|
||||||
|
|
||||||
@@ -483,13 +531,13 @@ end
|
|||||||
---Get completion item kind
|
---Get completion item kind
|
||||||
---@return lsp.CompletionItemKind
|
---@return lsp.CompletionItemKind
|
||||||
entry.get_kind = function(self)
|
entry.get_kind = function(self)
|
||||||
return self:get_completion_item().kind or types.lsp.CompletionItemKind.Text
|
return self.completion_item.kind or types.lsp.CompletionItemKind.Text
|
||||||
end
|
end
|
||||||
|
|
||||||
---Execute completion item's command.
|
---Execute completion item's command.
|
||||||
---@param callback fun()
|
---@param callback fun()
|
||||||
entry.execute = function(self, callback)
|
entry.execute = function(self, callback)
|
||||||
self.source:execute(self:get_completion_item(), callback)
|
self.source:execute(self.completion_item, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Resolve completion item.
|
---Resolve completion item.
|
||||||
@@ -507,7 +555,8 @@ entry.resolve = function(self, callback)
|
|||||||
if not completion_item then
|
if not completion_item then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.resolved_completion_item = self:fill_defaults(completion_item, self.item_defaults)
|
self:_set_completion_item(completion_item)
|
||||||
|
self.resolved_completion_item = self.completion_item
|
||||||
self.cache:clear()
|
self.cache:clear()
|
||||||
for _, c in ipairs(self.resolved_callbacks) do
|
for _, c in ipairs(self.resolved_callbacks) do
|
||||||
c()
|
c()
|
||||||
@@ -560,13 +609,18 @@ end
|
|||||||
|
|
||||||
---Convert the oneline range encoding.
|
---Convert the oneline range encoding.
|
||||||
entry.convert_range_encoding = function(self, range)
|
entry.convert_range_encoding = function(self, range)
|
||||||
local from_encoding = self.source:get_position_encoding_kind()
|
local from_encoding = self.source.position_encoding
|
||||||
return self.context.cache:ensure('entry.convert_range_encoding:' .. range.start.character .. ':' .. range['end'].character .. ':' .. from_encoding, function()
|
local cache_key = string.format('entry.convert_range_encoding:%d:%d:%s', range.start.character, range['end'].character, from_encoding)
|
||||||
return {
|
local res = self.context.cache:get(cache_key)
|
||||||
|
if res then
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
res = {
|
||||||
start = types.lsp.Position.to_utf8(self.context.cursor_line, range.start, from_encoding),
|
start = types.lsp.Position.to_utf8(self.context.cursor_line, range.start, from_encoding),
|
||||||
['end'] = types.lsp.Position.to_utf8(self.context.cursor_line, range['end'], from_encoding),
|
['end'] = types.lsp.Position.to_utf8(self.context.cursor_line, range['end'], from_encoding),
|
||||||
}
|
}
|
||||||
end)
|
self.context.cache:set(cache_key, res)
|
||||||
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return true if the entry is invalid.
|
---Return true if the entry is invalid.
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ describe('entry', function()
|
|||||||
local e = entry.new(state.manual(), state.source(), {
|
local e = entry.new(state.manual(), state.source(), {
|
||||||
label = '@',
|
label = '@',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 3)
|
assert.are.equal(e.offset, 3)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '@')
|
assert.are.equal(e:get_vim_item(e.offset).word, '@')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('word length (no fix)', function()
|
it('word length (no fix)', function()
|
||||||
@@ -21,8 +21,8 @@ describe('entry', function()
|
|||||||
local e = entry.new(state.manual(), state.source(), {
|
local e = entry.new(state.manual(), state.source(), {
|
||||||
label = 'b',
|
label = 'b',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 5)
|
assert.are.equal(e.offset, 5)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'b')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'b')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('word length (fix)', function()
|
it('word length (fix)', function()
|
||||||
@@ -31,8 +31,8 @@ describe('entry', function()
|
|||||||
local e = entry.new(state.manual(), state.source(), {
|
local e = entry.new(state.manual(), state.source(), {
|
||||||
label = 'b.',
|
label = 'b.',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 3)
|
assert.are.equal(e.offset, 3)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'b.')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'b.')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('semantic index (no fix)', function()
|
it('semantic index (no fix)', function()
|
||||||
@@ -41,8 +41,8 @@ describe('entry', function()
|
|||||||
local e = entry.new(state.manual(), state.source(), {
|
local e = entry.new(state.manual(), state.source(), {
|
||||||
label = 'c.',
|
label = 'c.',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 6)
|
assert.are.equal(e.offset, 6)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'c.')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'c.')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('semantic index (fix)', function()
|
it('semantic index (fix)', function()
|
||||||
@@ -51,8 +51,8 @@ describe('entry', function()
|
|||||||
local e = entry.new(state.manual(), state.source(), {
|
local e = entry.new(state.manual(), state.source(), {
|
||||||
label = 'bc.',
|
label = 'bc.',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 3)
|
assert.are.equal(e.offset, 3)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'bc.')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'bc.')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[vscode-html-language-server] 1', function()
|
it('[vscode-html-language-server] 1', function()
|
||||||
@@ -74,8 +74,8 @@ describe('entry', function()
|
|||||||
newText = ' </div',
|
newText = ' </div',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 5)
|
assert.are.equal(e.offset, 5)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '</div')
|
assert.are.equal(e:get_vim_item(e.offset).word, '</div')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[clangd] 1', function()
|
it('[clangd] 1', function()
|
||||||
@@ -101,7 +101,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(4).word, '->foo')
|
assert.are.equal(e:get_vim_item(4).word, '->foo')
|
||||||
assert.are.equal(e:get_filter_text(), 'foo')
|
assert.are.equal(e.filter_text, 'foo')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[typescript-language-server] 1', function()
|
it('[typescript-language-server] 1', function()
|
||||||
@@ -112,7 +112,7 @@ describe('entry', function()
|
|||||||
})
|
})
|
||||||
-- The offset will be 18 in this situation because the server returns `[Symbol]` as candidate.
|
-- The offset will be 18 in this situation because the server returns `[Symbol]` as candidate.
|
||||||
assert.are.equal(e:get_vim_item(18).word, '.catch')
|
assert.are.equal(e:get_vim_item(18).word, '.catch')
|
||||||
assert.are.equal(e:get_filter_text(), 'catch')
|
assert.are.equal(e.filter_text, 'catch')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[typescript-language-server] 2', function()
|
it('[typescript-language-server] 2', function()
|
||||||
@@ -136,7 +136,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(18).word, '[Symbol]')
|
assert.are.equal(e:get_vim_item(18).word, '[Symbol]')
|
||||||
assert.are.equal(e:get_filter_text(), '.Symbol')
|
assert.are.equal(e.filter_text, '.Symbol')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[lua-language-server] 1', function()
|
it('[lua-language-server] 1', function()
|
||||||
@@ -163,7 +163,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
||||||
assert.are.equal(e:get_filter_text(), 'cmp.config')
|
assert.are.equal(e.filter_text, 'cmp.config')
|
||||||
|
|
||||||
-- press '
|
-- press '
|
||||||
state.input("'")
|
state.input("'")
|
||||||
@@ -185,7 +185,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
assert.are.equal(e:get_vim_item(19).word, 'cmp.config')
|
||||||
assert.are.equal(e:get_filter_text(), 'cmp.config')
|
assert.are.equal(e.filter_text, 'cmp.config')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[lua-language-server] 2', function()
|
it('[lua-language-server] 2', function()
|
||||||
@@ -212,7 +212,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
||||||
assert.are.equal(e:get_filter_text(), 'lua.cmp.config')
|
assert.are.equal(e.filter_text, 'lua.cmp.config')
|
||||||
|
|
||||||
-- press '
|
-- press '
|
||||||
state.input("'")
|
state.input("'")
|
||||||
@@ -234,7 +234,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
assert.are.equal(e:get_vim_item(19).word, 'lua.cmp.config')
|
||||||
assert.are.equal(e:get_filter_text(), 'lua.cmp.config')
|
assert.are.equal(e.filter_text, 'lua.cmp.config')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[intelephense] 1', function()
|
it('[intelephense] 1', function()
|
||||||
@@ -260,8 +260,8 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '$this')
|
assert.are.equal(e:get_vim_item(e.offset).word, '$this')
|
||||||
assert.are.equal(e:get_filter_text(), '$this')
|
assert.are.equal(e.filter_text, '$this')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[odin-language-server] 1', function()
|
it('[odin-language-server] 1', function()
|
||||||
@@ -285,7 +285,7 @@ describe('entry', function()
|
|||||||
label = 'string',
|
label = 'string',
|
||||||
tags = {},
|
tags = {},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'string')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'string')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('[#47] word should not contain \\n character', function()
|
it('[#47] word should not contain \\n character', function()
|
||||||
@@ -299,8 +299,8 @@ describe('entry', function()
|
|||||||
insertTextFormat = 1,
|
insertTextFormat = 1,
|
||||||
insertText = '__init__(self) -> None:\n pass',
|
insertText = '__init__(self) -> None:\n pass',
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, '__init__(self) -> None:')
|
assert.are.equal(e:get_vim_item(e.offset).word, '__init__(self) -> None:')
|
||||||
assert.are.equal(e:get_filter_text(), '__init__')
|
assert.are.equal(e.filter_text, '__init__')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- I can't understand this test case...
|
-- I can't understand this test case...
|
||||||
@@ -360,7 +360,7 @@ describe('entry', function()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.are.equal(e:get_offset(), 12)
|
assert.are.equal(e.offset, 12)
|
||||||
assert.are.equal(e:get_vim_item(e:get_offset()).word, 'getPath()')
|
assert.are.equal(e:get_vim_item(e.offset).word, 'getPath()')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ local char = require('cmp.utils.char')
|
|||||||
---@field public completion_context lsp.CompletionContext|nil
|
---@field public completion_context lsp.CompletionContext|nil
|
||||||
---@field public status cmp.SourceStatus
|
---@field public status cmp.SourceStatus
|
||||||
---@field public complete_dedup function
|
---@field public complete_dedup function
|
||||||
|
---@field public default_replace_range lsp.Range
|
||||||
|
---@field public default_insert_range lsp.Range
|
||||||
|
---@field public position_encoding lsp.PositionEncodingKind
|
||||||
local source = {}
|
local source = {}
|
||||||
|
|
||||||
---@alias cmp.SourceStatus 1 | 2 | 3
|
---@alias cmp.SourceStatus 1 | 2 | 3
|
||||||
@@ -41,6 +44,7 @@ source.new = function(name, s)
|
|||||||
self.cache = cache.new()
|
self.cache = cache.new()
|
||||||
self.complete_dedup = async.dedup()
|
self.complete_dedup = async.dedup()
|
||||||
self.revision = 0
|
self.revision = 0
|
||||||
|
self.position_encoding = self:get_position_encoding_kind()
|
||||||
self:reset()
|
self:reset()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -108,7 +112,7 @@ source.get_entries = function(self, ctx)
|
|||||||
local entries = {}
|
local entries = {}
|
||||||
local matching_config = self:get_matching_config()
|
local matching_config = self:get_matching_config()
|
||||||
for _, e in ipairs(target_entries) do
|
for _, e in ipairs(target_entries) do
|
||||||
local o = e:get_offset()
|
local o = e.offset
|
||||||
if not inputs[o] then
|
if not inputs[o] then
|
||||||
inputs[o] = string.sub(ctx.cursor_before_line, o)
|
inputs[o] = string.sub(ctx.cursor_before_line, o)
|
||||||
end
|
end
|
||||||
@@ -118,7 +122,7 @@ source.get_entries = function(self, ctx)
|
|||||||
e.exact = false
|
e.exact = false
|
||||||
if e.score >= 1 then
|
if e.score >= 1 then
|
||||||
e.matches = match.matches
|
e.matches = match.matches
|
||||||
e.exact = e:get_filter_text() == inputs[o] or e:get_word() == inputs[o]
|
e.exact = e.filter_text == inputs[o] or e.word == inputs[o]
|
||||||
|
|
||||||
if entry_filter(e, ctx) then
|
if entry_filter(e, ctx) then
|
||||||
entries[#entries + 1] = e
|
entries[#entries + 1] = e
|
||||||
@@ -138,13 +142,9 @@ source.get_entries = function(self, ctx)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Get default insert range (UTF8 byte index).
|
---Get default insert range (UTF8 byte index).
|
||||||
|
---@package
|
||||||
---@return lsp.Range
|
---@return lsp.Range
|
||||||
source.get_default_insert_range = function(self)
|
source._get_default_insert_range = function(self)
|
||||||
if not self.context then
|
|
||||||
error('context is not initialized yet.')
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.cache:ensure({ 'get_default_insert_range', tostring(self.revision) }, function()
|
|
||||||
return {
|
return {
|
||||||
start = {
|
start = {
|
||||||
line = self.context.cursor.row - 1,
|
line = self.context.cursor.row - 1,
|
||||||
@@ -155,17 +155,12 @@ source.get_default_insert_range = function(self)
|
|||||||
character = self.context.cursor.col - 1,
|
character = self.context.cursor.col - 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get default replace range (UTF8 byte index).
|
---Get default replace range (UTF8 byte index).
|
||||||
|
---@package
|
||||||
---@return lsp.Range
|
---@return lsp.Range
|
||||||
source.get_default_replace_range = function(self)
|
source._get_default_replace_range = function(self)
|
||||||
if not self.context then
|
|
||||||
error('context is not initialized yet.')
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.cache:ensure({ 'get_default_replace_range', tostring(self.revision) }, function()
|
|
||||||
local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset))
|
local _, e = pattern.offset('^' .. '\\%(' .. self:get_keyword_pattern() .. '\\)', string.sub(self.context.cursor_line, self.offset))
|
||||||
return {
|
return {
|
||||||
start = {
|
start = {
|
||||||
@@ -177,7 +172,16 @@ source.get_default_replace_range = function(self)
|
|||||||
character = (e and self.offset + e - 2 or self.context.cursor.col - 1),
|
character = (e and self.offset + e - 2 or self.context.cursor.col - 1),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
---@deprecated use source.default_insert_range instead
|
||||||
|
source.get_default_insert_range = function(self)
|
||||||
|
return self.default_insert_range
|
||||||
|
end
|
||||||
|
|
||||||
|
---@deprecated use source.default_replace_range instead
|
||||||
|
source.get_default_replae_range = function(self)
|
||||||
|
return self.default_replace_range
|
||||||
end
|
end
|
||||||
|
|
||||||
---Return source name.
|
---Return source name.
|
||||||
@@ -322,6 +326,9 @@ source.complete = function(self, ctx, callback)
|
|||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.request_offset = offset
|
self.request_offset = offset
|
||||||
self.context = ctx
|
self.context = ctx
|
||||||
|
self.default_replace_range = self:_get_default_replace_range()
|
||||||
|
self.default_insert_range = self:_get_default_insert_range()
|
||||||
|
self.position_encoding = self:get_position_encoding_kind()
|
||||||
self.completion_context = completion_context
|
self.completion_context = completion_context
|
||||||
self.source:complete(
|
self.source:complete(
|
||||||
vim.tbl_extend('keep', misc.copy(self:get_source_config()), {
|
vim.tbl_extend('keep', misc.copy(self:get_source_config()), {
|
||||||
@@ -346,11 +353,11 @@ source.complete = function(self, ctx, callback)
|
|||||||
self.status = source.SourceStatus.COMPLETED
|
self.status = source.SourceStatus.COMPLETED
|
||||||
self.entries = {}
|
self.entries = {}
|
||||||
for _, item in ipairs(response.items or response) do
|
for _, item in ipairs(response.items or response) do
|
||||||
if (item or {}).label then
|
if item.label then
|
||||||
local e = entry.new(ctx, self, item, response.itemDefaults)
|
local e = entry.new(ctx, self, item, response.itemDefaults)
|
||||||
if not e:is_invalid() then
|
if not e:is_invalid() then
|
||||||
table.insert(self.entries, e)
|
table.insert(self.entries, e)
|
||||||
self.offset = math.min(self.offset, e:get_offset())
|
self.offset = math.min(self.offset, e.offset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,9 +65,8 @@ lsp.Position = {
|
|||||||
return position
|
return position
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, byteindex = pcall(function()
|
local ok, byteindex = pcall(vim.str_byteindex,
|
||||||
return vim.str_byteindex(text, position.character, from_encoding == lsp.PositionEncodingKind.UTF16)
|
text, position.character, from_encoding == lsp.PositionEncodingKind.UTF16)
|
||||||
end)
|
|
||||||
if not ok then
|
if not ok then
|
||||||
return position
|
return position
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ end
|
|||||||
---Ensure value by callback
|
---Ensure value by callback
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param key string|string[]
|
---@param key string|string[]
|
||||||
---@param callback fun(): T
|
---@param callback fun(...): T
|
||||||
---@return T
|
---@return T
|
||||||
cache.ensure = function(self, key, callback)
|
cache.ensure = function(self, key, callback, ...)
|
||||||
local value = self:get(key)
|
local value = self:get(key)
|
||||||
if value == nil then
|
if value == nil then
|
||||||
local v = callback()
|
local v = callback(...)
|
||||||
self:set(key, v)
|
self:set(key, v)
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ view.open = function(self, ctx, sources)
|
|||||||
for _, e in ipairs(s:get_entries(ctx)) do
|
for _, e in ipairs(s:get_entries(ctx)) do
|
||||||
e.score = e.score + priority
|
e.score = e.score + priority
|
||||||
table.insert(group_entries, e)
|
table.insert(group_entries, e)
|
||||||
offset = math.min(offset, e:get_offset())
|
offset = math.min(offset, e.offset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ custom_entries_view.new = function()
|
|||||||
o = o + v[field].bytes + (self.column_width[field] - v[field].width) + 1
|
o = o + v[field].bytes + (self.column_width[field] - v[field].width) + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, m in ipairs(e.matches or {}) do
|
for _, m in ipairs(e:get_view_matches(v.abbr.text) 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, custom_entries_view.ns, i, a + m.word_match_start - 1, {
|
||||||
end_line = i,
|
end_line = i,
|
||||||
end_col = a + m.word_match_end,
|
end_col = a + m.word_match_end,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ ghost_text_view.text_gen = function(self, line, cursor_col)
|
|||||||
word = tostring(snippet.parse(word))
|
word = tostring(snippet.parse(word))
|
||||||
end
|
end
|
||||||
local word_clen = vim.str_utfindex(word)
|
local word_clen = vim.str_utfindex(word)
|
||||||
local cword = string.sub(line, self.entry:get_offset(), cursor_col)
|
local cword = string.sub(line, self.entry.offset, cursor_col)
|
||||||
local cword_clen = vim.str_utfindex(cword)
|
local cword_clen = vim.str_utfindex(cword)
|
||||||
-- Number of characters from entry text (word) to be displayed as ghost thext
|
-- Number of characters from entry text (word) to be displayed as ghost thext
|
||||||
local nchars = word_clen - cword_clen
|
local nchars = word_clen - cword_clen
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ wildmenu_entries_view.new = function()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, m in ipairs(e.matches or {}) do
|
for _, m in ipairs(e:get_view_matches(view.abbr.text) or {}) do
|
||||||
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, self.offsets[i] + m.word_match_start - 1, {
|
vim.api.nvim_buf_set_extmark(buf, wildmenu_entries_view.ns, 0, self.offsets[i] + m.word_match_start - 1, {
|
||||||
end_line = 0,
|
end_line = 0,
|
||||||
end_col = self.offsets[i] + m.word_match_end,
|
end_col = self.offsets[i] + m.word_match_end,
|
||||||
|
|||||||
Reference in New Issue
Block a user