2022-01-01 22:13:13 +08:00
|
|
|
local types = require('cmp.types')
|
2022-04-13 10:40:59 +08:00
|
|
|
local cache = require('cmp.utils.cache')
|
2022-01-01 22:13:13 +08:00
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---@type cmp.Comparator[]
|
2022-01-01 22:13:13 +08:00
|
|
|
local compare = {}
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
--- Comparators (:help cmp-config.sorting.comparators) should return
|
|
|
|
--- true when the first entry should come EARLIER (i.e., higher ranking) than the second entry,
|
|
|
|
--- or nil if no pairwise ordering preference from the comparator.
|
|
|
|
--- See also :help table.sort() and cmp.view.open() to see how comparators are used.
|
|
|
|
|
|
|
|
---@class cmp.ComparatorFunctor
|
|
|
|
---@overload fun(entry1: cmp.Entry, entry2: cmp.Entry): boolean | nil
|
|
|
|
---@alias cmp.ComparatorFunction fun(entry1: cmp.Entry, entry2: cmp.Entry): boolean | nil
|
|
|
|
---@alias cmp.Comparator cmp.ComparatorFunction | cmp.ComparatorFunctor
|
|
|
|
|
|
|
|
---offset: Entries with smaller offset will be ranked higher.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.offset = function(entry1, entry2)
|
|
|
|
local diff = entry1:get_offset() - entry2:get_offset()
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---exact: Entries with exact == true will be ranked higher.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.exact = function(entry1, entry2)
|
|
|
|
if entry1.exact ~= entry2.exact then
|
|
|
|
return entry1.exact
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---score: Entries with higher score will be ranked higher.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.score = function(entry1, entry2)
|
|
|
|
local diff = entry2.score - entry1.score
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---recently_used: Entries that are used recently will be ranked higher.
|
|
|
|
---@type cmp.ComparatorFunctor
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.recently_used = setmetatable({
|
|
|
|
records = {},
|
|
|
|
add_entry = function(self, e)
|
|
|
|
self.records[e.completion_item.label] = vim.loop.now()
|
|
|
|
end,
|
|
|
|
}, {
|
2024-03-21 19:32:46 +08:00
|
|
|
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
2022-01-01 22:13:13 +08:00
|
|
|
__call = function(self, entry1, entry2)
|
|
|
|
local t1 = self.records[entry1.completion_item.label] or -1
|
|
|
|
local t2 = self.records[entry2.completion_item.label] or -1
|
|
|
|
if t1 ~= t2 then
|
|
|
|
return t1 > t2
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---kind: Entires with smaller ordinal value of 'kind' will be ranked higher.
|
|
|
|
---(see lsp.CompletionItemKind enum).
|
|
|
|
---Exceptions are that Text(1) will be ranked the lowest, and snippets be the highest.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.kind = function(entry1, entry2)
|
2024-03-21 19:32:46 +08:00
|
|
|
local kind1 = entry1:get_kind() --- @type lsp.CompletionItemKind | number
|
|
|
|
local kind2 = entry2:get_kind() --- @type lsp.CompletionItemKind | number
|
2022-01-01 22:13:13 +08:00
|
|
|
kind1 = kind1 == types.lsp.CompletionItemKind.Text and 100 or kind1
|
|
|
|
kind2 = kind2 == types.lsp.CompletionItemKind.Text and 100 or kind2
|
|
|
|
if kind1 ~= kind2 then
|
|
|
|
if kind1 == types.lsp.CompletionItemKind.Snippet then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
if kind2 == types.lsp.CompletionItemKind.Snippet then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local diff = kind1 - kind2
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---sort_text: Entries will be ranked according to the lexicographical order of sortText.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.sort_text = function(entry1, entry2)
|
2023-06-08 21:15:37 +08:00
|
|
|
if entry1.completion_item.sortText and entry2.completion_item.sortText then
|
2022-01-01 22:13:13 +08:00
|
|
|
local diff = vim.stricmp(entry1.completion_item.sortText, entry2.completion_item.sortText)
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---length: Entires with shorter label length will be ranked higher.
|
|
|
|
---@type cmp.ComparatorFunction
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.length = function(entry1, entry2)
|
|
|
|
local diff = #entry1.completion_item.label - #entry2.completion_item.label
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
----order: Entries with smaller id will be ranked higher.
|
|
|
|
---@type fun(entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
2022-01-01 22:13:13 +08:00
|
|
|
compare.order = function(entry1, entry2)
|
|
|
|
local diff = entry1.id - entry2.id
|
|
|
|
if diff < 0 then
|
|
|
|
return true
|
|
|
|
elseif diff > 0 then
|
|
|
|
return false
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2022-01-01 22:13:13 +08:00
|
|
|
end
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---locality: Entries with higher locality (i.e., words that are closer to the cursor)
|
|
|
|
---will be ranked higher. See GH-183 for more details.
|
|
|
|
---@type cmp.ComparatorFunctor
|
2022-04-13 10:40:59 +08:00
|
|
|
compare.locality = setmetatable({
|
|
|
|
lines_count = 10,
|
|
|
|
lines_cache = cache.new(),
|
|
|
|
locality_map = {},
|
|
|
|
update = function(self)
|
|
|
|
local config = require('cmp').get_config()
|
2023-06-08 21:15:37 +08:00
|
|
|
if not vim.tbl_contains(config.sorting.comparators, compare.locality) then
|
2022-04-13 10:40:59 +08:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local win, buf = vim.api.nvim_get_current_win(), vim.api.nvim_get_current_buf()
|
|
|
|
local cursor_row = vim.api.nvim_win_get_cursor(win)[1] - 1
|
|
|
|
local max = vim.api.nvim_buf_line_count(buf)
|
|
|
|
|
|
|
|
if self.lines_cache:get('buf') ~= buf then
|
|
|
|
self.lines_cache:clear()
|
|
|
|
self.lines_cache:set('buf', buf)
|
|
|
|
end
|
|
|
|
|
|
|
|
self.locality_map = {}
|
|
|
|
for i = math.max(0, cursor_row - self.lines_count), math.min(max, cursor_row + self.lines_count) do
|
|
|
|
local is_above = i < cursor_row
|
|
|
|
local buffer = vim.api.nvim_buf_get_lines(buf, i, i + 1, false)[1] or ''
|
|
|
|
local locality_map = self.lines_cache:ensure({ 'line', buffer }, function()
|
|
|
|
local locality_map = {}
|
|
|
|
local regexp = vim.regex(config.completion.keyword_pattern)
|
|
|
|
while buffer ~= '' do
|
|
|
|
local s, e = regexp:match_str(buffer)
|
|
|
|
if s and e then
|
|
|
|
local w = string.sub(buffer, s + 1, e)
|
2023-06-08 21:15:37 +08:00
|
|
|
local d = math.abs(i - cursor_row) - (is_above and 1 or 0)
|
2022-04-13 10:40:59 +08:00
|
|
|
locality_map[w] = math.min(locality_map[w] or math.huge, d)
|
|
|
|
buffer = string.sub(buffer, e + 1)
|
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return locality_map
|
|
|
|
end)
|
|
|
|
for w, d in pairs(locality_map) do
|
|
|
|
self.locality_map[w] = math.min(self.locality_map[w] or d, math.abs(i - cursor_row))
|
|
|
|
end
|
|
|
|
end
|
2023-06-08 21:15:37 +08:00
|
|
|
end,
|
2022-04-13 10:40:59 +08:00
|
|
|
}, {
|
2024-03-21 19:32:46 +08:00
|
|
|
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
2022-04-13 10:40:59 +08:00
|
|
|
__call = function(self, entry1, entry2)
|
|
|
|
local local1 = self.locality_map[entry1:get_word()]
|
|
|
|
local local2 = self.locality_map[entry2:get_word()]
|
|
|
|
if local1 ~= local2 then
|
|
|
|
if local1 == nil then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
if local2 == nil then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return local1 < local2
|
|
|
|
end
|
2024-03-21 19:32:46 +08:00
|
|
|
return nil
|
2023-06-08 21:15:37 +08:00
|
|
|
end,
|
2022-04-13 10:40:59 +08:00
|
|
|
})
|
|
|
|
|
2024-03-21 19:32:46 +08:00
|
|
|
---scopes: Entries defined in a closer scope will be ranked higher (e.g., prefer local variables to globals).
|
|
|
|
---@type cmp.ComparatorFunctor
|
2022-04-13 10:40:59 +08:00
|
|
|
compare.scopes = setmetatable({
|
|
|
|
scopes_map = {},
|
|
|
|
update = function(self)
|
|
|
|
local config = require('cmp').get_config()
|
|
|
|
if not vim.tbl_contains(config.sorting.comparators, compare.scopes) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local ok, locals = pcall(require, 'nvim-treesitter.locals')
|
|
|
|
if ok then
|
|
|
|
local win, buf = vim.api.nvim_get_current_win(), vim.api.nvim_get_current_buf()
|
|
|
|
local cursor_row = vim.api.nvim_win_get_cursor(win)[1] - 1
|
|
|
|
|
|
|
|
-- Cursor scope.
|
|
|
|
local cursor_scope = nil
|
|
|
|
for _, scope in ipairs(locals.get_scopes(buf)) do
|
|
|
|
if scope:start() <= cursor_row and cursor_row <= scope:end_() then
|
|
|
|
if not cursor_scope then
|
|
|
|
cursor_scope = scope
|
|
|
|
else
|
|
|
|
if cursor_scope:start() <= scope:start() and scope:end_() <= cursor_scope:end_() then
|
|
|
|
cursor_scope = scope
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif cursor_scope and cursor_scope:end_() <= scope:start() then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Definitions.
|
|
|
|
local definitions = locals.get_definitions_lookup_table(buf)
|
|
|
|
|
|
|
|
-- Narrow definitions.
|
|
|
|
local depth = 0
|
|
|
|
for scope in locals.iter_scope_tree(cursor_scope, buf) do
|
|
|
|
local s, e = scope:start(), scope:end_()
|
|
|
|
|
|
|
|
-- Check scope's direct child.
|
|
|
|
for _, definition in pairs(definitions) do
|
|
|
|
if s <= definition.node:start() and definition.node:end_() <= e then
|
|
|
|
if scope:id() == locals.containing_scope(definition.node, buf):id() then
|
2023-06-08 21:15:37 +08:00
|
|
|
local get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text
|
|
|
|
local text = get_node_text(definition.node, buf) or ''
|
2022-04-13 10:40:59 +08:00
|
|
|
if not self.scopes_map[text] then
|
|
|
|
self.scopes_map[text] = depth
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
depth = depth + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}, {
|
2024-03-21 19:32:46 +08:00
|
|
|
---@type fun(self: table, entry1: cmp.Entry, entry2: cmp.Entry): boolean|nil
|
2022-04-13 10:40:59 +08:00
|
|
|
__call = function(self, entry1, entry2)
|
|
|
|
local local1 = self.scopes_map[entry1:get_word()]
|
|
|
|
local local2 = self.scopes_map[entry2:get_word()]
|
|
|
|
if local1 ~= local2 then
|
|
|
|
if local1 == nil then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
if local2 == nil then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return local1 < local2
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2022-01-01 22:13:13 +08:00
|
|
|
return compare
|