Skip to content

Commit 683c47a

Browse files
authored
feat!: matched character highlighting, draw rework (#245)
* feat: matched character highlighting * feat: grid rendering * fix: window sizing and positioning * fix: signature help window positioning against autocomplete * fix: anchor documentation to bottom of autocomplete on north * feat: column gap and label description * feat: add draw documentation and configuration
1 parent d2a216d commit 683c47a

16 files changed

+638
-353
lines changed

Cargo.lock

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+59-5
Original file line numberDiff line numberDiff line change
@@ -404,11 +404,65 @@ MiniDeps.add({
404404
-- 'auto_insert' will not select any item by default, and insert the completion items automatically when selecting them
405405
selection = 'preselect',
406406
-- Controls how the completion items are rendered on the popup window
407-
-- 'simple' will render the item's kind icon the left alongside the label
408-
-- 'reversed' will render the label on the left and the kind icon + name on the right
409-
-- 'minimal' will render the label on the left and the kind name on the right
410-
-- 'function(blink.cmp.CompletionRenderContext): blink.cmp.Component[]' for custom rendering
411-
draw = 'simple',
407+
draw = {
408+
align_to_component = 'label', -- or 'none' to disable
409+
-- Left and right padding, optionally { left, right } for different padding on each side
410+
padding = 1,
411+
-- Gap between columns
412+
gap = 1,
413+
-- Components to render, grouped by column
414+
columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } },
415+
-- Definitions for possible components to render. Each component defines:
416+
-- ellipsis: whether to add an ellipsis when truncating the text
417+
-- width: control the min, max and fill behavior of the component
418+
-- text function: will be called for each item
419+
-- highlight function: will be called only when the line appears on screen
420+
components = {
421+
kind_icon = {
422+
ellipsis = false,
423+
text = function(ctx) return ctx.kind_icon .. ' ' end,
424+
highlight = function(ctx) return 'BlinkCmpKind' .. ctx.kind end,
425+
},
426+
427+
kind = {
428+
ellipsis = false,
429+
text = function(ctx) return ctx.kind .. ' ' end,
430+
highlight = function(ctx) return 'BlinkCmpKind' .. ctx.kind end,
431+
},
432+
433+
label = {
434+
width = { fill = true, max = 60 },
435+
text = function(ctx) return ctx.label .. (ctx.label_detail or '') end,
436+
highlight = function(ctx)
437+
-- label and label details
438+
local highlights = {
439+
{ 0, #ctx.label, group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' },
440+
}
441+
if ctx.label_detail then
442+
table.insert(
443+
highlights,
444+
{ #ctx.label + 1, #ctx.label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' }
445+
)
446+
end
447+
448+
-- characters matched on the label by the fuzzy matcher
449+
if ctx.label_matched_indices ~= nil then
450+
for _, idx in ipairs(ctx.label_matched_indices) do
451+
table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' })
452+
end
453+
end
454+
455+
return highlights
456+
end,
457+
},
458+
459+
label_description = {
460+
width = { max = 30 },
461+
text = function(ctx) return ctx.label_description or '' end,
462+
highlight = 'BlinkCmpLabelDescription',
463+
},
464+
},
465+
},
412466
-- Controls the cycling behavior when reaching the beginning or end of the completion list.
413467
cycle = {
414468
-- When `true`, calling `select_next` at the *bottom* of the completion list will select the *first* completion item.

lua/blink/cmp/config.lua

+69-8
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
--- @field winblend? number
121121
--- @field winhighlight? string
122122
--- @field scrolloff? number
123-
--- @field draw? 'simple' | 'reversed' | 'minimal' | blink.cmp.CompletionDrawFn
123+
--- @field draw? blink.cmp.Draw
124124
--- @field cycle? blink.cmp.AutocompleteConfig.CycleConfig
125125

126126
--- @class blink.cmp.AutocompleteConfig.CycleConfig
@@ -389,11 +389,65 @@ local config = {
389389
-- 'auto_insert' will not select any item by default, and insert the completion items automatically when selecting them
390390
selection = 'preselect',
391391
-- Controls how the completion items are rendered on the popup window
392-
-- 'simple' will render the item's kind icon the left alongside the label
393-
-- 'reversed' will render the label on the left and the kind icon + name on the right
394-
-- 'minimal' will render the label on the left and the kind name on the right
395-
-- 'function(blink.cmp.CompletionRenderContext): blink.cmp.Component[]' for custom rendering
396-
draw = 'simple',
392+
draw = {
393+
align_to_component = 'label', -- or 'none' to disable
394+
-- Left and right padding, optionally { left, right } for different padding on each side
395+
padding = 1,
396+
-- Gap between columns
397+
gap = 1,
398+
-- Components to render, grouped by column
399+
columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } },
400+
-- Definitions for possible components to render. Each component defines:
401+
-- ellipsis: whether to add an ellipsis when truncating the text
402+
-- width: control the min, max and fill behavior of the component
403+
-- text function: will be called for each item
404+
-- highlight function: will be called only when the line appears on screen
405+
components = {
406+
kind_icon = {
407+
ellipsis = false,
408+
text = function(ctx) return ctx.kind_icon .. ' ' end,
409+
highlight = function(ctx) return 'BlinkCmpKind' .. ctx.kind end,
410+
},
411+
412+
kind = {
413+
ellipsis = false,
414+
text = function(ctx) return ctx.kind .. ' ' end,
415+
highlight = function(ctx) return 'BlinkCmpKind' .. ctx.kind end,
416+
},
417+
418+
label = {
419+
width = { fill = true, max = 60 },
420+
text = function(ctx) return ctx.label .. (ctx.label_detail or '') end,
421+
highlight = function(ctx)
422+
-- label and label details
423+
local highlights = {
424+
{ 0, #ctx.label, group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' },
425+
}
426+
if ctx.label_detail then
427+
table.insert(
428+
highlights,
429+
{ #ctx.label + 1, #ctx.label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' }
430+
)
431+
end
432+
433+
-- characters matched on the label by the fuzzy matcher
434+
if ctx.label_matched_indices ~= nil then
435+
for _, idx in ipairs(ctx.label_matched_indices) do
436+
table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' })
437+
end
438+
end
439+
440+
return highlights
441+
end,
442+
},
443+
444+
label_description = {
445+
width = { max = 30 },
446+
text = function(ctx) return ctx.label_description or '' end,
447+
highlight = 'BlinkCmpLabelDescription',
448+
},
449+
},
450+
},
397451
-- Controls the cycling behavior when reaching the beginning or end of the completion list.
398452
cycle = {
399453
-- When `true`, calling `select_next` at the *bottom* of the completion list will select the *first* completion item.
@@ -405,7 +459,7 @@ local config = {
405459
documentation = {
406460
max_width = 80,
407461
max_height = 20,
408-
desired_min_width = 40,
462+
desired_min_width = 50,
409463
desired_min_height = 10,
410464
border = 'padded',
411465
winblend = 0,
@@ -496,6 +550,13 @@ local config = {
496550
local M = {}
497551

498552
--- @param opts blink.cmp.Config
499-
function M.merge_with(opts) config = vim.tbl_deep_extend('force', config, opts or {}) end
553+
function M.merge_with(opts)
554+
config = vim.tbl_deep_extend('force', config, opts or {})
555+
556+
-- TODO: remove on 1.0
557+
if type(config.windows.autocomplete.draw) == 'string' or type(config.windows.autocomplete.draw) == 'function' then
558+
error('The blink.cmp autocomplete draw has been rewritten, please see the README for the new configuration')
559+
end
560+
end
500561

501562
return setmetatable(M, { __index = function(_, k) return config[k] end })

lua/blink/cmp/fuzzy/init.lua

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ function fuzzy.access(item) fuzzy.rust.access(item) end
2121
---@param lines string
2222
function fuzzy.get_words(lines) return fuzzy.rust.get_words(lines) end
2323

24+
function fuzzy.fuzzy_matched_indices(needle, haystack) return fuzzy.rust.fuzzy_matched_indices(needle, haystack) end
25+
2426
---@param needle string
2527
---@param haystack blink.cmp.CompletionItem[]?
2628
---@return blink.cmp.CompletionItem[]
27-
function fuzzy.filter_items(needle, haystack)
29+
function fuzzy.fuzzy(needle, haystack)
2830
haystack = haystack or {}
2931

3032
-- get the nearby words

lua/blink/cmp/fuzzy/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ pub fn fuzzy(
6969
.collect())
7070
}
7171

72+
pub fn fuzzy_matched_indices(
73+
_lua: &Lua,
74+
(needle, haystack): (String, Vec<String>),
75+
) -> LuaResult<Vec<Vec<usize>>> {
76+
Ok(frizbee::match_list_for_matched_indices(
77+
&needle,
78+
&haystack.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
79+
))
80+
}
81+
7282
pub fn get_words(_: &Lua, text: String) -> LuaResult<Vec<String>> {
7383
Ok(REGEX
7484
.find_iter(&text)
@@ -84,6 +94,10 @@ pub fn get_words(_: &Lua, text: String) -> LuaResult<Vec<String>> {
8494
fn blink_cmp_fuzzy(lua: &Lua) -> LuaResult<LuaTable> {
8595
let exports = lua.create_table()?;
8696
exports.set("fuzzy", lua.create_function(fuzzy)?)?;
97+
exports.set(
98+
"fuzzy_matched_indices",
99+
lua.create_function(fuzzy_matched_indices)?,
100+
)?;
87101
exports.set("get_words", lua.create_function(get_words)?)?;
88102
exports.set("init_db", lua.create_function(init_db)?)?;
89103
exports.set("destroy_db", lua.create_function(destroy_db)?)?;

lua/blink/cmp/init.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ cmp.setup = function(opts)
5151
vim.schedule(function()
5252
if cmp.trigger.context == nil or cmp.trigger.context.id ~= context.id then return end
5353

54-
local filtered_items = cmp.fuzzy.filter_items(cmp.fuzzy.get_query(), items)
54+
local filtered_items = cmp.fuzzy.fuzzy(cmp.fuzzy.get_query(), items)
5555
filtered_items = cmp.sources.apply_max_items_for_completions(context, filtered_items)
5656
if #filtered_items > 0 then
5757
cmp.windows.autocomplete.open_with_items(context, filtered_items)

0 commit comments

Comments
 (0)