Skip to content

Commit a4f5f8e

Browse files
authored
fix!: autocompletion window components alignment (#51)
1 parent 88f71b1 commit a4f5f8e

File tree

5 files changed

+82
-94
lines changed

5 files changed

+82
-94
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,6 @@ For LazyVim/distro users, you can disable nvim-cmp via:
235235

236236
windows = {
237237
autocomplete = {
238-
min_width = 30,
239-
max_width = 60,
240238
max_height = 10,
241239
border = 'none',
242240
winhighlight = 'Normal:BlinkCmpMenu,FloatBorder:BlinkCmpMenuBorder,CursorLine:BlinkCmpMenuSelection,Search:None',

lua/blink/cmp/config.lua

-4
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@
8282
--- @field use_nvim_cmp_as_default? boolean
8383

8484
--- @class blink.cmp.AutocompleteConfig
85-
--- @field min_width? number
86-
--- @field max_width? number
8785
--- @field max_height? number
8886
--- @field border? blink.cmp.WindowBorder
8987
--- @field order? "top_down" | "bottom_up"
@@ -234,8 +232,6 @@ local config = {
234232

235233
windows = {
236234
autocomplete = {
237-
min_width = 30,
238-
max_width = 60,
239235
max_height = 10,
240236
border = 'none',
241237
winhighlight = 'Normal:BlinkCmpMenu,FloatBorder:BlinkCmpMenuBorder,CursorLine:BlinkCmpMenuSelection,Search:None',

lua/blink/cmp/windows/autocomplete.lua

+13-11
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ local autocomplete = {
2121

2222
function autocomplete.setup()
2323
autocomplete.win = require('blink.cmp.windows.lib').new({
24-
min_width = autocmp_config.min_width,
25-
max_width = autocmp_config.max_width,
2624
max_height = autocmp_config.max_height,
2725
border = autocmp_config.border,
2826
winhighlight = autocmp_config.winhighlight,
@@ -192,13 +190,13 @@ end
192190
function autocomplete.draw()
193191
local draw_fn = autocomplete.get_draw_fn()
194192
local icon_gap = config.nerd_font_variant == 'mono' and ' ' or ' '
195-
local arr_of_components = {}
193+
local components_list = {}
196194
for _, item in ipairs(autocomplete.items) do
197195
local kind = require('blink.cmp.types').CompletionItemKind[item.kind] or 'Unknown'
198196
local kind_icon = config.kind_icons[kind] or config.kind_icons.Field
199197

200198
table.insert(
201-
arr_of_components,
199+
components_list,
202200
draw_fn({
203201
item = item,
204202
kind = kind,
@@ -209,11 +207,10 @@ function autocomplete.draw()
209207
)
210208
end
211209

212-
local max_line_length =
213-
math.min(autocmp_config.max_width, math.max(autocmp_config.min_width, renderer.get_max_length(arr_of_components)))
210+
local max_lengths = renderer.get_max_lengths(components_list)
214211
autocomplete.rendered_items = vim.tbl_map(
215-
function(component) return renderer.render(component, max_line_length) end,
216-
arr_of_components
212+
function(component) return renderer.render(component, max_lengths) end,
213+
components_list
217214
)
218215

219216
local lines = vim.tbl_map(function(rendered) return rendered.text end, autocomplete.rendered_items)
@@ -237,21 +234,26 @@ end
237234
--- @return blink.cmp.Component[]
238235
function autocomplete.render_item_simple(ctx)
239236
return {
240-
{ ' ', ctx.kind_icon, ctx.icon_gap, hl_group = 'BlinkCmpKind' .. ctx.kind },
237+
' ',
238+
{ ctx.kind_icon, ctx.icon_gap, hl_group = 'BlinkCmpKind' .. ctx.kind },
241239
{ ctx.item.label, fill = true, hl_group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' },
240+
' ',
242241
}
243242
end
244243

245244
--- @param ctx blink.cmp.CompletionRenderContext
246245
--- @return blink.cmp.Component[]
247246
function autocomplete.render_item_reversed(ctx)
248247
return {
248+
' ',
249249
{
250-
' ' .. ctx.item.label,
250+
ctx.item.label,
251251
fill = true,
252252
hl_group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel',
253253
},
254-
{ ' ', ctx.kind_icon, ctx.icon_gap, ctx.kind .. ' ', hl_group = 'BlinkCmpKind' .. ctx.kind },
254+
' ',
255+
{ ctx.kind_icon, ctx.icon_gap, ctx.kind, hl_group = 'BlinkCmpKind' .. ctx.kind },
256+
' ',
255257
}
256258
end
257259

lua/blink/cmp/windows/lib/init.lua

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
local win = {}
22

33
--- @class blink.cmp.WindowOptions
4-
--- @field min_width number
5-
--- @field max_width number
4+
--- @field min_width? number
5+
--- @field max_width? number
66
--- @field max_height number
77
--- @field cursorline boolean
88
--- @field border blink.cmp.WindowBorder
@@ -12,7 +12,7 @@ local win = {}
1212
--- @field scrolloff number
1313

1414
--- @class blink.cmp.Window
15-
--- @field id number | nil
15+
--- @field id? number
1616
--- @field config blink.cmp.WindowOptions
1717
---
1818
--- @param config blink.cmp.WindowOptions
@@ -21,8 +21,8 @@ function win.new(config)
2121

2222
self.id = nil
2323
self.config = {
24-
min_width = config.min_width or 30,
25-
max_width = config.max_width or 60,
24+
min_width = config.min_width,
25+
max_width = config.max_width,
2626
max_height = config.max_height or 10,
2727
cursorline = config.cursorline or false,
2828
border = config.border or 'none',
@@ -63,7 +63,7 @@ function win:open()
6363
self.id = vim.api.nvim_open_win(self:get_buf(), false, {
6464
relative = 'cursor',
6565
style = 'minimal',
66-
width = self.config.min_width,
66+
width = self.config.min_width or 1,
6767
height = self.config.max_height,
6868
row = 1,
6969
col = 1,
@@ -81,9 +81,7 @@ function win:open()
8181
vim.api.nvim_set_option_value('scrolloff', self.config.scrolloff, { win = self.id })
8282
end
8383

84-
function win:set_option_values(option, value)
85-
vim.api.nvim_set_option_value(option, value, { win = self.id })
86-
end
84+
function win:set_option_values(option, value) vim.api.nvim_set_option_value(option, value, { win = self.id }) end
8785

8886
function win:close()
8987
if self.id ~= nil then
@@ -100,7 +98,9 @@ function win:update_size()
10098
-- todo: never go above the screen width and height
10199

102100
-- set width to current content width, bounded by min and max
103-
local width = math.max(math.min(self:get_content_width(), config.max_width), config.min_width)
101+
local width = self:get_content_width()
102+
if config.max_width then width = math.min(width, config.max_width) end
103+
if config.min_width then width = math.max(width, config.min_width) end
104104
vim.api.nvim_win_set_width(winnr, width)
105105

106106
-- set height to current line count, bounded by max

lua/blink/cmp/windows/lib/render.lua

+59-67
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
--- @class blink.cmp.Component
22
--- @field [number] blink.cmp.Component | string
3-
--- @field fill boolean | nil
4-
--- @field hl_group string | nil
5-
--- @field hl_params table | nil
3+
--- @field fill? boolean
4+
--- @field max_width? number
5+
--- @field hl_group? string
6+
--- @field hl_params? table
67

78
--- @class blink.cmp.RenderedComponentTree
89
--- @field text string
9-
--- @field highlights { start: number, stop: number, group: string | nil, params: table | nil }[]
10+
--- @field highlights { start: number, stop: number, group?: string, params?: table }[]
11+
12+
--- @class blink.cmp.StringsBuild
13+
--- @field text string
14+
--- @field length number
1015

1116
local renderer = {}
1217

18+
---@param text string
19+
---@param max_width number
20+
---@return string
21+
function renderer.truncate_text(text, max_width)
22+
if vim.api.nvim_strwidth(text) > max_width then
23+
return vim.fn.strcharpart(text, 0, max_width) .. ''
24+
else
25+
return text
26+
end
27+
end
28+
1329
--- Draws the highlights for the rendered component tree
1430
--- as ephemeral extmarks
1531
--- @param rendered blink.cmp.RenderedComponentTree
@@ -25,101 +41,77 @@ function renderer.draw_highlights(rendered, bufnr, ns, line_number)
2541
end
2642
end
2743

28-
--- @param components blink.cmp.Component[]
29-
--- @param length number
30-
--- @return blink.cmp.RenderedComponentTree
31-
function renderer.render(components, length)
32-
local left_of_fill = {}
33-
local right_of_fill = {}
34-
local fill = nil
35-
for _, component in ipairs(components) do
36-
if component.fill then
37-
fill = component
38-
else
39-
table.insert(fill and right_of_fill or left_of_fill, component)
40-
end
44+
---@param strings string[]
45+
---@param max_width? number
46+
---@return blink.cmp.StringsBuild
47+
function renderer.build_strings(strings, max_width)
48+
local text = ''
49+
for _, component in ipairs(strings) do
50+
text = text .. component
4151
end
4252

43-
local left_rendered = renderer.render_components(left_of_fill)
44-
local fill_rendered = renderer.render_components({ fill })
45-
local right_rendered = renderer.render_components(right_of_fill)
46-
47-
-- expanad/truncate the fill component to the width
48-
fill_rendered.text = fill_rendered.text
49-
.. string.rep(' ', length - vim.api.nvim_strwidth(left_rendered.text .. fill_rendered.text .. right_rendered.text))
50-
fill_rendered.text = fill_rendered.text:sub(
51-
1,
52-
length - vim.api.nvim_strwidth(left_rendered.text) - vim.api.nvim_strwidth(right_rendered.text)
53-
)
54-
55-
renderer.add_offset_to_rendered_component(fill_rendered, left_rendered.text:len())
56-
renderer.add_offset_to_rendered_component(right_rendered, left_rendered.text:len() + fill_rendered.text:len())
57-
58-
local highlights = {}
59-
vim.list_extend(highlights, left_rendered.highlights)
60-
vim.list_extend(highlights, fill_rendered.highlights)
61-
vim.list_extend(highlights, right_rendered.highlights)
62-
63-
return {
64-
text = left_rendered.text .. fill_rendered.text .. right_rendered.text,
65-
highlights = highlights,
66-
}
53+
if max_width then text = renderer.truncate_text(text, max_width) end
54+
return { text = text, length = vim.api.nvim_strwidth(text) }
6755
end
6856

6957
--- @param components (blink.cmp.Component | string)[]
58+
--- @param lengths number[]
7059
--- @return blink.cmp.RenderedComponentTree
71-
function renderer.render_components(components)
60+
function renderer.render(components, lengths)
7261
local text = ''
7362
local offset = 0
7463
local highlights = {}
7564

76-
for _, component in ipairs(components) do
65+
for i, component in ipairs(components) do
7766
if type(component) == 'string' then
7867
text = text .. component
7968
offset = offset + #component
8069
else
81-
local rendered = renderer.render_components(component)
70+
local build = renderer.build_strings(component, component.max_width)
8271

83-
renderer.add_offset_to_rendered_component(rendered, offset)
8472
table.insert(highlights, {
8573
start = offset + 1,
86-
stop = offset + #rendered.text + 1,
74+
stop = offset + #build.text + 1,
8775
group = component.hl_group,
8876
params = component.hl_params,
8977
})
90-
vim.list_extend(highlights, rendered.highlights)
9178

92-
text = text .. rendered.text
93-
offset = offset + #rendered.text
79+
text = text .. build.text
80+
offset = offset + #build.text
81+
82+
if component.fill then
83+
local spaces = lengths[i] - build.length
84+
text = text .. string.rep(' ', spaces)
85+
offset = offset + spaces
86+
end
9487
end
9588
end
9689

9790
return { text = text, highlights = highlights }
9891
end
9992

100-
--- @param components blink.cmp.Component[]
93+
--- @param component blink.cmp.Component | string
10194
--- @return number
102-
function renderer.get_length(components)
103-
local length = 0
104-
for _, component in ipairs(components) do
105-
if type(component) == 'string' then
106-
length = length + #component
107-
else
108-
length = length + renderer.get_length(component)
109-
end
95+
function renderer.get_length(component)
96+
if type(component) == 'string' then
97+
return vim.api.nvim_strwidth(component)
98+
else
99+
local build = renderer.build_strings(component, component.max_width)
100+
return build.length
110101
end
111-
return length
112102
end
113103

114-
--- @param arr_of_components blink.cmp.Component[][]
115-
--- @return number
116-
function renderer.get_max_length(arr_of_components)
117-
local max_length = 0
118-
for _, components in ipairs(arr_of_components) do
119-
local length = renderer.get_length(components)
120-
if length > max_length then max_length = length end
104+
--- @param components_list (blink.cmp.Component | string)[][]
105+
--- @return number[]
106+
function renderer.get_max_lengths(components_list)
107+
local lengths = {}
108+
for _, components in ipairs(components_list) do
109+
for i, component in ipairs(components) do
110+
local length = renderer.get_length(component)
111+
if not lengths[i] or lengths[i] < length then lengths[i] = length end
112+
end
121113
end
122-
return max_length
114+
return lengths
123115
end
124116

125117
--- @param component blink.cmp.RenderedComponentTree

0 commit comments

Comments
 (0)