diff --git a/lua/blink/cmp/config/fuzzy.lua b/lua/blink/cmp/config/fuzzy.lua index bf19fa62..3f769550 100644 --- a/lua/blink/cmp/config/fuzzy.lua +++ b/lua/blink/cmp/config/fuzzy.lua @@ -2,7 +2,7 @@ --- @field use_typo_resistance boolean When enabled, allows for a number of typos relative to the length of the query. Disabling this matches the behavior of fzf --- @field use_frecency boolean Tracks the most recently/frequently used items and boosts the score of the item --- @field use_proximity boolean Boosts the score of items matching nearby words ---- @field sorts ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] Controls which sorts to use and in which order, these three are currently the only allowed options +--- @field sort blink.cmp.FuzzySortConfig --- @field prebuilt_binaries blink.cmp.PrebuiltBinariesConfig --- @class (exact) blink.cmp.PrebuiltBinariesConfig @@ -11,16 +11,24 @@ --- @field force_system_triple? string When downloading a prebuilt binary, force the downloader to use this system triple. If this is unset then the downloader will attempt to infer the system triple from `jit.os` and `jit.arch`. Check the latest release for all available system triples. WARN: Beware that `main` may be incompatible with the version you select --- @field extra_curl_args? string[] Extra arguments that will be passed to curl like { 'curl', ..extra_curl_args, ..built_in_args } ---- @alias blink.cmp.SortFunction fun(a: blink.cmp.CompletionItem, b: blink.cmp.CompletionItem): boolean | nil +--- @alias blink.cmp.SortFunction fun(a: blink.cmp.CompletionItem, b: blink.cmp.CompletionItem): boolean | nil Fallbacks to the next sort function when returning nil +--- @alias blink.cmp.SortFunctions ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] Controls which sorts to use and in which order, falling back when the sort function returns nil + +--- @class blink.cmp.FuzzySortConfig +--- @field strong_match blink.cmp.SortFunctions Controls which sorts to use and in which order, for strong matches (based on fuzzy match score) +--- @field weak_match blink.cmp.SortFunctions Controls which sorts to use and in which order, for weak matches (based on fuzzy match score) local validate = require('blink.cmp.config.utils').validate local fuzzy = { --- @type blink.cmp.FuzzyConfig default = { use_typo_resistance = true, - use_frecency = true, - use_proximity = true, - sorts = { 'score', 'sort_text' }, + use_frecency = false, + use_proximity = false, + sort = { + strong_match = { 'score', 'sort_text' }, + weak_match = { 'sort_text', 'score' }, + }, prebuilt_binaries = { download = true, force_version = nil, @@ -35,18 +43,7 @@ function fuzzy.validate(config) use_typo_resistance = { config.use_typo_resistance, 'boolean' }, use_frecency = { config.use_frecency, 'boolean' }, use_proximity = { config.use_proximity, 'boolean' }, - sorts = { - config.sorts, - function(sorts) - for _, sort in ipairs(sorts) do - if not vim.tbl_contains({ 'label', 'sort_text', 'kind', 'score' }, sort) and type(sort) ~= 'function' then - return false - end - end - return true - end, - 'one of: "label", "sort_text", "kind", "score" or a function', - }, + sort = { config.sort, 'table' }, prebuilt_binaries = { config.prebuilt_binaries, 'table' }, }, config) validate('fuzzy.prebuilt_binaries', { @@ -55,6 +52,28 @@ function fuzzy.validate(config) force_system_triple = { config.prebuilt_binaries.force_system_triple, { 'string', 'nil' } }, extra_curl_args = { config.prebuilt_binaries.extra_curl_args, { 'table' } }, }, config.prebuilt_binaries) + + --- @param sorts blink.cmp.SortFunctions + local function validate_sort(sorts) + for _, sort in ipairs(sorts) do + if not vim.tbl_contains({ 'label', 'sort_text', 'kind', 'score' }, sort) and type(sort) ~= 'function' then + return false + end + end + return true + end + validate('fuzzy.sort', { + strong_match = { + config.sort.strong_match, + validate_sort, + 'one of: "label", "sort_text", "kind", "score" or a function', + }, + weak_match = { + config.sort.weak_match, + validate_sort, + 'one of: "label", "sort_text", "kind", "score" or a function', + }, + }, config.sort) end return fuzzy diff --git a/lua/blink/cmp/fuzzy/init.lua b/lua/blink/cmp/fuzzy/init.lua index b4a3e753..c0664b8a 100644 --- a/lua/blink/cmp/fuzzy/init.lua +++ b/lua/blink/cmp/fuzzy/init.lua @@ -65,7 +65,6 @@ function fuzzy.fuzzy(needle, haystacks_by_provider) use_typo_resistance = config.fuzzy.use_typo_resistance, use_frecency = config.fuzzy.use_frecency and #needle > 0, use_proximity = config.fuzzy.use_proximity and #needle > 0, - sorts = config.fuzzy.sorts, nearby_words = nearby_words, }) @@ -76,7 +75,12 @@ function fuzzy.fuzzy(needle, haystacks_by_provider) end end - return require('blink.cmp.fuzzy.sort').sort(filtered_items, config.fuzzy.sorts) + return require('blink.cmp.fuzzy.sort').sort( + filtered_items, + 6 * needle:len(), + config.fuzzy.sort.strong_match, + config.fuzzy.sort.weak_match + ) end return fuzzy diff --git a/lua/blink/cmp/fuzzy/sort.lua b/lua/blink/cmp/fuzzy/sort.lua index 2bfafcb2..985e38d4 100644 --- a/lua/blink/cmp/fuzzy/sort.lua +++ b/lua/blink/cmp/fuzzy/sort.lua @@ -1,9 +1,40 @@ local sort = {} +--- Similar to Zed, we split the list into two buckets, sort them separately and combine. +--- By default, the strong matches will be sorted by score and then sort_text, while the weak +--- matches will be sorted by sort_text and then score. +--- https://github.com/zed-industries/zed/blob/f64fcedab/crates/editor/src/code_context_menus.rs#L553-L566 --- @param list blink.cmp.CompletionItem[] ---- @param funcs ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] +--- @param score_threshold number +--- @param strong_match_funcs blink.cmp.SortFunctions +--- @param weak_match_funcs blink.cmp.SortFunctions --- @return blink.cmp.CompletionItem[] -function sort.sort(list, funcs) +function sort.sort(list, score_threshold, strong_match_funcs, weak_match_funcs) + local strong_matches, weak_matches = sort.partition_by_score(list, score_threshold) + + sort.list(strong_matches, strong_match_funcs) + sort.list(weak_matches, weak_match_funcs) + + return vim.list_extend(strong_matches, weak_matches) +end + +function sort.partition_by_score(list, score_threshold) + local above = {} + local below = {} + for _, item in ipairs(list) do + if item.score >= score_threshold then + table.insert(above, item) + else + table.insert(below, item) + end + end + return above, below +end + +--- @param list blink.cmp.CompletionItem[] +--- @param funcs blink.cmp.SortFunctions +--- @return blink.cmp.CompletionItem[] +function sort.list(list, funcs) local sorting_funcs = vim.tbl_map( function(name_or_func) return type(name_or_func) == 'string' and sort[name_or_func] or name_or_func end, funcs