Fix rendering of wide characters in the custom completion window (#641)
This commit is contained in:
@@ -198,27 +198,34 @@ entry.is_deprecated = function(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Return view information.
|
---Return view information.
|
||||||
|
---@param suggest_offset number
|
||||||
|
---@param entries_buf number The buffer this entry will be rendered into.
|
||||||
---@return { abbr: { text: string, bytes: number, width: number, hl_group: string }, kind: { text: string, bytes: number, width: number, hl_group: string }, menu: { text: string, bytes: number, width: number, hl_group: string } }
|
---@return { abbr: { text: string, bytes: number, width: number, hl_group: string }, kind: { text: string, bytes: number, width: number, hl_group: string }, menu: { text: string, bytes: number, width: number, hl_group: string } }
|
||||||
entry.get_view = function(self, suggest_offset)
|
entry.get_view = function(self, suggest_offset, entries_buf)
|
||||||
local item = self:get_vim_item(suggest_offset)
|
local item = self:get_vim_item(suggest_offset)
|
||||||
return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0 }, function()
|
return self.cache:ensure({ 'get_view', self.resolved_completion_item and 1 or 0, entries_buf }, function()
|
||||||
local view = {}
|
local view = {}
|
||||||
|
-- The result of vim.fn.strdisplaywidth depends on which buffer it was
|
||||||
|
-- called in because it reads the values of the option 'tabstop' when
|
||||||
|
-- rendering <Tab> characters.
|
||||||
|
vim.api.nvim_buf_call(entries_buf, function()
|
||||||
view.abbr = {}
|
view.abbr = {}
|
||||||
view.abbr.text = item.abbr or ''
|
view.abbr.text = item.abbr or ''
|
||||||
view.abbr.bytes = #view.abbr.text
|
view.abbr.bytes = #view.abbr.text
|
||||||
view.abbr.width = vim.str_utfindex(view.abbr.text)
|
view.abbr.width = vim.fn.strdisplaywidth(view.abbr.text)
|
||||||
view.abbr.hl_group = self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr'
|
view.abbr.hl_group = self:is_deprecated() and 'CmpItemAbbrDeprecated' or 'CmpItemAbbr'
|
||||||
view.kind = {}
|
view.kind = {}
|
||||||
view.kind.text = item.kind or ''
|
view.kind.text = item.kind or ''
|
||||||
view.kind.bytes = #view.kind.text
|
view.kind.bytes = #view.kind.text
|
||||||
view.kind.width = vim.str_utfindex(view.kind.text)
|
view.kind.width = vim.fn.strdisplaywidth(view.kind.text)
|
||||||
view.kind.hl_group = 'CmpItemKind' .. types.lsp.CompletionItemKind[self:get_kind()]
|
view.kind.hl_group = 'CmpItemKind' .. types.lsp.CompletionItemKind[self:get_kind()]
|
||||||
view.menu = {}
|
view.menu = {}
|
||||||
view.menu.text = item.menu or ''
|
view.menu.text = item.menu or ''
|
||||||
view.menu.bytes = #view.menu.text
|
view.menu.bytes = #view.menu.text
|
||||||
view.menu.width = vim.str_utfindex(view.menu.text)
|
view.menu.width = vim.fn.strdisplaywidth(view.menu.text)
|
||||||
view.menu.hl_group = 'CmpItemMenu'
|
view.menu.hl_group = 'CmpItemMenu'
|
||||||
view.dup = item.dup
|
view.dup = item.dup
|
||||||
|
end)
|
||||||
return view
|
return view
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
local buffer = {}
|
local buffer = {}
|
||||||
|
|
||||||
buffer.ensure = setmetatable({
|
buffer.cache = {}
|
||||||
cache = {},
|
|
||||||
}, {
|
---@return number buf
|
||||||
__call = function(self, name)
|
buffer.get = function(name)
|
||||||
if not (self.cache[name] and vim.api.nvim_buf_is_valid(self.cache[name])) then
|
local buf = buffer.cache[name]
|
||||||
local buf = vim.api.nvim_create_buf(false, true)
|
if buf and vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
return buf
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number buf
|
||||||
|
---@return boolean created_new
|
||||||
|
buffer.ensure = function(name)
|
||||||
|
local created_new = false
|
||||||
|
local buf = buffer.get(name)
|
||||||
|
if not buf then
|
||||||
|
created_new = true
|
||||||
|
buf = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
|
vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
|
||||||
vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide')
|
vim.api.nvim_buf_set_option(buf, 'bufhidden', 'hide')
|
||||||
self.cache[name] = buf
|
buffer.cache[name] = buf
|
||||||
|
end
|
||||||
|
return buf, created_new
|
||||||
end
|
end
|
||||||
return self.cache[name]
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ local api = require('cmp.utils.api')
|
|||||||
---@field public swin2 number|nil
|
---@field public swin2 number|nil
|
||||||
---@field public style cmp.WindowStyle
|
---@field public style cmp.WindowStyle
|
||||||
---@field public opt table<string, any>
|
---@field public opt table<string, any>
|
||||||
|
---@field public buffer_opt table<string, any>
|
||||||
---@field public cache cmp.Cache
|
---@field public cache cmp.Cache
|
||||||
local window = {}
|
local window = {}
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ window.new = function()
|
|||||||
self.style = {}
|
self.style = {}
|
||||||
self.cache = cache.new()
|
self.cache = cache.new()
|
||||||
self.opt = {}
|
self.opt = {}
|
||||||
|
self.buffer_opt = {}
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -54,6 +56,26 @@ window.option = function(self, key, value)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Set buffer option.
|
||||||
|
---NOTE: If the buffer already visible, immediately applied to it.
|
||||||
|
---@param key string
|
||||||
|
---@param value any
|
||||||
|
window.buffer_option = function(self, key, value)
|
||||||
|
if vim.fn.exists('+' .. key) == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if value == nil then
|
||||||
|
return self.buffer_opt[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
self.buffer_opt[key] = value
|
||||||
|
local existing_buf = buffer.get(self.name)
|
||||||
|
if existing_buf then
|
||||||
|
vim.api.nvim_buf_set_option(existing_buf, key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---Set style.
|
---Set style.
|
||||||
---@param style cmp.WindowStyle
|
---@param style cmp.WindowStyle
|
||||||
window.set_style = function(self, style)
|
window.set_style = function(self, style)
|
||||||
@@ -70,7 +92,13 @@ end
|
|||||||
---Return buffer id.
|
---Return buffer id.
|
||||||
---@return number
|
---@return number
|
||||||
window.get_buffer = function(self)
|
window.get_buffer = function(self)
|
||||||
return buffer.ensure(self.name)
|
local buf, created_new = buffer.ensure(self.name)
|
||||||
|
if created_new then
|
||||||
|
for k, v in pairs(self.buffer_opt) do
|
||||||
|
vim.api.nvim_buf_set_option(buf, k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return buf
|
||||||
end
|
end
|
||||||
|
|
||||||
---Open window
|
---Open window
|
||||||
@@ -89,7 +117,7 @@ window.open = function(self, style)
|
|||||||
else
|
else
|
||||||
local s = misc.copy(self.style)
|
local s = misc.copy(self.style)
|
||||||
s.noautocmd = true
|
s.noautocmd = true
|
||||||
self.win = vim.api.nvim_open_win(buffer.ensure(self.name), false, s)
|
self.win = vim.api.nvim_open_win(self:get_buffer(), false, s)
|
||||||
for k, v in pairs(self.opt) do
|
for k, v in pairs(self.opt) do
|
||||||
vim.api.nvim_win_set_option(self.win, k, v)
|
vim.api.nvim_win_set_option(self.win, k, v)
|
||||||
end
|
end
|
||||||
@@ -251,9 +279,14 @@ window.get_content_height = function(self)
|
|||||||
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
vim.api.nvim_buf_get_changedtick(self:get_buffer()),
|
||||||
}, function()
|
}, function()
|
||||||
local height = 0
|
local height = 0
|
||||||
for _, text in ipairs(vim.api.nvim_buf_get_lines(self:get_buffer(), 0, -1, false)) do
|
local buf = self:get_buffer()
|
||||||
height = height + math.ceil(math.max(1, vim.str_utfindex(text)) / self.style.width)
|
-- The result of vim.fn.strdisplaywidth depends on the buffer it was called
|
||||||
|
-- in (see comment in cmp.Entry.get_view).
|
||||||
|
vim.api.nvim_buf_call(buf, function()
|
||||||
|
for _, text in ipairs(vim.api.nvim_buf_get_lines(buf, 0, -1, false)) do
|
||||||
|
height = height + math.ceil(math.max(1, vim.fn.strdisplaywidth(text)) / self.style.width)
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
return height
|
return height
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ custom_entries_view.new = function()
|
|||||||
self.entries_win:option('wrap', false)
|
self.entries_win:option('wrap', false)
|
||||||
self.entries_win:option('scrolloff', 0)
|
self.entries_win:option('scrolloff', 0)
|
||||||
self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
|
self.entries_win:option('winhighlight', 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None')
|
||||||
|
-- This is done so that strdisplaywidth calculations for lines in the
|
||||||
|
-- custom_entries_view window exactly match with what is really displayed,
|
||||||
|
-- see comment in cmp.Entry.get_view. Setting tabstop to 1 makes all tabs be
|
||||||
|
-- always rendered one column wide, which removes the unpredictability coming
|
||||||
|
-- from variable width of the tab character.
|
||||||
|
self.entries_win:buffer_option('tabstop', 1)
|
||||||
self.event = event.new()
|
self.event = event.new()
|
||||||
self.offset = -1
|
self.offset = -1
|
||||||
self.active = false
|
self.active = false
|
||||||
@@ -48,7 +54,7 @@ custom_entries_view.new = function()
|
|||||||
|
|
||||||
vim.api.nvim_set_decoration_provider(custom_entries_view.ns, {
|
vim.api.nvim_set_decoration_provider(custom_entries_view.ns, {
|
||||||
on_win = function(_, win, buf, top, bot)
|
on_win = function(_, win, buf, top, bot)
|
||||||
if win ~= self.entries_win.win then
|
if win ~= self.entries_win.win or buf ~= self.entries_win:get_buffer() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,7 +62,7 @@ custom_entries_view.new = function()
|
|||||||
for i = top, bot do
|
for i = top, bot do
|
||||||
local e = self.entries[i + 1]
|
local e = self.entries[i + 1]
|
||||||
if e then
|
if e then
|
||||||
local v = e:get_view(self.offset)
|
local v = e:get_view(self.offset, buf)
|
||||||
local o = SIDE_PADDING
|
local o = SIDE_PADDING
|
||||||
local a = 0
|
local a = 0
|
||||||
for _, field in ipairs(fields) do
|
for _, field in ipairs(fields) do
|
||||||
@@ -106,12 +112,13 @@ custom_entries_view.open = function(self, offset, entries)
|
|||||||
-- Apply window options (that might be changed) on the custom completion menu.
|
-- Apply window options (that might be changed) on the custom completion menu.
|
||||||
self.entries_win:option('winblend', vim.o.pumblend)
|
self.entries_win:option('winblend', vim.o.pumblend)
|
||||||
|
|
||||||
|
local entries_buf = self.entries_win:get_buffer()
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local dedup = {}
|
local dedup = {}
|
||||||
local preselect = 0
|
local preselect = 0
|
||||||
local i = 1
|
local i = 1
|
||||||
for _, e in ipairs(entries) do
|
for _, e in ipairs(entries) do
|
||||||
local view = e:get_view(offset)
|
local view = e:get_view(offset, entries_buf)
|
||||||
if view.dup == 1 or not dedup[e.completion_item.label] then
|
if view.dup == 1 or not dedup[e.completion_item.label] then
|
||||||
dedup[e.completion_item.label] = true
|
dedup[e.completion_item.label] = true
|
||||||
self.column_width.abbr = math.max(self.column_width.abbr, view.abbr.width)
|
self.column_width.abbr = math.max(self.column_width.abbr, view.abbr.width)
|
||||||
@@ -125,7 +132,7 @@ custom_entries_view.open = function(self, offset, entries)
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_set_lines(self.entries_win:get_buffer(), 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(entries_buf, 0, -1, false, lines)
|
||||||
|
|
||||||
local width = 0
|
local width = 0
|
||||||
width = width + 1
|
width = width + 1
|
||||||
@@ -193,10 +200,11 @@ custom_entries_view.draw = function(self)
|
|||||||
local botline = info.topline + info.height - 1
|
local botline = info.topline + info.height - 1
|
||||||
local texts = {}
|
local texts = {}
|
||||||
local fields = config.get().formatting.fields
|
local fields = config.get().formatting.fields
|
||||||
|
local entries_buf = self.entries_win:get_buffer()
|
||||||
for i = topline, botline - 1 do
|
for i = topline, botline - 1 do
|
||||||
local e = self.entries[i + 1]
|
local e = self.entries[i + 1]
|
||||||
if e then
|
if e then
|
||||||
local view = e:get_view(self.offset)
|
local view = e:get_view(self.offset, entries_buf)
|
||||||
local text = {}
|
local text = {}
|
||||||
table.insert(text, string.rep(' ', SIDE_PADDING))
|
table.insert(text, string.rep(' ', SIDE_PADDING))
|
||||||
for _, field in ipairs(fields) do
|
for _, field in ipairs(fields) do
|
||||||
@@ -207,7 +215,7 @@ custom_entries_view.draw = function(self)
|
|||||||
table.insert(texts, table.concat(text, ''))
|
table.insert(texts, table.concat(text, ''))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_set_lines(self.entries_win:get_buffer(), topline, botline, false, texts)
|
vim.api.nvim_buf_set_lines(entries_buf, topline, botline, false, texts)
|
||||||
|
|
||||||
if api.is_cmdline_mode() then
|
if api.is_cmdline_mode() then
|
||||||
vim.api.nvim_win_call(self.entries_win.win, function()
|
vim.api.nvim_win_call(self.entries_win.win, function()
|
||||||
|
|||||||
Reference in New Issue
Block a user