1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
--- @class blink.cmp.Renderer
--- @field def blink.cmp.Draw
--- @field padding number[]
--- @field gap number
--- @field columns blink.cmp.DrawColumn[]
---
--- @field new fun(draw: blink.cmp.Draw): blink.cmp.Renderer
--- @field draw fun(self: blink.cmp.Renderer, context: blink.cmp.Context, bufnr: number, items: blink.cmp.CompletionItem[])
--- @field get_component_column_location fun(self: blink.cmp.Renderer, component_name: string): { column_idx: number, component_idx: number }
--- @field get_component_start_col fun(self: blink.cmp.Renderer, component_name: string): number
--- @field get_alignment_start_col fun(self: blink.cmp.Renderer): number
local ns = vim.api.nvim_create_namespace('blink_cmp_renderer')
--- @type blink.cmp.Renderer
--- @diagnostic disable-next-line: missing-fields
local renderer = {}
function renderer.new(draw)
--- Convert the component names in the columns to the component definitions
--- @type blink.cmp.DrawComponent[][]
local columns_definitions = vim.tbl_map(function(column)
local components = {}
for _, component_name in ipairs(column) do
local component = draw.components[component_name]
assert(component ~= nil, 'No component definition found for component: "' .. component_name .. '"')
table.insert(components, draw.components[component_name])
end
return {
components = components,
gap = column.gap or 0,
}
end, draw.columns)
local padding = type(draw.padding) == 'number' and { draw.padding, draw.padding } or draw.padding
--- @cast padding number[]
local self = setmetatable({}, { __index = renderer })
self.padding = padding
self.gap = draw.gap
self.def = draw
self.columns = vim.tbl_map(
function(column_definition)
return require('blink.cmp.completion.windows.render.column').new(
column_definition.components,
column_definition.gap
)
end,
columns_definitions
)
return self
end
function renderer:draw(context, bufnr, items)
-- gather contexts
local draw_contexts = require('blink.cmp.completion.windows.render.context').get_from_items(context, self.def, items)
-- render the columns
for _, column in ipairs(self.columns) do
column:render(draw_contexts)
end
-- apply to the buffer
local lines = {}
for idx, _ in ipairs(draw_contexts) do
local line = ''
if self.padding[1] > 0 then line = string.rep(' ', self.padding[1]) end
for _, column in ipairs(self.columns) do
local text = column:get_line_text(idx)
if #text > 0 then line = line .. text .. string.rep(' ', self.gap) end
end
line = line:sub(1, -self.gap - 1)
if self.padding[2] > 0 then line = line .. string.rep(' ', self.padding[2]) end
table.insert(lines, line)
end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.api.nvim_set_option_value('modified', false, { buf = bufnr })
-- Setting highlights is slow and we update on every keystroke so we instead use a decoration provider
-- which will only render highlights of the visible lines. This also avoids having to do virtual scroll
-- like nvim-cmp does, which breaks on UIs like neovide
vim.api.nvim_set_decoration_provider(ns, {
on_win = function(_, _, win_bufnr) return bufnr == win_bufnr end,
on_line = function(_, _, _, line)
local offset = self.padding[1]
for _, column in ipairs(self.columns) do
local text = column:get_line_text(line + 1)
if #text > 0 then
local highlights = column:get_line_highlights(line + 1)
for _, highlight in ipairs(highlights) do
local col = offset + highlight[1]
local end_col = offset + highlight[2]
vim.api.nvim_buf_set_extmark(bufnr, ns, line, col, {
end_col = end_col,
hl_group = highlight.group,
hl_mode = 'combine',
hl_eol = true,
ephemeral = true,
})
end
offset = offset + #text + self.gap
end
end
end,
})
end
function renderer:get_component_column_location(component_name)
for column_idx, column in ipairs(self.def.columns) do
for component_idx, other_component_name in ipairs(column) do
if other_component_name == component_name then return { column_idx, component_idx } end
end
end
error('No component found with name: ' .. component_name)
end
function renderer:get_component_start_col(component_name)
local column_idx, component_idx = unpack(self:get_component_column_location(component_name))
-- add previous columns
local start_col = self.padding[1]
for i = 1, column_idx - 1 do
start_col = start_col + self.columns[i].width + self.gap
end
-- add previous components
local line = self.columns[column_idx].lines[1]
if not line then return start_col end
for i = 1, component_idx - 1 do
start_col = start_col + #line[i]
end
return start_col
end
function renderer:get_alignment_start_col()
local component_name = self.def.align_to
if component_name == nil or component_name == 'none' or component_name == 'cursor' then return 0 end
return self:get_component_start_col(component_name)
end
return renderer
|